diff --git a/vlib/builtin/js/builtin.js.v b/vlib/builtin/js/builtin.js.v index 4290f4b989..c150f3c40c 100644 --- a/vlib/builtin/js/builtin.js.v +++ b/vlib/builtin/js/builtin.js.v @@ -36,3 +36,11 @@ pub fn eprint(s any) { panic('Cannot `eprint` in a browser, use `println` 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) + js_throw('exit($c)') +} diff --git a/vlib/builtin/js/builtin.v b/vlib/builtin/js/builtin.v index 5f97a29226..d1829def62 100644 --- a/vlib/builtin/js/builtin.v +++ b/vlib/builtin/js/builtin.v @@ -6,14 +6,6 @@ module builtin fn (a any) toString() -// 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) - js_throw('exit($c)') -} - pub fn unwrap(opt any) any { o := &Option(opt) if o.state != 0 { diff --git a/vlib/builtin/js/string.js.v b/vlib/builtin/js/string.js.v index ec95bd6bb6..8b2933de7c 100644 --- a/vlib/builtin/js/string.js.v +++ b/vlib/builtin/js/string.js.v @@ -567,16 +567,19 @@ pub fn (s string) replace_each(vals []string) string { mut idxs := []RepIndex{} mut idx := 0 + mut new_len := s.len s_ := s.clone() #function setCharAt(str,index,chr) { #if(index > str.length-1) return str; #return str.substring(0,index) + chr + str.substring(index+1); #} - for rep_i := 0; rep_i < vals.len; rep_i += 2 { + for rep_i := 0; rep_i < vals.len; rep_i = rep_i + 2 { rep := vals[rep_i] + mut with_ := vals[rep_i + 1] with_ = with_ + for { idx = s_.index_after(rep, idx) if idx == -1 { @@ -589,11 +592,16 @@ pub fn (s string) replace_each(vals []string) string { #s_.str = setCharAt(s_.str,idx + i, String.fromCharCode(127)) } - idxs << RepIndex{ - idx: idx - val_idx: rep_i + rep_idx := RepIndex{ + idx: 0 + val_idx: 0 } + // todo: primitives should always be copied + #rep_idx.idx = idx.val + #rep_idx.val_idx = new int(rep_i.val) + idxs << rep_idx idx += rep.len + new_len += with_.len - rep.len } } @@ -604,6 +612,9 @@ pub fn (s string) replace_each(vals []string) string { idxs.sort(a.idx < b.idx) mut b := '' + #for (let i = 0; i < new_len.val;i++) b.str += String.fromCharCode(127) + + new_len = new_len mut idx_pos := 0 mut cur_idx := idxs[idx_pos] mut b_i := 0 @@ -613,6 +624,7 @@ pub fn (s string) replace_each(vals []string) string { with_ := vals[cur_idx.val_idx + 1] for j in 0 .. with_.len { mut j_ := j + j_ = j_ #b.str = setCharAt(b.str,b_i, with_.str[j]) //#b.str[b_i] = with_.str[j] diff --git a/vlib/builtin/string.v b/vlib/builtin/string.v index 7caf9e5063..1a4815dc2b 100644 --- a/vlib/builtin/string.v +++ b/vlib/builtin/string.v @@ -357,9 +357,9 @@ pub fn (s string) replace_each(vals []string) string { // vals: ['rep1, 'with1', 'rep2', 'with2'] rep := vals[rep_i] with := vals[rep_i + 1] + for { idx = s_.index_after(rep, idx) - if idx == -1 { break } @@ -371,6 +371,7 @@ pub fn (s string) replace_each(vals []string) string { } // We need to remember both the position in the string, // and which rep/with pair it refers to. + idxs << RepIndex{ idx: idx val_idx: rep_i @@ -380,6 +381,7 @@ pub fn (s string) replace_each(vals []string) string { new_len += with.len - rep.len } } + // Dont change the string if there's nothing to replace if idxs.len == 0 { return s.clone() diff --git a/vlib/os/environment.js.v b/vlib/os/environment.js.v index 0e7961f56d..ac760a59c4 100644 --- a/vlib/os/environment.js.v +++ b/vlib/os/environment.js.v @@ -1,9 +1,9 @@ module os $if js_node { - #const $ENV = $process.env + #global.$ENV = $process.env } $else { - #const $ENV = {} + #global.$ENV = {} } // setenv sets the value of an environment variable with `name` to `value`. diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index 3aeb9fe099..d86064659d 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -81,6 +81,11 @@ mut: defer_ifdef string } +fn (mut g JsGen) write_tests_definitions() { + g.definitions.writeln('globalThis.g_test_oks = 0;') + g.definitions.writeln('globalThis.g_test_fails = 0;') +} + pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string { mut g := &JsGen{ definitions: strings.new_builder(100) @@ -105,6 +110,8 @@ pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string { mut sg := sourcemap.generate_empty_map() g.sourcemap = sg.add_map('', '', g.pref.sourcemap_src_included, 0, 0) } + mut tests_inited := false + // Get class methods for file in files { g.file = file @@ -133,15 +140,22 @@ pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string { g.writeln('Object.defineProperty(array.prototype,"length", { get: function() {return new builtin.int(this.arr.length);}, set: function(l) { this.arr.length = l.valueOf(); } }); ') g.generated_builtin = true } - + if g.is_test && !tests_inited { + g.write_tests_definitions() + tests_inited = true + } g.stmts(file.stmts) g.writeln('try { init() } catch (_) {}') // store the current namespace g.escape_namespace() } + if g.pref.is_test { + g.gen_js_main_for_tests() + } // resolve imports deps_resolved := graph.resolve() nodes := deps_resolved.nodes + mut out := g.hashes() + g.definitions.str() // equality check for js objects // TODO: Fix msvc bug that's preventing $embed_file('fast_deep_equal.js') @@ -262,6 +276,67 @@ fn (g JsGen) create_sourcemap() string { return out } +pub fn (mut g JsGen) gen_js_main_for_tests() { + g.enter_namespace('main') + g.writeln('(function() { ') + g.inc_indent() + all_tfuncs := g.get_all_test_function_names() + + g.writeln('') + g.writeln('globalThis.VTEST=1') + // g.writeln('let bt = start_testing($all_tfuncs.len, "$g.pref.path")') + + for tname in all_tfuncs { + tcname := g.js_name(tname) + + if g.pref.is_stats { + // g.writeln('bt.testing_step_start("$tcname")') + } + + g.writeln('try { ${tcname}(); } catch (_e) {} ') + if g.pref.is_stats { + // g.writeln('bt.testing_step_end();') + } + } + + g.writeln('') + if g.pref.is_stats { + // g.writeln('bt.end_testing();') + } + g.dec_indent() + g.writeln('})();') + g.escape_namespace() +} + +fn (g &JsGen) get_all_test_function_names() []string { + mut tfuncs := []string{} + mut tsuite_begin := '' + mut tsuite_end := '' + for _, f in g.table.fns { + if f.name.ends_with('.testsuite_begin') { + tsuite_begin = f.name + continue + } + if f.name.contains('.test_') { + tfuncs << f.name + continue + } + if f.name.ends_with('.testsuite_end') { + tsuite_end = f.name + continue + } + } + mut all_tfuncs := []string{} + if tsuite_begin.len > 0 { + all_tfuncs << tsuite_begin + } + all_tfuncs << tfuncs + if tsuite_end.len > 0 { + all_tfuncs << tsuite_end + } + return all_tfuncs +} + pub fn (mut g JsGen) enter_namespace(name string) { if g.namespaces[name] == 0 { // create a new namespace @@ -936,6 +1011,63 @@ fn (mut g JsGen) expr(node ast.Expr) { } } +fn (mut g JsGen) gen_assert_metainfo(node ast.AssertStmt) string { + mod_path := g.file.path + fn_name := g.fn_decl.name + line_nr := node.pos.line_nr + src := node.expr.str() + metaname := 'v_assert_meta_info_$g.new_tmp_var()' + g.writeln('let $metaname = {}') + g.writeln('${metaname}.fpath = new builtin.string("$mod_path");') + g.writeln('${metaname}.line_nr = new builtin.int("$line_nr")') + g.writeln('${metaname}.fn_name = new builtin.string("$fn_name")') + metasrc := src + g.writeln('${metaname}.src = "$metasrc"') + + match mut node.expr { + ast.InfixExpr { + expr_op_str := node.expr.op.str() + expr_left_str := node.expr.left.str() + expr_right_str := node.expr.right.str() + g.writeln('\t${metaname}.op = new builtin.string("$expr_op_str");') + g.writeln('\t${metaname}.llabel = new builtin.string("$expr_left_str");') + g.writeln('\t${metaname}.rlabel = new builtin.string("$expr_right_str");') + g.write('\t${metaname}.lvalue = new builtin.string("') + g.gen_assert_single_expr(node.expr.left, node.expr.left_type) + g.writeln('");') + g.write('\t${metaname}.rvalue = new builtin.string("') + g.gen_assert_single_expr(node.expr.right, node.expr.right_type) + g.writeln('");') + } + ast.CallExpr { + g.writeln('\t${metaname}.op = new builtin.string("call");') + } + else {} + } + return metaname +} + +fn (mut g JsGen) gen_assert_single_expr(expr ast.Expr, typ ast.Type) { + // eprintln('> gen_assert_single_expr typ: $typ | expr: $expr | typeof(expr): ${typeof(expr)}') + unknown_value := '*unknown value*' + match expr { + ast.CastExpr, ast.IfExpr, ast.IndexExpr, ast.MatchExpr { + g.write(unknown_value) + } + ast.PrefixExpr { + g.write(unknown_value) + } + ast.TypeNode { + sym := g.table.get_type_symbol(g.unwrap_generic(typ)) + g.write('$sym.name') + } + else { + g.writeln(unknown_value) + } + } + g.write(' /* typeof: ' + expr.type_name() + ' type: ' + typ.str() + ' */ ') +} + // TODO fn (mut g JsGen) gen_assert_stmt(a ast.AssertStmt) { if !a.is_used { @@ -948,12 +1080,14 @@ fn (mut g JsGen) gen_assert_stmt(a ast.AssertStmt) { s_assertion := a.expr.str().replace('"', "'") mut mod_path := g.file.path.replace('\\', '\\\\') if g.is_test { + metaname_ok := g.gen_assert_metainfo(a) g.writeln(' g_test_oks++;') - g.writeln(' cb_assertion_ok("$mod_path", ${a.pos.line_nr + 1}, "assert $s_assertion", "${g.fn_decl.name}()" );') + g.writeln(' cb_assertion_ok($metaname_ok);') g.writeln('} else {') + metaname_fail := g.gen_assert_metainfo(a) g.writeln(' g_test_fails++;') - g.writeln(' cb_assertion_failed("$mod_path", ${a.pos.line_nr + 1}, "assert $s_assertion", "${g.fn_decl.name}()" );') - g.writeln(' exit(1);') + g.writeln(' cb_assertion_failed($metaname_fail);') + g.writeln(' builtin.exit(1);') g.writeln('}') return } diff --git a/vlib/v/preludes_js/tests_assertions.v b/vlib/v/preludes_js/tests_assertions.v index 4226ea2f51..0c96f95fb6 100644 --- a/vlib/v/preludes_js/tests_assertions.v +++ b/vlib/v/preludes_js/tests_assertions.v @@ -1,6 +1,6 @@ module main -import os_js +import os // VAssertMetaInfo is used during assertions. An instance of it // is filled in by compile time generated code, when an assertion fails. @@ -20,7 +20,7 @@ pub: const use_relative_paths = can_use_relative_paths() fn can_use_relative_paths() bool { - return match os_js.getenv('VERROR_PATHS') { + return match os.getenv('VERROR_PATHS') { 'absolute' { false } else { true } } @@ -40,11 +40,13 @@ fn myeprintln(s string) { // ////////////////////////////////////////////////////////////////// // TODO copy pasta builtin.v fn ___print_assert_failure fn cb_assertion_failed(i VAssertMetaInfo) { - filepath := if use_relative_paths { i.fpath } else { os_js.real_path(i.fpath) } + filepath := if use_relative_paths { i.fpath } else { os.real_path(i.fpath) } mut final_filepath := filepath + ':${i.line_nr + 1}:' - mut final_funcname := 'fn ' + i.fn_name.replace('main.', '').replace('__', '.') + mut final_funcname := 'fn ' + i.fn_name final_src := 'assert ' + i.src + myeprintln('$final_filepath $final_funcname') + if i.op.len > 0 && i.op != 'call' { mut lvtitle := ' Left value:' mut rvtitle := ' Right value:' @@ -72,15 +74,15 @@ fn cb_assertion_ok(i &VAssertMetaInfo) { } fn cb_propagate_test_error(line_nr int, file string, mod string, fn_name string, errmsg string) { - filepath := if use_relative_paths { file } else { os_js.real_path(file) } + filepath := if use_relative_paths { file } else { os.real_path(file) } mut final_filepath := filepath + ':$line_nr:' mut final_funcname := 'fn ' + fn_name.replace('main.', '').replace('__', '.') final_msg := errmsg myeprintln('$final_filepath $final_funcname failed propagation with error: $final_msg') - // TODO: implement os_js.is_file and os_js.read_lines: + // TODO: implement os.is_file and os.read_lines: /* - if os_js.is_file(file) { - source_lines := os_js.read_lines(file) or { []string{len: line_nr + 1} } + if os.is_file(file) { + source_lines := os.read_lines(file) or { []string{len: line_nr + 1} } myeprintln('${line_nr:5} | ${source_lines[line_nr - 1]}') } */ diff --git a/vlib/v/preludes_js/tests_with_stats.v b/vlib/v/preludes_js/tests_with_stats.v index 2ea2d568ba..cc6992ec01 100644 --- a/vlib/v/preludes_js/tests_with_stats.v +++ b/vlib/v/preludes_js/tests_with_stats.v @@ -20,7 +20,7 @@ mut: // /////////////////////////////////////////////////////////////////// // Called at the start of the test program produced by `v -stats file_test.v` -fn start_testing(total_number_of_tests int, vfilename string) BenchedTests { +pub fn start_testing(total_number_of_tests int, vfilename string) BenchedTests { mut benched_tests_res := BenchedTests{} benched_tests_res.total_number_of_tests = total_number_of_tests benched_tests_res.test_suit_file = vfilename