diff --git a/vlib/v/builder/compile.v b/vlib/v/builder/compile.v index cda440efb9..29250aec71 100644 --- a/vlib/v/builder/compile.v +++ b/vlib/v/builder/compile.v @@ -40,7 +40,7 @@ pub fn compile(command string, pref &pref.Preferences) { mut sw := time.new_stopwatch({}) match pref.backend { .c { b.compile_c() } - .js { b.compile_js() } + .js_node, .js_freestanding, .js_browser { b.compile_js() } .native { b.compile_native() } } mut timers := util.get_timers() @@ -118,7 +118,7 @@ fn (mut b Builder) run_compiled_executable_and_exit() { } mut exefile := os.real_path(b.pref.out_name) mut cmd := '"$exefile"' - if b.pref.backend == .js { + if b.pref.backend.is_js() { exefile = os.real_path('${b.pref.out_name}.js') cmd = 'node "$exefile"' } @@ -200,7 +200,7 @@ pub fn (v Builder) get_builtin_files() []string { for location in v.pref.lookup_path { if os.exists(os.join_path(location, 'builtin')) { mut builtin_files := []string{} - if v.pref.backend == .js { + if v.pref.backend.is_js() { builtin_files << v.v_files_from_dir(os.join_path(location, 'builtin', 'js')) } else { diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 13495494a4..afb5942236 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -26,7 +26,7 @@ const ( valid_comp_if_platforms = ['amd64', 'i386', 'aarch64', 'arm64', 'arm32', 'rv64', 'rv32'] valid_comp_if_cpu_features = ['x64', 'x32', 'little_endian', 'big_endian'] valid_comp_if_other = ['js', 'debug', 'prod', 'test', 'glibc', 'prealloc', - 'no_bounds_checking', 'freestanding', 'threads'] + 'no_bounds_checking', 'freestanding', 'threads', 'js_browser', 'js_freestanding'] valid_comp_not_user_defined = all_valid_comptime_idents() array_builtin_methods = ['filter', 'clone', 'repeat', 'reverse', 'map', 'slice', 'sort', 'contains', 'index', 'wait', 'any', 'all', 'first', 'last', 'pop'] @@ -4802,7 +4802,7 @@ fn (mut c Checker) asm_stmt(mut stmt ast.AsmStmt) { c.warn('inline assembly goto is not supported, it will most likely not work', stmt.pos) } - if c.pref.backend == .js { + if c.pref.backend.is_js() { c.error('inline assembly is not supported in the js backend', stmt.pos) } if c.pref.backend == .c && c.pref.ccompiler_type == .msvc { @@ -4901,7 +4901,7 @@ fn (mut c Checker) hash_stmt(mut node ast.HashStmt) { if c.skip_flags { return } - if c.pref.backend == .js { + if c.pref.backend.is_js() { if !c.file.path.ends_with('.js.v') { c.error('hash statements are only allowed in backend specific files such "x.js.v"', node.pos) @@ -6771,7 +6771,7 @@ fn (mut c Checker) comp_if_branch(cond ast.Expr, pos token.Position) bool { return false } else if cname in checker.valid_comp_if_other { match cname { - 'js' { return c.pref.backend != .js } + 'js' { return !c.pref.backend.is_js() } 'debug' { return !c.pref.is_debug } 'prod' { return !c.pref.is_prod } 'test' { return !c.pref.is_test } diff --git a/vlib/v/gen/js/comptime.v b/vlib/v/gen/js/comptime.v new file mode 100644 index 0000000000..d4c55ee1f5 --- /dev/null +++ b/vlib/v/gen/js/comptime.v @@ -0,0 +1,311 @@ +module js + +import v.ast +import v.pref + +fn (mut g JsGen) comp_if(node ast.IfExpr) { + if !node.is_expr && !node.has_else && node.branches.len == 1 { + if node.branches[0].stmts.len == 0 { + // empty ifdef; result of target OS != conditional => skip + return + } + } + + for i, branch in node.branches { + if i == node.branches.len - 1 && node.has_else { + g.writeln('else') + } else { + if i == 0 { + g.write('if (') + } else { + g.write('else if (') + } + g.comp_if_cond(branch.cond, branch.pkg_exist) + g.writeln(')') + } + + if node.is_expr { + print('$branch.stmts') + len := branch.stmts.len + if len > 0 { + last := branch.stmts[len - 1] as ast.ExprStmt + if len > 1 { + tmp := g.new_tmp_var() + g.inc_indent() + g.writeln('let $tmp;') + g.writeln('{') + g.stmts(branch.stmts[0..len - 1]) + g.write('\t$tmp = ') + g.stmt(last) + g.writeln('}') + g.dec_indent() + g.writeln('$tmp;') + } else { + g.stmt(last) + } + } + } else { + g.writeln('{') + g.stmts(branch.stmts) + g.writeln('}') + } + } +} + +/* +// returning `false` means the statements inside the $if can be skipped +*/ +// returns the value of the bool comptime expression +fn (mut g JsGen) comp_if_cond(cond ast.Expr, pkg_exist bool) bool { + match cond { + ast.BoolLiteral { + g.expr(cond) + return true + } + ast.ParExpr { + g.write('(') + is_cond_true := g.comp_if_cond(cond.expr, pkg_exist) + g.write(')') + return is_cond_true + } + ast.PrefixExpr { + g.write(cond.op.str()) + return g.comp_if_cond(cond.right, pkg_exist) + } + ast.PostfixExpr { + ifdef := g.comp_if_to_ifdef((cond.expr as ast.Ident).name, true) or { + verror(err.msg) + return false + } + g.write('$ifdef') + return true + } + ast.InfixExpr { + match cond.op { + .and, .logical_or { + l := g.comp_if_cond(cond.left, pkg_exist) + g.write(' $cond.op ') + r := g.comp_if_cond(cond.right, pkg_exist) + return if cond.op == .and { l && r } else { l || r } + } + .key_is, .not_is { + left := cond.left + mut name := '' + mut exp_type := ast.Type(0) + got_type := (cond.right as ast.TypeNode).typ + // Handle `$if x is Interface {` + // mut matches_interface := 'false' + if left is ast.TypeNode && cond.right is ast.TypeNode + && g.table.get_type_symbol(got_type).kind == .interface_ { + // `$if Foo is Interface {` + interface_sym := g.table.get_type_symbol(got_type) + if interface_sym.info is ast.Interface { + // q := g.table.get_type_symbol(interface_sym.info.types[0]) + checked_type := g.unwrap_generic(left.typ) + // TODO PERF this check is run twice (also in the checker) + // store the result in a field + is_true := g.table.does_type_implement_interface(checked_type, + got_type) + // true // exp_type in interface_sym.info.types + if cond.op == .key_is { + if is_true { + g.write('1') + } else { + g.write('0') + } + return is_true + } else if cond.op == .not_is { + if is_true { + g.write('0') + } else { + g.write('1') + } + return !is_true + } + // matches_interface = '/*iface:$got_type $exp_type*/ true' + //} + } + } else if left is ast.SelectorExpr { + name = '${left.expr}.$left.field_name' + exp_type = g.comptime_var_type_map[name] + } else if left is ast.TypeNode { + name = left.str() + // this is only allowed for generics currently, otherwise blocked by checker + exp_type = g.unwrap_generic(left.typ) + } + + if cond.op == .key_is { + g.write('$exp_type == $got_type') + return exp_type == got_type + } else { + g.write('$exp_type != $got_type') + return exp_type != got_type + } + } + .eq, .ne { + // TODO Implement `$if method.args.len == 1` + g.write('1') + return true + } + else { + return true + } + } + } + ast.Ident { + ifdef := g.comp_if_to_ifdef(cond.name, false) or { 'true' } // handled in checker + g.write('$ifdef') + return true + } + ast.ComptimeCall { + g.write('$pkg_exist') + return true + } + else { + // should be unreachable, but just in case + g.write('1') + return true + } + } +} + +fn (mut g JsGen) comp_if_to_ifdef(name string, is_comptime_optional bool) ?string { + match name { + // platforms/os-es: + 'windows' { + return '(\$process.platform == "windows")' + } + 'ios' { + return '(\$process.platform == "darwin")' + } + 'macos' { + return '(\$process.platform == "darwin")' + } + 'mach' { + return '(\$process.platform == "darwin")' + } + 'darwin' { + return '(\$process.platform == "darwin")' + } + 'linux' { + return '(\$process.platform == "linux")' + } + 'freebsd' { + return '(\$process.platform == "freebsd")' + } + 'openbsd' { + return '(\$process.platform == "openbsd")' + } + 'bsd' { + return '(\$process.platform == "freebsd" || (\$process.platform == "openbsd"))' + } + 'android' { + return '(\$process.platform == "android")' + } + 'solaris' { + return '(\$process.platform == "sunos")' + } + 'js_node' { + if g.pref.backend == .js_node { + return 'true' + } else { + return 'false' + } + } + 'js_freestanding' { + if g.pref.backend == .js_freestanding { + return 'true' + } else { + return 'false' + } + } + 'js_browser' { + if g.pref.backend == .js_browser { + return 'true' + } else { + return 'false' + } + } + // + 'js' { + return 'true' + } + // compilers: + 'gcc' { + return 'false' + } + 'tinyc' { + return 'false' + } + 'clang' { + return 'false' + } + 'mingw' { + return 'false' + } + 'msvc' { + return 'false' + } + 'cplusplus' { + return 'false' + } + // other: + 'threads' { + return 'false' + } + 'gcboehm' { + return 'false' + } + // todo(playX): these should return true or false depending on CLI options + 'debug' { + return 'false' + } + 'prod' { + return 'false' + } + 'test' { + return 'false' + } + 'glibc' { + return 'false' + } + 'prealloc' { + return 'false' + } + 'no_bounds_checking' { + return 'CUSTOM_DEFINE_no_bounds_checking' + } + 'freestanding' { + return '_VFREESTANDING' + } + // architectures: + 'amd64' { + return '(\$process.arch == "x64")' + } + 'aarch64', 'arm64' { + return '(\$process.arch == "arm64)' + } + // bitness: + 'x64' { + return '(\$process.arch == "x64")' + } + 'x32' { + return '(\$process.arch == "x32")' + } + // endianness: + 'little_endian' { + return '(\$os.endianess == "LE")' + } + 'big_endian' { + return '(\$os.endianess == "BE")' + } + else { + if is_comptime_optional + || (g.pref.compile_defines_all.len > 0 && name in g.pref.compile_defines_all) { + return 'CUSTOM_DEFINE_$name' + } + return error('bad os ifdef name "$name"') // should never happen, caught in the checker + } + } + return none +} diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index 503945634a..a81313f52e 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -48,34 +48,36 @@ mut: [heap] struct JsGen { - table &ast.Table - pref &pref.Preferences + pref &pref.Preferences mut: - definitions strings.Builder - ns &Namespace - namespaces map[string]&Namespace - doc &JsDoc - enable_doc bool - file &ast.File - tmp_count int - inside_ternary bool - inside_loop bool - inside_map_set bool // map.set(key, value) - inside_builtin bool - generated_builtin bool - inside_def_typ_decl bool - is_test bool - stmt_start_pos int - 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.FnDecl - builtin_fns []string // Functions defined in `builtin` - empty_line bool - cast_stack []ast.Type - call_stack []ast.CallExpr - is_vlines_enabled bool // is it safe to generate #line directives when -g is passed - sourcemap sourcemap.SourceMap // maps lines in generated javascrip file to original source files and line + table &ast.Table + definitions strings.Builder + ns &Namespace + namespaces map[string]&Namespace + doc &JsDoc + enable_doc bool + file &ast.File + tmp_count int + inside_ternary bool + inside_loop bool + inside_map_set bool // map.set(key, value) + inside_builtin bool + generated_builtin bool + inside_def_typ_decl bool + is_test bool + stmt_start_pos int + 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.FnDecl + builtin_fns []string // Functions defined in `builtin` + empty_line bool + cast_stack []ast.Type + call_stack []ast.CallExpr + is_vlines_enabled bool // is it safe to generate #line directives when -g is passed + sourcemap sourcemap.SourceMap // maps lines in generated javascrip file to original source files and line + comptime_var_type_map map[string]ast.Type + defer_ifdef string } pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string { @@ -303,6 +305,25 @@ pub fn (mut g JsGen) init() { g.definitions.writeln('"use strict";') g.definitions.writeln('') g.definitions.writeln('var \$global = (new Function("return this"))();') + + if g.pref.backend != .js_node { + g.definitions.writeln('const \$process = {') + g.definitions.writeln(' arch: "js",') + if g.pref.backend == .js_freestanding { + g.definitions.writeln(' platform: "freestanding"') + } else { + g.definitions.writeln(' platform: "browser"') + } + g.definitions.writeln('}') + + g.definitions.writeln('const \$os = {') + g.definitions.writeln(' endianess: "LE",') + + g.definitions.writeln('}') + } else { + g.definitions.writeln('const \$os = require("os");') + g.definitions.writeln('const \$process = process;') + } } pub fn (g JsGen) hashes() string { @@ -1443,6 +1464,10 @@ fn (mut g JsGen) gen_lock_expr(node ast.LockExpr) { } fn (mut g JsGen) gen_if_expr(node ast.IfExpr) { + if node.is_comptime { + g.comp_if(node) + return + } type_sym := g.table.get_type_symbol(node.typ) // one line ?: if node.is_expr && node.branches.len >= 2 && node.has_else && type_sym.kind != .void { @@ -1936,3 +1961,14 @@ fn (mut g JsGen) gen_float_literal_expr(it ast.FloatLiteral) { g.write('${g.typ(typ)}($it.val)') } + +fn (mut g JsGen) unwrap_generic(typ ast.Type) ast.Type { + if typ.has_flag(.generic) { + if t_typ := g.table.resolve_generic_to_concrete(typ, g.table.cur_fn.generic_names, + g.table.cur_concrete_types) + { + return t_typ + } + } + return typ +} diff --git a/vlib/v/gen/js/program_test.v b/vlib/v/gen/js/program_test.v index e9439e21ea..8ba06ed125 100644 --- a/vlib/v/gen/js/program_test.v +++ b/vlib/v/gen/js/program_test.v @@ -60,7 +60,7 @@ fn check_path(dir string, tests []string) ?int { os.write_file(program_out, '') ? } print(program + ' ') - res := os.execute('$vexe -b js run $program') + res := os.execute('$vexe -b js_node run $program') if res.exit_code < 0 { panic(res.output) } diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index 0d67a1471d..3ecb0ca664 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -633,7 +633,7 @@ fn (mut p Parser) anon_fn() ast.AnonFn { old_inside_defer := p.inside_defer p.inside_defer = false p.open_scope() - if p.pref.backend != .js { + if !p.pref.backend.is_js() { p.scope.detached_from_parent = true } // TODO generics diff --git a/vlib/v/pref/os.v b/vlib/v/pref/os.v index bc1e560a60..578b00d00e 100644 --- a/vlib/v/pref/os.v +++ b/vlib/v/pref/os.v @@ -13,7 +13,9 @@ pub enum OS { openbsd netbsd dragonfly - js // TODO + js_node + js_browser + js_freestanding android solaris serenity @@ -34,7 +36,9 @@ pub fn os_from_string(os_str string) ?OS { 'openbsd' { return .openbsd } 'netbsd' { return .netbsd } 'dragonfly' { return .dragonfly } - 'js' { return .js } + 'js', 'js_node' { return .js_node } + 'js_freestanding' { return .js_freestanding } + 'js_browser' { return .js_browser } 'solaris' { return .solaris } 'serenity' { return .serenity } 'vinix' { return .vinix } @@ -58,7 +62,9 @@ pub fn (o OS) str() string { .openbsd { return 'OpenBSD' } .netbsd { return 'NetBSD' } .dragonfly { return 'Dragonfly' } - .js { return 'JavaScript' } + .js_node { return 'NodeJS' } + .js_freestanding { return 'JavaScript' } + .js_browser { return 'JavaScript(Browser)' } .android { return 'Android' } .solaris { return 'Solaris' } .serenity { return 'SerenityOS' } diff --git a/vlib/v/pref/pref.v b/vlib/v/pref/pref.v index a86de7d7d3..8c8a425818 100644 --- a/vlib/v/pref/pref.v +++ b/vlib/v/pref/pref.v @@ -45,10 +45,23 @@ pub enum ColorOutput { pub enum Backend { c // The (default) C backend - js // The JavaScript backend + js_node // The JavaScript NodeJS backend + js_browser // The JavaScript browser backend + js_freestanding // The JavaScript freestanding backend native // The Native backend } +pub fn (b Backend) is_js() bool { + match b { + .js_node, .js_browser, .js_freestanding { + return true + } + else { + return false + } + } +} + pub enum CompilerType { gcc tinyc @@ -513,7 +526,7 @@ pub fn parse_args(known_external_commands []string, args []string) (&Preferences '-o', '-output' { res.out_name = cmdline.option(current_args, arg, '') if res.out_name.ends_with('.js') { - res.backend = .js + res.backend = .js_node } if !os.is_abs_path(res.out_name) { res.out_name = os.join_path(os.getwd(), res.out_name) @@ -524,6 +537,9 @@ pub fn parse_args(known_external_commands []string, args []string) (&Preferences sbackend := cmdline.option(current_args, arg, 'c') res.build_options << '$arg $sbackend' b := backend_from_string(sbackend) or { continue } + if b.is_js() { + res.output_cross_c = true + } res.backend = b i++ } @@ -723,7 +739,10 @@ fn is_source_file(path string) bool { pub fn backend_from_string(s string) ?Backend { match s { 'c' { return .c } - 'js' { return .js } + 'js' { return .js_node } + 'js_node' { return .js_node } + 'js_browser' { return .js_browser } + 'js_freestanding' { return .js_freestanding } 'native' { return .native } else { return error('Unknown backend type $s') } } diff --git a/vlib/v/pref/should_compile.v b/vlib/v/pref/should_compile.v index 37a9ccdba0..5f69fa4fe6 100644 --- a/vlib/v/pref/should_compile.v +++ b/vlib/v/pref/should_compile.v @@ -18,13 +18,13 @@ pub fn (prefs &Preferences) should_compile_filtered_files(dir string, files_ []s if prefs.backend == .c && !prefs.should_compile_c(file) { continue } - if prefs.backend == .js && !prefs.should_compile_js(file) { + if prefs.backend.is_js() && !prefs.should_compile_js(file) { continue } if prefs.backend == .native && !prefs.should_compile_native(file) { continue } - if prefs.backend != .js && !prefs.should_compile_asm(file) { + if !prefs.backend.is_js() && !prefs.should_compile_asm(file) { continue } if file.starts_with('.#') {