jsgen: fixes and improvements

pull/5442/head
spaceface777 2020-06-20 13:22:49 +02:00 committed by GitHub
parent ddd83f1fc6
commit a02aff9126
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 376 additions and 1364 deletions

View File

@ -4,13 +4,40 @@
module builtin
fn JS.console.log(arg ...string)
fn JS.process.stdout.write(arg string)
pub fn println(s any) {
JS.console.log(s)
}
pub fn print(s any) {
JS.process.stdout.write(s)
// TODO
// $if js.node {
JS.process.stdout.write(s)
// } $else {
// panic('Cannot `print` in a browser, use `println` instead')
// }
}
pub fn eprintln(s any) {
JS.console.error(s)
}
pub fn eprint(s any) {
// TODO
// $if js.node {
JS.process.stderr.write(s)
// } $else {
// panic('Cannot `eprint` in a browser, use `eprintln` instead')
// }
}
// Exits the process in node, and halts execution in the browser
// because `process.exit` is undefined. Workaround for not having
// a 'real' way to exit in the browser.
pub fn exit(c int) {
JS.process.exit(c)
}
pub fn panic(s string) {
eprintln('V panic: $s')
exit(1)
}

View File

@ -0,0 +1,65 @@
// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
// This file contains JS functions present in both node and the browser.
// They have been ported from their TypeScript definitions.
module builtin
// Top level functions
fn JS.eval(string) any
fn JS.parseInt(string, f64) f64
fn JS.parseFloat(string) f64
fn JS.isNaN(f64) bool
fn JS.isFinite(f64) bool
fn JS.decodeURI(string) string
fn JS.decodeURIComponent(string) string
fn JS.encodeURI(string) string
type EncodeURIComponentArg = string | f64 | bool
fn JS.encodeURIComponent(EncodeURIComponentArg) string
fn JS.escape(string) string
fn JS.unescape(string) string
// console
fn JS.console.assert(bool, ...any)
fn JS.console.clear()
fn JS.console.count(string)
fn JS.console.countReset(string)
fn JS.console.debug(...any)
fn JS.console.dir(any, any)
fn JS.console.dirxml(...any)
fn JS.console.error(...any)
fn JS.console.exception(string, ...any)
fn JS.console.group(...any)
fn JS.console.groupCollapsed(...any)
fn JS.console.groupEnd()
fn JS.console.info(...any)
fn JS.console.log(...any)
fn JS.console.table(any, []string)
fn JS.console.time(string)
fn JS.console.timeEnd(string)
fn JS.console.timeLog(string, ...any)
fn JS.console.timeStamp(string)
fn JS.console.trace(...any)
fn JS.console.warn(...any)
// Math
fn JS.Math.abs(f64) f64
fn JS.Math.acos(f64) f64
fn JS.Math.asin(f64) f64
fn JS.Math.atan(f64) f64
fn JS.Math.atan2(f64, f64) f64
fn JS.Math.ceil(f64) f64
fn JS.Math.cos(f64) f64
fn JS.Math.exp(f64) f64
fn JS.Math.floor(f64) f64
fn JS.Math.log(f64) f64
fn JS.Math.max(...f64) f64
fn JS.Math.min(...f64) f64
fn JS.Math.pow(f64, f64) f64
fn JS.Math.random() f64
fn JS.Math.round(f64) f64
fn JS.Math.sin(f64) f64
fn JS.Math.sqrt(f64) f64
fn JS.Math.tan(f64) f64

View File

@ -0,0 +1,53 @@
// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
// This file contains JS functions only present in the browser.
// They have been ported from their TypeScript definitions.
module builtin
// Window
fn JS.atob(string) string
fn JS.btoa(string) string
fn JS.clearInterval(int)
fn JS.clearTimeout(int)
// fn JS.createImageBitmap(ImageBitmapSource, ImageBitmapOptions) Promise<ImageBitmap>
// fn JS.createImageBitmap(ImageBitmapSource, int, int, int, int, ImageBitmapOptions) Promise<ImageBitmap>
// TODO: js async attribute
// [js_async]
// fn JS.fetch(RequestInfo, RequestInit) Promise<Response>
fn JS.queueMicrotask(fn())
fn JS.setInterval(any, int, ...any) int
fn JS.setTimeout(any, int, ...any) int
fn JS.alert(any)
fn JS.blur()
fn JS.captureEvents()
fn JS.close()
fn JS.confirm(string) bool
// fn JS.departFocus(NavigationReason, FocusNavigationOrigin)
fn JS.focus()
// fn JS.getComputedStyle(Element, string | null) CSSStyleDeclaration
// fn JS.getMatchedCSSRules(Element, string | null) CSSRuleList
// fn JS.getSelection() Selection | null
// fn JS.matchMedia(string) MediaQueryList
fn JS.moveBy(int, int)
fn JS.moveTo(int, int)
fn JS.msWriteProfilerMark(string)
// fn JS.open(string, string, string, bool) ?Window
// fn JS.postMessage(any, string, []Transferable)
fn JS.print()
fn JS.prompt(string, string) ?string
fn JS.releaseEvents()
fn JS.resizeBy(int, int)
fn JS.resizeTo(int, int)
// fn JS.scroll(ScrollToOptions)
fn JS.scroll(int, int)
//fn JS.scrollBy(ScrollToOptions)
fn JS.scrollBy(int, int)
// fn JS.scrollTo(ScrollToOptions)
fn JS.scrollTo(int, int)
fn JS.stop()

View File

@ -0,0 +1,14 @@
// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
// This file contains JS functions only present in node.js.
// They have been ported from their TypeScript definitions.
module builtin
fn JS.process.exit(int)
fn JS.process.stdout.write(string) bool
fn JS.process.stdout.writeln(string) bool
fn JS.process.stderr.write(string) bool
fn JS.process.stderr.writeln(string) bool

View File

@ -15,10 +15,6 @@ const (
'protected', 'public', 'return', 'static', 'super', 'switch', 'this', 'throw', 'try', 'typeof',
'var', 'void', 'while', 'with', 'yield']
tabs = ['', '\t', '\t\t', '\t\t\t', '\t\t\t\t', '\t\t\t\t\t', '\t\t\t\t\t\t', '\t\t\t\t\t\t\t', '\t\t\t\t\t\t\t\t']
builtin_globals = ['println', 'print']
type_values = {
}
)
struct JsGen {
@ -44,7 +40,8 @@ mut:
defer_stmts []ast.DeferStmt
fn_decl &ast.FnDecl // pointer to the FnDecl we are currently inside otherwise 0
str_types []string // types that need automatic str() generation
method_fn_decls map[string][]ast.Stmt
method_fn_decls map[string][]ast.FnDecl
builtin_fns []string // Functions defined in `builtin`
empty_line bool
}
@ -99,7 +96,7 @@ pub fn gen(files []ast.File, table &table.Table, pref &pref.Preferences) string
mut out := g.hashes() + g.definitions.str()
for node in deps_resolved.nodes {
name := g.js_name(node.name)
name := g.js_name(node.name).replace('.', '_')
if g.enable_doc {
out += '/** @namespace $name */\n'
}
@ -127,7 +124,7 @@ pub fn gen(files []ast.File, table &table.Table, pref &pref.Preferences) string
out += '\n})('
for i, key in imports.keys() {
if i > 0 { out += ', ' }
out += key
out += key.replace('.', '_')
}
out += ');\n\n'
}
@ -225,7 +222,7 @@ pub fn (mut g JsGen) typ(t table.Type) string {
}
.array_fixed {
info := sym.info as table.ArrayFixed
styp = g.array_fixed_typ(info.elem_type) or { g.typ(info.elem_type) + '[]' }
styp = g.typ(info.elem_type) + '[]'
}
// 'map[string]int' => 'Map<string, number>'
.map {
@ -266,16 +263,10 @@ pub fn (mut g JsGen) typ(t table.Type) string {
// 'anon_fn_7_7_1' => '(a number, b number) => void'
.function {
info := sym.info as table.FnType
mut res := '('
for i, arg in info.func.args {
res += '$arg.name: ${g.typ(arg.typ)}'
if i < info.func.args.len - 1 { res += ', ' }
}
styp = res + ') => ' + g.typ(info.func.return_type)
styp = g.fn_typ(info.func.args, info.func.return_type)
}
.interface_ {
// TODO: Implement interfaces
styp = 'interface'
styp = g.js_name(sym.name)
}
}
/* else {
@ -286,6 +277,16 @@ pub fn (mut g JsGen) typ(t table.Type) string {
return styp
}
fn (mut g JsGen) fn_typ(args []table.Arg, return_type table.Type) string {
mut res := '('
for i, arg in args {
res += '$arg.name: ${g.typ(arg.typ)}'
if i < args.len - 1 { res += ', ' }
}
return res + ') => ' + g.typ(return_type)
}
fn (mut g JsGen) struct_typ(s string) string {
ns := get_ns(s)
mut name := if ns == g.namespace { s.split('.').last() } else { g.get_alias(s) }
@ -329,23 +330,6 @@ fn (mut g JsGen) to_js_typ_val(t table.Type) string {
return styp
}
fn (mut g JsGen) array_fixed_typ(t table.Type) ?string {
sym := g.table.get_type_symbol(t)
match sym.kind {
.i8 { return 'Int8Array' }
.i16 { return 'Int16Array' }
.int { return 'Int32Array' }
.i64 { return 'BigInt64Array' }
.byte { return 'Uint8Array' }
.u16 { return 'Uint16Array' }
.u32 { return 'Uint32Array' }
.u64 { return 'BigUint64Array' }
.f32 { return 'Float32Array' }
.f64 { return 'Float64Array' }
else { return none }
}
}
pub fn (mut g JsGen) gen_indent() {
if g.indents[g.namespace] > 0 && g.empty_line {
g.out.write(tabs[g.indents[g.namespace]])
@ -456,7 +440,6 @@ fn (mut g JsGen) stmt(node ast.Stmt) {
ast.FnDecl {
g.fn_decl = it
g.gen_fn_decl(it)
g.writeln('')
}
ast.ForCStmt {
g.gen_for_c_stmt(it)
@ -490,7 +473,7 @@ fn (mut g JsGen) stmt(node ast.Stmt) {
g.gen_import_stmt(it)
}
ast.InterfaceDecl {
// TODO skip: interfaces not implemented yet
g.gen_interface_decl(it)
}
ast.Module {
// skip: namespacing implemented externally
@ -541,8 +524,9 @@ fn (mut g JsGen) expr(node ast.Expr) {
g.gen_call_expr(it)
}
ast.CastExpr {
// skip: JS has no types, so no need to cast
// TODO: Check if jsdoc is needed for TS support
// JS has no types, so no need to cast
// Just write the expression inside
g.expr(it.expr)
}
ast.CharLiteral {
g.write("'$it.val'")
@ -589,14 +573,21 @@ fn (mut g JsGen) expr(node ast.Expr) {
// TODO
}
ast.ParExpr {
// TODO
g.write('(')
g.expr(it.expr)
g.write(')')
}
ast.PostfixExpr {
g.expr(it.expr)
g.write(it.op.str())
}
ast.PrefixExpr {
// TODO
if it.op in [.amp, .mul] {
// C pointers/references: ignore them
} else {
g.write(it.op.str())
}
g.expr(it.right)
}
ast.RangeExpr {
// Only used in IndexExpr, requires index type info
@ -649,8 +640,8 @@ fn (mut g JsGen) gen_assert_stmt(a ast.AssertStmt) {
g.write('if( ')
g.expr(a.expr)
g.write(' ) {')
s_assertion := a.expr.str().replace('"', "\'")
mut mod_path := g.file.path
s_assertion := a.expr.str().replace('"', "'")
mut mod_path := g.file.path.replace('\\', '\\\\')
if g.is_test {
g.writeln(' g_test_oks++;')
g.writeln(' cb_assertion_ok("${mod_path}", ${a.pos.line_nr+1}, "assert ${s_assertion}", "${g.fn_decl.name}()" );')
@ -662,8 +653,10 @@ fn (mut g JsGen) gen_assert_stmt(a ast.AssertStmt) {
return
}
g.writeln('} else {')
g.writeln(' eprintln("${mod_path}:${a.pos.line_nr+1}: FAIL: fn ${g.fn_decl.name}(): assert $s_assertion");')
g.writeln(' exit(1);')
g.inc_indent()
g.writeln('builtin.eprintln("${mod_path}:${a.pos.line_nr+1}: FAIL: fn ${g.fn_decl.name}(): assert $s_assertion");')
g.writeln('builtin.exit(1);')
g.dec_indent()
g.writeln('}')
}
@ -795,18 +788,16 @@ fn (mut g JsGen) gen_enum_decl(it ast.EnumDecl) {
fn (mut g JsGen) gen_expr_stmt(it ast.ExprStmt) {
g.expr(it.expr)
expr := it.expr
if expr is ast.IfExpr { } // no ; after an if expression
else if !g.inside_ternary && !it.is_expr { g.writeln(';') }
if !it.is_expr && it.expr !is ast.IfExpr && !g.inside_ternary { g.writeln(';') }
}
fn (mut g JsGen) gen_fn_decl(it ast.FnDecl) {
if it.is_method {
if it.no_body || it.is_method {
// Struct methods are handled by class generation code.
return
}
if it.no_body {
return
if g.namespace == 'builtin' {
g.builtin_fns << it.name
}
g.gen_method_decl(it)
}
@ -877,7 +868,7 @@ fn (mut g JsGen) gen_method_decl(it ast.FnDecl) {
g.write(')();')
}
if !it.is_anon && !it.is_method {
g.writeln('')
g.writeln('\n')
}
g.fn_decl = voidptr(0)
@ -1012,6 +1003,17 @@ fn (mut g JsGen) gen_import_stmt(it ast.Import) {
g.namespace_imports[g.namespace] = imports
}
fn (mut g JsGen) gen_interface_decl(it ast.InterfaceDecl) {
// JS is dynamically typed, so we don't need any codegen at all
// We just need the JSDoc so TypeScript type checking works
g.doc.gen_interface(it)
// This is a hack to make the interface's type accessible outside its namespace
// TODO: interfaces are always `pub`?
name := g.js_name(it.name)
g.push_pub_var('/** @type $name */\n\t\t$name: undefined')
}
fn (mut g JsGen) gen_return_stmt(it ast.Return) {
if it.exprs.len == 0 {
// Returns nothing
@ -1022,16 +1024,8 @@ fn (mut g JsGen) gen_return_stmt(it ast.Return) {
g.write('return ')
if it.exprs.len == 1 {
g.expr(it.exprs[0])
} else {
// Multi return
g.write('[')
for i, expr in it.exprs {
g.expr(expr)
if i < it.exprs.len - 1 {
g.write(', ')
}
}
g.write(']')
} else { // Multi return
g.gen_array_init_values(it.exprs)
}
g.writeln(';')
}
@ -1073,9 +1067,7 @@ fn (mut g JsGen) gen_struct_decl(node ast.StructDecl) {
}
for i, cfn in fns {
// TODO: Move cast to the entire array whenever it's possible
it := cfn as ast.FnDecl
g.gen_method_decl(it)
g.gen_method_decl(cfn)
if i < fns.len - 1 { g.writeln(',') } else { g.writeln('') }
}
g.dec_indent()
@ -1086,17 +1078,50 @@ fn (mut g JsGen) gen_struct_decl(node ast.StructDecl) {
}
fn (mut g JsGen) gen_array_init_expr(it ast.ArrayInit) {
type_sym := g.table.get_type_symbol(it.typ)
if type_sym.kind != .array_fixed {
g.write('[')
for i, expr in it.exprs {
g.expr(expr)
if i < it.exprs.len - 1 {
g.write(', ')
}
// NB: Fixed arrays and regular arrays are handled the same, since fixed arrays:
// 1) Are only available for number types
// 2) Give the code unnecessary complexity
// 3) Have several limitations like missing most `Array.prototype` methods
// 4) Modern engines can optimize regular arrays into typed arrays anyways,
// offering similar performance
if it.has_len {
t1 := g.new_tmp_var()
t2 := g.new_tmp_var()
g.writeln('(function() {')
g.inc_indent()
g.writeln('const $t1 = [];')
g.write('for (let $t2 = 0; $t2 < ')
g.expr(it.len_expr)
g.writeln('; $t2++) {')
g.inc_indent()
g.write('${t1}.push(')
if it.has_default {
g.expr(it.default_expr)
} else {
// Fill the array with the default values for its type
t := g.to_js_typ_val(it.elem_type)
g.write(t)
}
g.write(']')
} else {}
g.writeln(');')
g.dec_indent()
g.writeln('};')
g.writeln('return $t1;')
g.dec_indent()
g.write('})()')
} else {
g.gen_array_init_values(it.exprs)
}
}
fn (mut g JsGen) gen_array_init_values(exprs []ast.Expr) {
g.write('[')
for i, expr in exprs {
g.expr(expr)
if i < exprs.len - 1 {
g.write(', ')
}
}
g.write(']')
}
fn (mut g JsGen) gen_call_expr(it ast.CallExpr) {
@ -1143,7 +1168,7 @@ fn (mut g JsGen) gen_call_expr(it ast.CallExpr) {
return
}
} else {
if name in builtin_globals {
if name in g.builtin_fns {
g.write('builtin.')
}
}
@ -1281,19 +1306,19 @@ fn (mut g JsGen) gen_infix_expr(it ast.InfixExpr) {
if r_sym.kind == .array { g.write('...') } // arr << [1, 2]
g.expr(it.right)
g.write(')')
} else if r_sym.kind in [.array, .map] && it.op in [.key_in, .not_in] {
} else if r_sym.kind in [.array, .map, .string] && it.op in [.key_in, .not_in] {
if it.op == .not_in { g.write('!(') }
g.expr(it.right)
g.write(if r_sym.kind == .map { '.has(' } else { '.includes(' })
g.expr(it.left)
g.write(')')
if it.op == .not_in { g.write(')') }
} else if it.op == .key_is { // foo is Foo
g.write('/*')
} else if it.op in [.key_is, .not_is] { // foo is Foo
if it.op == .not_is { g.write('!(') }
g.expr(it.left)
g.write(' is $r_sym.name')
g.write('*/0')
// TODO
g.write(' instanceof ')
g.write(g.typ(it.right_type))
if it.op == .not_is { g.write(')') }
} else {
g.expr(it.left)

View File

@ -59,7 +59,7 @@ fn (mut d JsDoc) gen_fn(it ast.FnDecl) {
d.writeln(' * @deprecated')
}
for i, arg in it.args {
if it.is_method && i == 0 {
if (it.is_method || it.receiver.typ == 0) && i == 0 {
continue
}
arg_type_name := d.gen.typ(arg.typ)
@ -74,3 +74,17 @@ fn (mut d JsDoc) gen_fn(it ast.FnDecl) {
d.writeln(' * @returns {$type_name}')
d.writeln('*/')
}
fn (mut d JsDoc) gen_interface(it ast.InterfaceDecl) {
name := d.gen.js_name(it.name)
d.writeln('/**')
d.writeln(' * @interface $name')
d.writeln(' * @typedef $name')
for method in it.methods {
// Skip receiver
typ := d.gen.fn_typ(method.args[1..], method.return_type)
method_name := d.gen.js_name(method.name)
d.writeln(' * @property {$typ} $method_name')
}
d.writeln(' */\n')
}

View File

@ -0,0 +1,36 @@
import os
const (
test_dir = 'vlib/v/gen/js/tests/'
output_dir = '_js_tests/'
v_options = '-b js -w'
)
fn test_example_compilation() {
vexe := os.getenv('VEXE')
os.chdir(os.dir(vexe))
os.mkdir_all(output_dir)
files := find_test_files()
for file in files {
path := os.join_path(test_dir, file)
println('Testing $file')
v_code := os.system('$vexe $v_options -o ${output_dir}${file}.js $path')
if v_code != 0 { assert false } // Compilation failed
js_code := os.system('node ${output_dir}${file}.js')
if js_code != 0 { assert false } // Running failed
}
os.rmdir_all(output_dir)
}
fn find_test_files() []string {
files := os.ls(test_dir) or { panic(err) }
// The life example never exits, so tests would hang with it, skip
mut tests := files.filter(it.ends_with('.v')).filter(it != 'life.v')
tests.sort()
return tests
}

1
vlib/v/gen/js/tests/.gitignore vendored 100644
View File

@ -0,0 +1 @@
*.js

View File

@ -1,174 +0,0 @@
// V_COMMIT_HASH 8a24d7d
// V_CURRENT_COMMIT_HASH 6f886dc
// Generated by the V compiler
"use strict";
/** @namespace builtin */
const builtin = (function () {
/**
* @function
* @param {any} s
* @returns {void}
*/
function println(s) {
console.log(s);
}
/**
* @function
* @param {any} s
* @returns {void}
*/
function print(s) {
process.stdout.write(s);
}
/* module exports */
return {
println,
print
};
})();
/** @namespace main */
const main = (function () {
/**
* @function
* @param {string} s
* @returns {string}
*/
function map_cb(s) {
return `CB: ${s}`;
}
/**
* @function
* @param {number} n
* @returns {boolean}
*/
function filter_cb(n) {
return n < 4;
}
/**
* @function
* @param {...number} args
* @returns {void}
*/
function variadic(...args) {
builtin.println(args);
builtin.println(args[0]);
builtin.println(args[1]);
}
/**
* @function
* @returns {void}
*/
function vararg_test() {
variadic(1, 2, 3);
}
/* program entry point */
(function() {
vararg_test();
/** @type {string[]} */
const arr1 = ["Hello", "JS", "Backend"];
/** @type {number[]} */
let arr2 = [1, 2, 3, 4, 5];
/** @type {string[]} */
const slice1 = arr1.slice(1, 3);
/** @type {number[]} */
const slice2 = arr2.slice(0, 3);
/** @type {number[]} */
const slice3 = arr2.slice(3, arr2.length);
/** @type {string} */
const idx1 = slice1[1];
arr2[0] = 1;
arr2[0 + 1] = 2;
builtin.println(arr2);
arr2.push(6);
arr2.push(...[7, 8, 9]);
builtin.println(arr2);
builtin.println("\n\n");
/** @type {string} */
let slice4 = idx1.slice(0, 4);
builtin.print("Back\t=> ");
builtin.println(slice4);
/** @type {number} */
const idx2 = slice4.charCodeAt(0);
builtin.print("66\t=> ");
builtin.println(idx2);
/** @type {Map<string, string>} */
let m = new Map();
/** @type {string} */
const key = "key";
m.set(key, "value");
/** @type {string} */
const val = m.get("key");
builtin.print("value\t=> ");
builtin.println(val);
builtin.print("true\t=> ");
builtin.println(arr1.includes("JS"));
builtin.print("false\t=> ");
builtin.println(!(arr2.includes(3)));
builtin.print("true\t=> ");
builtin.println(m.has("key"));
builtin.print("true\t=> ");
builtin.println(!(m.has("badkey")));
for (let _tmp1 = 0; _tmp1 < arr1.length; ++_tmp1) {
}
builtin.println("0 to 8\t=>");
for (let i = 0; i < arr2.length; ++i) {
builtin.println(i);
}
builtin.println("\n\n4 to 5\t=> ");
for (let _tmp2 = 0; _tmp2 < slice3.length; ++_tmp2) {
const v = slice3[_tmp2];
builtin.println(v);
}
/** @type {string[]} */
const a = arr1.map(it => `VAL: ${it}`);
/** @type {string[]} */
const b = arr1.map(map_cb);
/** @type {string[]} */
const c = arr1.map(it => map_cb(it));
/** @type {string[]} */
const d = arr1.map(function (a) {
const _tmp3 = a;
return `ANON: ${a}`;
});
/** @type {number[]} */
const e = arr1.map(it => 456);
builtin.println(a);
builtin.println(b);
builtin.println(c);
builtin.println(d);
builtin.println(e);
builtin.println("\n\n");
/** @type {number[]} */
const aa = arr2.filter(it => it < 4);
/** @type {number[]} */
const bb = arr2.filter(filter_cb);
/** @type {number[]} */
const cc = arr2.filter(it => filter_cb(it));
/** @type {number[]} */
const dd = arr2.filter(function (a) {
const _tmp4 = a;
return a < 4;
});
builtin.println(aa);
builtin.println(bb);
builtin.println(cc);
builtin.println(dd);
})();
/* module exports */
return {};
})();

View File

@ -66,6 +66,8 @@ print('true\t=> ')
println('key' in m)
print('true\t=> ')
println('badkey' !in m)
print('true\t=> ')
println('o' in 'hello')
// for in
for _ in arr1 {}
@ -103,3 +105,16 @@ println(aa)
println(bb)
println(cc)
println(dd)
// fixed arrays: implemented as normal arrays
f1 := [1, 2, 3, 4, 5]!!
mut f2 := [8]f32
f2[0] = f32(1.23)
f3 := ['foo', 'bar']!!
f4 := [u64(0xffffffffffffffff), 0xdeadface]!!
println('
$f1
$f2
$f3
$f4')

View File

@ -1,139 +0,0 @@
// V_COMMIT_HASH 2943bdc
// V_CURRENT_COMMIT_HASH ad5deef
// Generated by the V compiler
"use strict";
/** @namespace builtin */
const builtin = (function () {
/**
* @function
* @param {any} s
* @returns {void}
*/
function println(s) {
console.log(s);
}
/**
* @function
* @param {any} s
* @returns {void}
*/
function print(s) {
process.stdout.write(s);
}
/* module exports */
return {
println,
print,
};
})();
/** @namespace hello */
const hello = (function () {
/**
* @function
* @returns {void}
*/
function raw_js_log() {
console.log('hello')
}
/** @constant {string} */
const hello = "Hello";
/**
* @constructor
* @param {{foo?: string}} init
*/
function Aaa({ foo = "" }) {
this.foo = foo
};
Aaa.prototype = {
/** @type {string} */
foo: "",
/**
* @function
* @param {string} s
* @returns {void}
*/
update(s) {
const a = this;
a.foo = s;
}
};
/**
* @constructor
* @param {{}} init
*/
function Bbb({ }) {
};
Bbb.prototype = {
};
/** @enum {number} */
const Ccc = {
a: 0,
b: 5,
c: 6,
};
/**
* @function
* @returns {string}
*/
function v_debugger() {
/** @type {Bbb} */
const v = new Bbb({});
return hello;
}
/**
* @function
* @returns {string}
*/
function excited() {
return v_debugger() + "!";
}
/* module exports */
return {
raw_js_log,
Aaa,
Ccc,
v_debugger,
excited,
};
})();
/** @namespace main */
const main = (function (hello) {
/** @enum {number} */
const Test = {
foo: 2,
bar: 5,
baz: 6,
};
/* program entry point */
(function() {
/** @type {number} */
let a = hello.Ccc.a;
a = hello.Ccc.b;
a = hello.Ccc.c;
builtin.println(a);
/** @type {number} */
let b = Test.foo;
b = Test.bar;
builtin.println(b);
})();
/* module exports */
return {};
})(hello);

View File

@ -1,5 +1,7 @@
module hello
import hello.hello1
pub const (
hello = 'Hello'
)
@ -27,5 +29,5 @@ pub fn debugger() string {
}
pub fn excited() string {
return debugger() + "!"
return "$hello1.nested() $debugger()!"
}

View File

@ -1,7 +1,5 @@
module hello1
// Unused for now: nested modules do not work yet
pub fn nested() string {
return 'Nested'
}

View File

@ -0,0 +1,29 @@
struct Dog {
name string
age int
}
struct Cat {
name string
age int
}
interface Animal {
say(s string)
greet() int
}
fn (d Dog) say(s string) { println('Dog $d.name: "$s"') }
fn (c Cat) say(s string) { println('Cat $c.name: "$s"') }
fn (d Dog) greet() int { d.say('Hello!') return d.age }
fn (c Cat) greet() int { c.say('Hello!') return c.age }
fn use(a Animal) {
if a is Dog { println('dog') }
else if a is Cat { println('cat') }
else { println('its a bug!') }
}
use(Dog{ 'Doggo', 5 })
use(Cat{ 'Nyancat', 6 })

View File

@ -1,260 +0,0 @@
// V_COMMIT_HASH 8a24d7d
// V_CURRENT_COMMIT_HASH 123d788
// Generated by the V compiler
"use strict";
/** @namespace builtin */
const builtin = (function () {
/**
* @function
* @param {any} s
* @returns {void}
*/
function println(s) {
console.log(s);
}
/**
* @function
* @param {any} s
* @returns {void}
*/
function print(s) {
process.stdout.write(s);
}
/* module exports */
return {
println,
print
};
})();
/** @namespace main */
const main = (function () {
/**
* @function
* @param {string} s1
* @param {string} s2
* @returns {void}
*/
function test_fn(s1, s2) {
builtin.print((s1 === s2 ? "true" : "false"));
builtin.print("\t=> ");
builtin.println(`"${s1}", "${s2}"`);
}
/**
* @function
* @returns {void}
*/
function simple_string_interpolation() {
/** @type {string} */
const a = "Hello";
/** @type {string} */
const b = "World";
/** @type {string} */
const res = `${a} ${b}`;
test_fn(res, "Hello World");
}
/**
* @function
* @returns {void}
*/
function mixed_string_interpolation() {
/** @type {number} */
const num = 7;
/** @type {string} */
const str = "abc";
/** @type {string} */
const s1 = `number=${num}`;
test_fn(s1, "number=7");
/** @type {string} */
const s2 = `string=${str}`;
test_fn(s2, "string=abc");
/** @type {string} */
const s3 = `a: ${num} | b: ${str}`;
test_fn(s3, "a: 7 | b: abc");
}
/**
* @function
* @returns {void}
*/
function formatted_string_interpolation() {
/** @type {string} */
const x = "abc";
/** @type {string} */
const axb = `a:${x}:b`;
test_fn(axb, "a:abc:b");
/** @type {string} */
const x_10 = `a:${x}:b`;
/** @type {string} */
const x10_ = `a:${x}:b`;
test_fn(x_10, "a: abc:b");
test_fn(x10_, "a:abc :b");
/** @type {number} */
const i = 23;
/** @type {string} */
const si_right = `${i}`;
/** @type {string} */
const si__left = `${i}`;
test_fn(si_right, " 23");
test_fn(si__left, "23 ");
}
/**
* @function
* @returns {void}
*/
function implicit_str() {
/** @type {number} */
const i = 42;
test_fn(`int ${i}`, "int 42");
test_fn(`${i}`, "42");
/** @type {boolean} */
const check = `${i}` === "42";
/** @type {string} */
const text = `${i}` + "42";
test_fn(text, "4242");
}
/**
* @function
* @returns {void}
*/
function string_interpolation_percent_escaping() {
/** @type {string} */
const test = "hello";
/** @type {string} */
const hello = "world";
/** @type {string} */
const x = `%.*s${hello}${test} |${hello}|`;
test_fn(x, "%.*sworldhello |world |");
}
/**
* @function
* @returns {void}
*/
function string_interpolation_string_prefix() {
/** @type {string} */
const r = "r";
/** @type {string} */
const rr = `${r}${r}`;
test_fn(rr, "rr");
/** @type {string} */
const c = "c";
/** @type {string} */
const cc = `${c}${c}`;
test_fn(cc, "cc");
/** @type {string} */
const js = "js";
/** @type {string} */
const jsjs = `${js}${js}`;
test_fn(jsjs, "jsjs");
}
/**
* @function
* @returns {void}
*/
function interpolation_string_prefix_expr() {
/** @type {number} */
const r = 1;
/** @type {number} */
const c = 2;
/** @type {number} */
const js = 1;
test_fn(`>${3 + r}<`, ">4<");
test_fn(`${r === js} ${js}`, "true 1");
test_fn(`>${js + c} ${js + r === c}<`, ">3 true<");
}
/**
* @function
* @returns {void}
*/
function utf8_string_interpolation() {
/** @type {string} */
const a = "à-côté";
/** @type {string} */
const st = "Sträßle";
/** @type {string} */
const m = "10€";
test_fn(`${a} ${st} ${m}`, "à-côté Sträßle 10€");
/** @type {string} */
const zz = `>${a}< >${st}< >${m}<-`;
/** @type {string} */
const zz_expected = "> à-côté< >Sträßle < > 10€<-";
test_fn(zz, zz_expected);
/** @type {string} */
const e = "€";
test_fn(`100.00 ${e}`, "100.00 €");
/** @type {string} */
const m2 = "Москва́";
/** @type {string} */
const d = "Antonín Dvořák";
test_fn(`:${m2}:${d}:`, ": Москва́:Antonín Dvořák :");
/** @type {string} */
const g = "Πελοπόννησος";
test_fn(`>${g}<`, ">Πελοπόννησος <");
}
/**
* @constructor
* @param {{v1?: number, v2?: number}} init
*/
function Sss({ v1 = 0, v2 = 0 }) {
this.v1 = v1
this.v2 = v2
};
Sss.prototype = {
/** @type {number} */
v1: 0,
/** @type {number} */
v2: 0,
/**
* @function
* @returns {string}
*/
str() {
const s = this;
return `[${s.v1}, ${s.v2}]`;
}
};
/**
* @function
* @returns {void}
*/
function string_interpolation_str_evaluation() {
/** @type {Sss} */
let x = new Sss({
v1: 17,
v2: 13.455893
});
test_fn(`${x.str()}`, "[17, 13.456]");
}
/* program entry point */
(function() {
simple_string_interpolation();
mixed_string_interpolation();
formatted_string_interpolation();
implicit_str();
string_interpolation_percent_escaping();
string_interpolation_string_prefix();
interpolation_string_prefix_expr();
utf8_string_interpolation();
string_interpolation_str_evaluation();
})();
/* module exports */
return {};
})();

View File

@ -1,313 +0,0 @@
// V_COMMIT_HASH 8a24d7d
// V_CURRENT_COMMIT_HASH 123d788
// Generated by the V compiler
"use strict";
/** @namespace builtin */
const builtin = (function () {
/**
* @function
* @param {any} s
* @returns {void}
*/
function println(s) {
console.log(s);
}
/**
* @function
* @param {any} s
* @returns {void}
*/
function print(s) {
process.stdout.write(s);
}
/* module exports */
return {
println,
print
};
})();
/** @namespace hello */
const hello = (function () {
/**
* @function
* @returns {void}
*/
function raw_js_log() {
console.log('hello')
}
/** @constant {string} */
const hello = "Hello";
/**
* @constructor
* @param {{foo?: string}} init
*/
function Aaa({ foo = "" }) {
this.foo = foo
};
Aaa.prototype = {
/** @type {string} */
foo: "",
/**
* @function
* @param {string} s
* @returns {void}
*/
update(s) {
const a = this;
a.foo = s;
}
};
/**
* @constructor
* @param {{}} init
*/
function Bbb({ }) {
};
Bbb.prototype = {
};
/** @enum {number} */
const Ccc = {
a: 0,
b: 5,
c: 6,
};
/**
* @function
* @returns {string}
*/
function v_debugger() {
/** @type {Bbb} */
const v = new Bbb({});
return hello;
}
/**
* @function
* @returns {string}
*/
function excited() {
return v_debugger() + "!";
}
/* module exports */
return {
raw_js_log,
Aaa,
Ccc,
v_debugger,
excited
};
})();
/** @namespace main */
const main = (function (hl) {
/** @constant {number} */
const i_am_a_const = 21214;
/** @constant {string} */
const v_super = "amazing keyword";
/**
* @constructor
* @param {{a?: hl["Aaa"]["prototype"]}} init
*/
function Foo({ a = new hl.Aaa({}) }) {
this.a = a
};
Foo.prototype = {
/** @type {hl["Aaa"]["prototype"]} */
a: new hl.Aaa({})
};
/**
* @constructor
* @param {{google?: number, amazon?: boolean, yahoo?: string}} init
*/
function Companies({ google = 0, amazon = false, yahoo = "" }) {
this.google = google
this.amazon = amazon
this.yahoo = yahoo
};
Companies.prototype = {
/** @type {number} */
google: 0,
/** @type {boolean} */
amazon: false,
/** @type {string} */
yahoo: "",
/**
* @function
* @returns {number}
*/
method() {
const it = this;
/** @type {Companies} */
const ss = new Companies({
google: 2,
amazon: true,
yahoo: "hello"
});
const [a, b] = hello(2, "google", "not google");
/** @type {string} */
const glue = (a > 2 ? "more_glue" : a > 5 ? "more glueee" : "less glue");
if (a !== 2) {
}
return 0;
}
};
/** @enum {number} */
const POSITION = {
go_back: 0,
dont_go_back: 1,
};
/**
* @function
* @param {string} v_extends
* @param {number} v_instanceof
* @returns {void}
*/
function v_class(v_extends, v_instanceof) {
/** @type {number} */
const v_delete = v_instanceof;
const _tmp1 = v_delete;
}
/* program entry point */
(async function() {
builtin.println("Hello from V.js!");
builtin.println(Math.atan2(1, 0));
/** @type {number} */
let a = 1;
a *= 2;
a += 3;
builtin.println(a);
/** @type {hl["Aaa"]["prototype"]} */
let b = new hl.Aaa({});
b.update("an update");
builtin.println(b);
/** @type {Foo} */
let c = new Foo({
a: new hl.Aaa({})
});
c.a.update("another update");
builtin.println(c);
const _tmp2 = "done";
{
const _tmp3 = "block";
}
const _tmp4 = POSITION.go_back;
const _tmp5 = hl.Ccc.a;
/** @type {string} */
const v_debugger = "JS keywords";
/** @type {string} */
const v_await = `${v_super}: ${v_debugger}`;
/** @type {string} */
let v_finally = "implemented";
builtin.println(`${v_await} ${v_finally}`);
/** @type {number} */
const dun = i_am_a_const * 20;
/** @type {string} */
const dunn = hl.hello;
for (let i = 0; i < 10; i++) {
}
for (let i = 0; i < "hello".length; ++i) {
const x = "hello"[i];
}
for (let x = 1; x < 10; ++x) {
}
/** @type {number[]} */
const arr = [1, 2, 3, 4, 5];
for (let _tmp6 = 0; _tmp6 < arr.length; ++_tmp6) {
const i = arr[_tmp6];
}
/** @type {Map<string, string>} */
const ma = new Map([
["str", "done"],
["ddo", "baba"]
]);
for (let [m, n] of ma) {
/** @type {string} */
const iss = m;
}
await new Promise(function(resolve){
async(0, "hello");
resolve();
});
/** @type {(number: number) => void} */
const fn_in_var = function (number) {
builtin.println(`number: ${number}`);
};
hl.v_debugger();
anon_consumer(hl.excited(), function (message) {
builtin.println(message);
});
hl.raw_js_log();
})();
/**
* @function
* @param {string} greeting
* @param {(message: string) => void} anon
* @returns {void}
*/
function anon_consumer(greeting, anon) {
anon(greeting);
}
/**
* @function
* @param {number} num
* @param {string} def
* @returns {void}
*/
function async(num, def) {
}
/* [inline] */
/* [deprecated] */
/**
* @function
* @deprecated
* @param {number} game_on
* @param {...string} dummy
* @returns {[number, number]}
*/
function hello(game_on, ...dummy) {
for (let _tmp7 = 0; _tmp7 < dummy.length; ++_tmp7) {
const dd = dummy[_tmp7];
/** @type {string} */
const l = dd;
}
(function defer() {
/** @type {string} */
const v_do = "not";
})();
return [game_on + 2, 221];
}
/* module exports */
return {};
})(hello);

View File

@ -1,7 +1,5 @@
import hello as hl
fn JS.alert(arg string)
fn JS.Math.atan2(f64, f64) f64
import hello.hello1 as hl1
const (
i_am_a_const = 21214
@ -64,6 +62,7 @@ fn main() {
dun := i_am_a_const * 20
dunn := hl.hello // External constant
_ = hl1.nested()
for i := 0; i < 10; i++ {}
@ -79,6 +78,7 @@ fn main() {
'ddo': "baba"
}
// panic('foo')
for m, n in ma {
iss := m
}

View File

@ -1,204 +0,0 @@
// V_COMMIT_HASH 2943bdc
// V_CURRENT_COMMIT_HASH ad5deef
// Generated by the V compiler
"use strict";
/** @namespace builtin */
const builtin = (function () {
/**
* @function
* @param {any} s
* @returns {void}
*/
function println(s) {
console.log(s);
}
/**
* @function
* @param {any} s
* @returns {void}
*/
function print(s) {
process.stdout.write(s);
}
/* module exports */
return {
println,
print,
};
})();
/** @namespace main */
const main = (function () {
/**
* @function
* @returns {void}
*/
function clear() {
console.clear();
}
/** @constant {number} */
const w = 30;
/** @constant {number} */
const h = 30;
/**
* @function
* @param {boolean[][]} game
* @param {number} x
* @param {number} y
* @returns {boolean}
*/
function get(game, x, y) {
if (y < 0 || x < 0) {
return false;
}
if (y >= h || x >= w) {
return false;
}
return game[y][x];
}
/**
* @function
* @param {boolean[][]} game
* @param {number} x
* @param {number} y
* @returns {number}
*/
function neighbours(game, x, y) {
/** @type {number} */
let count = 0;
if (get(game, x - 1, y - 1)) {
count++;
}
if (get(game, x, y - 1)) {
count++;
}
if (get(game, x + 1, y - 1)) {
count++;
}
if (get(game, x - 1, y)) {
count++;
}
if (get(game, x + 1, y)) {
count++;
}
if (get(game, x - 1, y + 1)) {
count++;
}
if (get(game, x, y + 1)) {
count++;
}
if (get(game, x + 1, y + 1)) {
count++;
}
return count;
}
/**
* @function
* @param {boolean[][]} game
* @returns {boolean[][]}
*/
function step(game) {
/** @type {boolean[][]} */
let new_game = [[]];
for (let y = 0; y < game.length; ++y) {
let row = game[y];
/** @type {boolean[]} */
let new_row = [];
new_game[y] = new_row;
for (let x = 0; x < row.length; ++x) {
let cell = row[x];
/** @type {number} */
const count = neighbours(game, x, y);
new_row[x] = cell && count === 2 || count === 3;
}
}
return new_game;
}
/**
* @function
* @param {boolean[]} row
* @returns {string}
*/
function row_str(row) {
/** @type {string} */
let str = "";
for (let _tmp1 = 0; _tmp1 < row.length; ++_tmp1) {
let cell = row[_tmp1];
if (cell) {
str += "◼ ";
} else {
str += "◻ ";
}
}
return str;
}
/**
* @function
* @param {boolean[][]} game
* @returns {void}
*/
function show(game) {
clear();
for (let _tmp2 = 0; _tmp2 < game.length; ++_tmp2) {
let row = game[_tmp2];
builtin.println(row_str(row));
}
}
/* program entry point */
(function() {
/** @type {boolean[][]} */
let game = [[]];
for (let y = 0; y < h; ++y) {
/** @type {boolean[]} */
let row = [];
for (let x = 0; x < w; ++x) {
row[x] = false;
}
game[y] = row;
}
game[11][15] = true;
game[11][16] = true;
game[12][16] = true;
game[10][21] = true;
game[12][20] = true;
game[12][21] = true;
game[12][22] = true;
setInterval(function () {
show(game);
game = step(game);
}, 500);
})();
/* module exports */
return {};
})();

View File

@ -1,5 +1,3 @@
fn JS.setInterval(f fn (), ms int)
fn JS.console.clear()
fn clear() { JS.console.clear() }
const (w = 30 h = 30)
@ -25,13 +23,13 @@ fn neighbours(game [][]bool, x int, y int) int {
}
fn step(game [][]bool) [][]bool {
mut new_game := [[]bool{}]
mut new_game := [][]bool{}
for y, row in game {
mut new_row := []bool{}
new_game[y] = new_row
for x, cell in row {
count := neighbours(game, x, y)
new_row[x] = cell && count == 2 || count == 3 // TODO: count in [2, 3]
new_row[x] = (cell && count in [2, 3]) || count == 3
}
}
return new_game
@ -53,15 +51,7 @@ fn show(game [][]bool) {
}
}
mut game := [[]bool{}]
for y in 0..h {
mut row := []bool{}
for x in 0..w {
row[x] = false
}
game[y] = row
}
mut game := [][]bool{ len: h, init: []bool{ len: w } }
game[11][15] = true
game[11][16] = true

View File

@ -1,45 +0,0 @@
// V_COMMIT_HASH 2943bdc
// V_CURRENT_COMMIT_HASH ad5deef
// Generated by the V compiler
"use strict";
/** @namespace builtin */
const builtin = (function () {
/**
* @function
* @param {any} s
* @returns {void}
*/
function println(s) {
console.log(s);
}
/**
* @function
* @param {any} s
* @returns {void}
*/
function print(s) {
process.stdout.write(s);
}
/* module exports */
return {
println,
print,
};
})();
/** @namespace main */
const main = (function () {
/* program entry point */
(function() {
builtin.println("hello world");
})();
/* module exports */
return {};
})();

View File

@ -1,122 +0,0 @@
// V_COMMIT_HASH 2943bdc
// V_CURRENT_COMMIT_HASH ad5deef
// Generated by the V compiler
"use strict";
/** @namespace builtin */
const builtin = (function () {
/**
* @function
* @param {any} s
* @returns {void}
*/
function println(s) {
console.log(s);
}
/**
* @function
* @param {any} s
* @returns {void}
*/
function print(s) {
process.stdout.write(s);
}
/* module exports */
return {
println,
print,
};
})();
/** @namespace main */
const main = (function () {
/**
* @constructor
* @param {{value?: number, test?: Map<string, number>, hello?: number[]}} init
*/
function Int({ value = 0, test = new Map(), hello = [] }) {
this.value = value
this.test = test
this.hello = hello
};
Int.prototype = {
/** @type {number} */
value: 0,
/** @type {Map<string, number>} */
test: new Map(),
/** @type {number[]} */
hello: [],
/**
* @function
* @param {number} value
* @returns {void}
*/
add(value) {
const i = this;
i.value += value;
},
/**
* @function
* @returns {number}
*/
get() {
const i = this;
return i.value;
}
};
/**
* @constructor
* @param {{foo?: number, bar?: string}} init
*/
function Config({ foo = 0, bar = "" }) {
this.foo = foo
this.bar = bar
};
Config.prototype = {
/** @type {number} */
foo: 0,
/** @type {string} */
bar: ""
};
/**
* @function
* @param {Config} c
* @returns {void}
*/
function use_config(c) {
}
/* program entry point */
(function() {
/** @type {Int} */
const a = new Int({
value: 10
});
a.add(5);
builtin.println(a);
/** @type {Int} */
const b = new Int({});
b.add(10);
builtin.println(b.get());
use_config(new Config({
foo: 2,
bar: "bar"
}));
use_config(new Config({
foo: 2,
bar: "bar"
}));
})();
/* module exports */
return {};
})();

View File

@ -23,11 +23,11 @@ struct Config {
fn use_config(c Config) {}
fn main() {
a := Int { value: 10 }
mut a := Int { value: 10 }
a.add(5)
println(a) // 15
b := Int{}
mut b := Int{}
b.add(10)
println(b.get()) // 10

View File

@ -355,7 +355,7 @@ fn (mut p Parser) fn_args() ([]table.Arg, bool) {
mut args := []table.Arg{}
mut is_variadic := false
// `int, int, string` (no names, just types)
types_only := p.tok.kind in [.amp, .ellipsis] || (p.peek_tok.kind == .comma && p.table.known_type(p.tok.lit)) ||
types_only := p.tok.kind in [.amp, .ellipsis, .key_fn] || (p.peek_tok.kind == .comma && p.table.known_type(p.tok.lit)) ||
p.peek_tok.kind == .rpar
// TODO copy pasta, merge 2 branches
if types_only {