diff --git a/vlib/builtin/js/builtin.js.v b/vlib/builtin/js/builtin.js.v index b97445ec99..99f451814c 100644 --- a/vlib/builtin/js/builtin.js.v +++ b/vlib/builtin/js/builtin.js.v @@ -78,3 +78,21 @@ fn js_stacktrace() string { return stacktrace } + +// Check for nil value +pub fn isnil(val voidptr) bool { + res := false + // This one is kinda weird. In C and native backend we can cast booleans and integers to pointers + // so we just check *for* all possible NULL-like values here. + #val = val.valueOf() + #res.val = val === null || val === undefined || val === false || val === 0 || val === BigInt(0) + + return res +} + +pub fn (f float_literal) str() string { + res := '' + #res.str += f.valueOf() + + return res +} diff --git a/vlib/builtin/js/byte.js.v b/vlib/builtin/js/byte.js.v index 78b3f6fb2e..c2bee28d0f 100644 --- a/vlib/builtin/js/byte.js.v +++ b/vlib/builtin/js/byte.js.v @@ -7,14 +7,6 @@ pub fn (b byte) is_space() bool { return result } -pub fn (c byte) is_letter() bool { - result := false - - #result.val = (c.val >= `a`.charCodeAt() && c.val <= `z`.charCodeAt()) || (c.val >= `A`.charCodeAt() && c.val <= `Z`.charCodeAt()) - - return result -} - pub fn (c byte) str() string { res := '' #res.str = c.val.toString() @@ -37,3 +29,42 @@ pub fn (c byte) repeat(count int) string { return res } + +pub fn (c byte) is_digit() bool { + return c >= `0` && c <= `9` +} + +// is_hex_digit returns `true` if the byte is either in range 0-9, a-f or A-F and `false` otherwise. +// Example: assert byte(`F`) == true +[inline] +pub fn (c byte) is_hex_digit() bool { + return c.is_digit() || (c >= `a` && c <= `f`) || (c >= `A` && c <= `F`) +} + +// is_oct_digit returns `true` if the byte is in range 0-7 and `false` otherwise. +// Example: assert byte(`7`) == true +[inline] +pub fn (c byte) is_oct_digit() bool { + return c >= `0` && c <= `7` +} + +// is_bin_digit returns `true` if the byte is a binary digit (0 or 1) and `false` otherwise. +// Example: assert byte(`0`) == true +[inline] +pub fn (c byte) is_bin_digit() bool { + return c == `0` || c == `1` +} + +// is_letter returns `true` if the byte is in range a-z or A-Z and `false` otherwise. +// Example: assert byte(`V`) == true +[inline] +pub fn (c byte) is_letter() bool { + return (c >= `a` && c <= `z`) || (c >= `A` && c <= `Z`) +} + +// is_alnum returns `true` if the byte is in range a-z, A-Z, 0-9 and `false` otherwise. +// Example: assert byte(`V`) == true +[inline] +pub fn (c byte) is_alnum() bool { + return c.is_letter() || c.is_digit() +} diff --git a/vlib/builtin/js/int.js.v b/vlib/builtin/js/int.js.v index dd67017078..379fc95645 100644 --- a/vlib/builtin/js/int.js.v +++ b/vlib/builtin/js/int.js.v @@ -23,35 +23,35 @@ pub fn (i u16) str() string { pub fn (i int) str() string { mut res := '' - #res = new string( i ) + #res = new string( i+'' ) return res } pub fn (i i64) str() string { mut res := '' - #res = new string( i ) + #res = new string( i + '') return res } pub fn (i u32) str() string { mut res := '' - #res = new string( i ) + #res = new string( i + '') return res } pub fn (i u64) str() string { mut res := '' - #res = new string( i ) + #res = new string( i + '') return res } pub fn (i bool) str() string { mut res := '' - #res = new string( i ) + #res = new string( i + '') return res } diff --git a/vlib/builtin/js/string.js.v b/vlib/builtin/js/string.js.v index 43a35a96e5..16e290db3e 100644 --- a/vlib/builtin/js/string.js.v +++ b/vlib/builtin/js/string.js.v @@ -232,6 +232,13 @@ pub fn (s string) u64() u64 { return u64(JS.parseInt(s)) } +pub fn (s string) byte() u64 { + res := byte(0) + #res.val = byte(JS.parseInt(s)) + + return res +} + // trim_right strips any of the characters given in `cutset` from the right of the string. // Example: assert ' Hello V d'.trim_right(' d') == ' Hello V' pub fn (s string) trim_right(cutset string) string { @@ -897,3 +904,6 @@ pub fn (s []string) join(sep string) string { } return res } + +// There's no better way to find length of JS String in bytes. +#Object.defineProperty(string.prototype,"len", { get: function() {return new int(new TextEncoder().encode(this.str).length);}, set: function(l) {/* ignore */ } }); diff --git a/vlib/builtin/js/string_test.js.v b/vlib/builtin/js/string_test.js.v index 82d214b498..71c608658e 100644 --- a/vlib/builtin/js/string_test.js.v +++ b/vlib/builtin/js/string_test.js.v @@ -359,11 +359,10 @@ fn test_reassign() { assert b == 'hi!' } -/* fn test_runes() { s := 'привет' println(s.len) - //assert s.len == 12 + // assert s.len == 12 s2 := 'privet' assert s2.len == 6 u := s.runes() @@ -386,7 +385,7 @@ fn test_runes() { last := u[u.len - 1] assert first.str().len == 2 assert last.str().len == 2 -}*/ +} fn test_contains() { s := 'view.v' @@ -420,7 +419,6 @@ fn test_arr_contains() { assert ints.contains(2) } -/* fn test_to_num() { s := '7' assert s.int() == 7 @@ -434,7 +432,7 @@ fn test_to_num() { big := '93993993939322' assert big.u64() == 93993993939322 assert big.i64() == 93993993939322 -}*/ +} /* fn test_inter_format_string() { @@ -473,7 +471,6 @@ fn test_hash() { assert s5.hash() % ((1 << 20) - 1) == s.hash() % ((1 << 20) - 1) assert s5.hash() % ((1 << 20) - 1) == 592861 }*/ - fn test_trim() { assert 'banana'.trim('bna') == '' assert 'abc'.trim('ac') == 'b' diff --git a/vlib/strings/builder.js.v b/vlib/strings/builder.js.v index 7134b2a015..a2cc91854f 100644 --- a/vlib/strings/builder.js.v +++ b/vlib/strings/builder.js.v @@ -97,3 +97,25 @@ pub fn (mut b Builder) write_runes(runes []rune) { b << res.bytes() } } + +// after(6) returns 'world' +// buf == 'hello world' +pub fn (mut b Builder) after(n int) string { + if n >= b.len { + return '' + } + + x := b.slice(n, b.len) + return x.bytestr() +} + +// last_n(5) returns 'world' +// buf == 'hello world' +pub fn (b &Builder) last_n(n int) string { + if n >= b.len { + return '' + } + + x := b.slice(b.len - n, b.len) + return x.bytestr() +} diff --git a/vlib/v/gen/js/comptime.v b/vlib/v/gen/js/comptime.v index 69d60412d4..ce4c43a9da 100644 --- a/vlib/v/gen/js/comptime.v +++ b/vlib/v/gen/js/comptime.v @@ -272,7 +272,7 @@ fn (mut g JsGen) comp_if_to_ifdef(name string, is_comptime_optional bool) ?strin return 'false' } 'no_bounds_checking' { - return 'CUSTOM_DEFINE_no_bounds_checking' + return 'checkDefine("CUSTOM_DEFINE_no_bounds_checking")' } 'freestanding' { return '_VFREESTANDING' @@ -301,7 +301,7 @@ fn (mut g JsGen) comp_if_to_ifdef(name string, is_comptime_optional bool) ?strin else { if is_comptime_optional || (g.pref.compile_defines_all.len > 0 && name in g.pref.compile_defines_all) { - return 'CUSTOM_DEFINE_$name' + return 'checkDefine("CUSTOM_DEFINE_$name")' } return error('bad os ifdef name "$name"') // should never happen, caught in the checker } diff --git a/vlib/v/gen/js/fn.v b/vlib/v/gen/js/fn.v index dd52f292eb..734042d207 100644 --- a/vlib/v/gen/js/fn.v +++ b/vlib/v/gen/js/fn.v @@ -221,6 +221,7 @@ fn (mut g JsGen) method_call(node ast.CallExpr) { name = g.generic_fn_name(node.concrete_types, name, false) g.write('${name}(') g.expr(it.left) + g.gen_deref_ptr(it.left_type) g.write(',') for i, arg in it.args { g.expr(arg.expr) diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index 4af8632679..d37175b3f6 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -144,7 +144,6 @@ pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string { if g.file.mod.name == 'builtin' && !g.generated_builtin { g.gen_builtin_type_defs() g.writeln('Object.defineProperty(array.prototype,"len", { get: function() {return new int(this.arr.arr.length);}, set: function(l) { this.arr.arr.length = l.valueOf(); } }); ') - g.writeln('Object.defineProperty(string.prototype,"len", { get: function() {return new int(this.str.length);}, set: function(l) {/* ignore */ } }); ') g.writeln('Object.defineProperty(map.prototype,"len", { get: function() {return new int(this.map.size);}, set: function(l) { this.map.size = l.valueOf(); } }); ') g.writeln('Object.defineProperty(array.prototype,"length", { get: function() {return new int(this.arr.arr.length);}, set: function(l) { this.arr.arr.length = l.valueOf(); } }); ') g.generated_builtin = true @@ -410,19 +409,14 @@ pub fn (mut g JsGen) init() { 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;') } - g.definitions.writeln('function alias(value) { return value; } ') - - g.definitions.writeln('function \$v_fmt(value) { let res = ""; - if (Object.getPrototypeOf(s).hasOwnProperty("str") && typeof s.str == "function") res = s.str().str - else res = s.toString() - return res - } ') + g.definitions.writeln('function checkDefine(key) {') + g.definitions.writeln('\tif (globalThis.hasOwnProperty(key)) { return !!globalThis[key]; } return false;') + g.definitions.writeln('}') } pub fn (g JsGen) hashes() string { @@ -848,7 +842,7 @@ fn (mut g JsGen) expr(node ast.Expr) { // TODO } ast.CTempVar { - g.write('/* ast.CTempVar: node.name */') + g.write('$node.name') } ast.DumpExpr { g.write('/* ast.DumpExpr: $node.expr */') @@ -1008,6 +1002,60 @@ fn (mut g JsGen) expr(node ast.Expr) { } } +struct UnsupportedAssertCtempTransform { + msg string + code int +} + +const unsupported_ctemp_assert_transform = IError(UnsupportedAssertCtempTransform{}) + +fn (mut g JsGen) assert_subexpression_to_ctemp(expr ast.Expr, expr_type ast.Type) ?ast.Expr { + match expr { + ast.CallExpr { + return g.new_ctemp_var_then_gen(expr, expr_type) + } + ast.ParExpr { + if expr.expr is ast.CallExpr { + return g.new_ctemp_var_then_gen(expr.expr, expr_type) + } + } + ast.SelectorExpr { + if expr.expr is ast.CallExpr { + sym := g.table.get_final_type_symbol(g.unwrap_generic(expr.expr.return_type)) + if sym.kind == .struct_ { + if (sym.info as ast.Struct).is_union { + return js.unsupported_ctemp_assert_transform + } + } + return g.new_ctemp_var_then_gen(expr, expr_type) + } + } + else {} + } + return js.unsupported_ctemp_assert_transform +} + +fn (mut g JsGen) new_ctemp_var(expr ast.Expr, expr_type ast.Type) ast.CTempVar { + return ast.CTempVar{ + name: g.new_tmp_var() + typ: expr_type + is_ptr: expr_type.is_ptr() + orig: expr + } +} + +fn (mut g JsGen) new_ctemp_var_then_gen(expr ast.Expr, expr_type ast.Type) ast.CTempVar { + x := g.new_ctemp_var(expr, expr_type) + g.gen_ctemp_var(x) + return x +} + +fn (mut g JsGen) gen_ctemp_var(tvar ast.CTempVar) { + g.write('let $tvar.name = ') + g.expr(tvar.orig) + g.writeln(';') +} + fn (mut g JsGen) gen_assert_metainfo(node ast.AssertStmt) string { mod_path := g.file.path fn_name := g.fn_decl.name @@ -1029,12 +1077,12 @@ fn (mut g JsGen) gen_assert_metainfo(node ast.AssertStmt) string { g.writeln('\t${metaname}.op = new string("$expr_op_str");') g.writeln('\t${metaname}.llabel = new string("$expr_left_str");') g.writeln('\t${metaname}.rlabel = new string("$expr_right_str");') - g.write('\t${metaname}.lvalue = new string("') + g.write('\t${metaname}.lvalue = ') g.gen_assert_single_expr(node.expr.left, node.expr.left_type) - g.writeln('");') - g.write('\t${metaname}.rvalue = new string("') + g.writeln(';') + g.write('\t${metaname}.rvalue = ') g.gen_assert_single_expr(node.expr.right, node.expr.right_type) - g.writeln('");') + g.writeln(';') } ast.CallExpr { g.writeln('\t${metaname}.op = new string("call");') @@ -1049,28 +1097,69 @@ fn (mut g JsGen) gen_assert_single_expr(expr ast.Expr, typ ast.Type) { unknown_value := '*unknown value*' match expr { ast.CastExpr, ast.IfExpr, ast.IndexExpr, ast.MatchExpr { - g.write(unknown_value) + g.write('new string("$unknown_value")') } ast.PrefixExpr { - g.write(unknown_value) + if expr.right is ast.CastExpr { + // TODO: remove this check; + // vlib/builtin/map_test.v (a map of &int, set to &int(0)) fails + // without special casing ast.CastExpr here + g.write('new string("$unknown_value")') + } else { + g.gen_expr_to_string(expr, typ) + } } ast.TypeNode { sym := g.table.get_type_symbol(g.unwrap_generic(typ)) - g.write('$sym.name') + g.write('new string("$sym.name"') } else { - g.write(unknown_value) + mut should_clone := true + if typ == ast.string_type && expr is ast.StringLiteral { + should_clone = false + } + if expr is ast.CTempVar { + if expr.orig is ast.CallExpr { + should_clone = false + if expr.orig.or_block.kind == .propagate { + should_clone = true + } + if expr.orig.is_method && expr.orig.args.len == 0 + && expr.orig.name == 'type_name' { + should_clone = true + } + } + } + if should_clone { + g.write('string_clone(') + } + g.gen_expr_to_string(expr, typ) + if should_clone { + g.write(')') + } } } // g.writeln(' /* typeof: ' + expr.type_name() + ' type: ' + typ.str() + ' */ ') } // TODO -fn (mut g JsGen) gen_assert_stmt(a ast.AssertStmt) { - if !a.is_used { +fn (mut g JsGen) gen_assert_stmt(orig_node ast.AssertStmt) { + mut node := orig_node + if !node.is_used { return } + g.writeln('// assert') + + if mut node.expr is ast.InfixExpr { + if subst_expr := g.assert_subexpression_to_ctemp(node.expr.left, node.expr.left_type) { + node.expr.left = subst_expr + } + if subst_expr := g.assert_subexpression_to_ctemp(node.expr.right, node.expr.right_type) { + node.expr.right = subst_expr + } + } + mut a := node g.write('if( ') g.expr(a.expr) g.write('.valueOf() ) {')