From 90c1c639fe8c60bc02f6c97591e5f8a73b8a83ae Mon Sep 17 00:00:00 2001 From: Leah Lundqvist Date: Tue, 8 Dec 2020 17:49:20 +0100 Subject: [PATCH] js: types (#7108) --- vlib/builtin/js/builtin.v | 8 +- vlib/builtin/js/jsfns.js.v | 21 ++ vlib/builtin/js/string.v | 9 + vlib/v/gen/js/builtin_types.v | 260 +++++++++++++++++++++++ vlib/v/gen/js/js.v | 390 +++++++++++++++------------------- vlib/v/gen/js/tests/array.v | 2 + vlib/v/gen/js/tests/js.v | 7 +- vlib/v/parser/fn.v | 2 +- 8 files changed, 476 insertions(+), 223 deletions(-) create mode 100644 vlib/builtin/js/string.v create mode 100644 vlib/v/gen/js/builtin_types.v diff --git a/vlib/builtin/js/builtin.v b/vlib/builtin/js/builtin.v index 1cac2f3cef..081ad60638 100644 --- a/vlib/builtin/js/builtin.v +++ b/vlib/builtin/js/builtin.v @@ -4,14 +4,18 @@ module builtin +fn (a any) toString() + pub fn println(s any) { - JS.console.log(s) + // Quickfix to properly print basic types + // TODO: Add proper detection code for this + JS.console.log(s.toString()) } pub fn print(s any) { // TODO // $if js.node { - JS.process.stdout.write(s) + JS.process.stdout.write(s.toString()) // } $else { // panic('Cannot `print` in a browser, use `println` instead') // } diff --git a/vlib/builtin/js/jsfns.js.v b/vlib/builtin/js/jsfns.js.v index 4c023a1bfa..b65945f8a3 100644 --- a/vlib/builtin/js/jsfns.js.v +++ b/vlib/builtin/js/jsfns.js.v @@ -7,6 +7,23 @@ module builtin +pub struct JS.Number {} +pub struct JS.String { + length JS.Number +} +pub struct JS.Boolean {} +pub struct JS.Array {} +pub struct JS.Map {} + +// Type prototype functions +fn (v JS.String) toString() JS.String +fn (v JS.Number) toString() JS.String +fn (v JS.Boolean) toString() JS.String +fn (v JS.Array) toString() JS.String +fn (v JS.Map) toString() JS.String + +fn (v JS.String) slice(a int, b int) JS.String + // Top level functions fn JS.eval(string) any fn JS.parseInt(string, f64) f64 @@ -63,3 +80,7 @@ fn JS.Math.round(f64) f64 fn JS.Math.sin(f64) f64 fn JS.Math.sqrt(f64) f64 fn JS.Math.tan(f64) f64 + +// JSON +fn JS.JSON.stringify(any) string +fn JS.JSON.parse(string) any \ No newline at end of file diff --git a/vlib/builtin/js/string.v b/vlib/builtin/js/string.v new file mode 100644 index 0000000000..51ff40a13d --- /dev/null +++ b/vlib/builtin/js/string.v @@ -0,0 +1,9 @@ +module builtin + +pub struct string { + str JS.String +} + +pub fn (s string) slice(a int, b int) string { + return string(s.str.slice(a, b)) +} diff --git a/vlib/v/gen/js/builtin_types.v b/vlib/v/gen/js/builtin_types.v new file mode 100644 index 0000000000..f7e0417ea8 --- /dev/null +++ b/vlib/v/gen/js/builtin_types.v @@ -0,0 +1,260 @@ +module js +import v.table + +fn (mut g JsGen) to_js_typ_def_val(s string) string { + mut dval := '' + match s { + 'JS.Number' { dval = '0' } + 'JS.String' { dval = '""' } + 'JS.Boolean' { dval = 'false' } + 'JS.Array', 'JS.Map' { dval = '' } + else { dval = '{}' } + } + return dval +} + +fn (mut g JsGen) to_js_typ_val(t table.Type) string { + sym := g.table.get_type_symbol(t) + mut styp := '' + mut prefix := if g.file.mod.name == 'builtin' { 'new ' } else { '' } + match sym.kind { + .i8, .i16, .int, .i64, .byte, .u16, .u32, .u64, .f32, .f64, .any_int, .any_float, .size_t { + styp = '${prefix}${g.sym_to_js_typ(sym)}(0)' + } + .bool { + styp = '${prefix}${g.sym_to_js_typ(sym)}(false)' + } + .string { + styp = '${prefix}${g.sym_to_js_typ(sym)}("")' + } + .map { + styp = 'new Map()' + } + .array { + styp = '${prefix}${g.sym_to_js_typ(sym)}()' + } + .struct_ { + styp = 'new ${g.js_name(sym.name)}(${g.to_js_typ_def_val(sym.name)})' + } + else { + // TODO + styp = 'undefined' + } + } + return styp +} + +fn (mut g JsGen) sym_to_js_typ(sym table.TypeSymbol) string { + mut styp := '' + match sym.kind { + .i8 { styp = 'i8' } + .i16 { styp = 'i16' } + .int { styp = 'int' } + .i64 { styp = 'i64' } + .byte { styp = 'byte' } + .u16 { styp = 'u16' } + .u32 { styp = 'u32' } + .u64 { styp = 'u64' } + .f32 { styp = 'f32' } + .f64 { styp = 'f64' } + .any_int { styp = 'any_int' } + .any_float { styp = 'any_float' } + .size_t { styp = 'size_t' } + .bool { styp = 'bool' } + .string { styp = 'string' } + .map { styp = 'map' } + .array { styp = 'array' } + else { + // TODO + styp = 'undefined' + } + } + return styp +} + +// V type to JS type +pub fn (mut g JsGen) typ(t table.Type) string { + sym := g.table.get_type_symbol(t) + mut styp := '' + match sym.kind { + .placeholder { + // This should never happen: means checker bug + styp = 'any' + } + .void { + styp = 'void' + } + .voidptr { + styp = 'any' + } + .byteptr, .charptr { + styp = '${g.sym_to_js_typ(sym)}' + } + .i8, .i16, .int, .i64, .byte, .u16, .u32, .u64, .f32, .f64, .any_int, .any_float, .size_t { + styp = '${g.sym_to_js_typ(sym)}' + } + .bool { + styp = '${g.sym_to_js_typ(sym)}' + } + .none_ { + styp = 'undefined' + } + .string, .ustring, .char { + styp = '${g.sym_to_js_typ(sym)}' + } + // 'array_array_int' => 'number[][]' + .array { + info := sym.info as table.Array + styp = '${g.sym_to_js_typ(sym)}(${g.typ(info.elem_type)})' + } + .array_fixed { + info := sym.info as table.ArrayFixed + styp = '${g.sym_to_js_typ(sym)}(${g.typ(info.elem_type)})' + } + .chan { + styp = 'chan' + } + // 'map[string]int' => 'Map' + .map { + info := sym.info as table.Map + key := g.typ(info.key_type) + val := g.typ(info.value_type) + styp = 'Map<$key, $val>' + } + .any { + styp = 'any' + } + // ns.Foo => alias["Foo"]["prototype"] + .struct_ { + styp = g.struct_typ(sym.name) + } + .generic_struct_inst {} + // 'multi_return_int_int' => '[number, number]' + .multi_return { + info := sym.info as table.MultiReturn + types := info.types.map(g.typ(it)) + joined := types.join(', ') + styp = '[$joined]' + } + .sum_type { + // TODO: Implement sumtypes + styp = 'union_sym_type' + } + .alias { + // TODO: Implement aliases + styp = 'alias' + } + .enum_ { + // NB: We could declare them as TypeScript enums but TS doesn't like + // our namespacing so these break if declared in a different module. + // Until this is fixed, We need to use the type of an enum's members + // rather than the enum itself, and this can only be 'number' for now + styp = 'number' + } + // 'anon_fn_7_7_1' => '(a number, b number) => void' + .function { + info := sym.info as table.FnType + styp = g.fn_typ(info.func.params, info.func.return_type) + } + .interface_ { + styp = g.js_name(sym.name) + } + .rune { + styp = 'any' + } + .aggregate { + panic('TODO: unhandled aggregate in JS') + } + } + /* + else { + println('jsgen.typ: Unhandled type $t') + styp = sym.name + } + */ + if styp.starts_with('JS.') { + return styp[3..] + } + return styp +} + +fn (mut g JsGen) fn_typ(args []table.Param, 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) + if ns == 'JS' { return s[3..] } + mut name := if ns == g.ns.name { s.split('.').last() } else { g.get_alias(s) } + mut styp := '' + for i, v in name.split('.') { + if i == 0 { + styp = v + } else { + styp += '["$v"]' + } + } + if ns in ['', g.ns.name] { + return styp + } + return styp + '["prototype"]' +} + +// ugly arguments but not sure a config struct would be worth it +fn (mut g JsGen) gen_builtin_prototype(typ_name string, val_name string, default_value string, constructor string, value_of string, to_string string, extras string) { + g.writeln('function ${typ_name}($val_name = ${default_value}) { $constructor }') + g.writeln('${typ_name}.prototype = {') + g.inc_indent() + g.writeln('val: $default_value,') + if extras.len > 0 { + g.writeln('$extras,') + } + for method in g.method_fn_decls[typ_name] { + g.inside_def_typ_decl = true + g.gen_method_decl(method) + g.inside_def_typ_decl = false + g.writeln(',') + } + g.writeln('valueOf() { return $value_of },') + g.writeln('toString() { return $to_string }') + g.dec_indent() + g.writeln('};\n') +} + +// generate builtin type definitions, used for casting and methods. +fn (mut g JsGen) gen_builtin_type_defs() { + g.inc_indent() + for typ_name in v_types { + // TODO: JsDoc + match typ_name { + 'i8', 'i16', 'int', 'i64', 'byte', 'u16', 'u32', 'u64', 'any_int', 'size_t' { + // TODO: Bounds checking + g.gen_builtin_prototype(typ_name, 'val', 'new Number(0)', 'this.val = val | 0;', 'this.val | 0', '(this.val | 0).toString()', '') + } + 'f32', 'f64', 'any_float' { + g.gen_builtin_prototype(typ_name, 'val', 'new Number(0)', 'this.val = val;', 'this.val', 'this.val.toString()', '') + } + 'bool' { + g.gen_builtin_prototype(typ_name, 'val', 'new Boolean(false)', 'this.val = val;', 'this.val', 'this.val.toString()', '') + } + 'string' { + g.gen_builtin_prototype(typ_name, 'str', 'new String("")', 'this.str = str;', 'this.str', 'this.str.toString()', 'get length() { return this.str.length }') + } + 'map' { + g.gen_builtin_prototype(typ_name, 'val', 'new Map()', 'this.val = val;', 'this.val', 'this.val.toString()', '') + } + 'array' { + g.gen_builtin_prototype(typ_name, 'val', 'new Array()', 'this.val = val;', 'this.val', 'this.val.toString()', '') + } + else {} + } + } + g.dec_indent() +} \ No newline at end of file diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index a263a03652..d15a4cbae1 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -3,6 +3,7 @@ module js import strings import v.ast import v.table +import v.token import v.pref import v.util import v.depgraph @@ -13,7 +14,9 @@ const ( 'default', 'delete', 'do', 'else', 'enum', 'export', 'extends', 'finally', 'for', 'function', 'if', 'implements', 'import', 'in', 'instanceof', 'interface', 'let', 'new', 'package', 'private', 'protected', 'public', 'return', 'static', 'super', 'switch', 'this', 'throw', 'try', 'typeof', 'var', 'void', - 'while', 'with', 'yield'] + 'while', 'with', 'yield', 'Number', 'String', 'Boolean', 'Array', 'Map'] + // used to generate type structs + v_types = ['i8', 'i16', 'int', 'i64', 'byte', 'u16', 'u32', 'u64', 'f32', 'f64', 'any_int', 'any_float', 'size_t', 'bool', 'string', 'map', 'array'] 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', '\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'] ) @@ -32,25 +35,27 @@ struct JsGen { table &table.Table 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 - 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 + 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 } pub fn gen(files []ast.File, table &table.Table, pref &pref.Preferences) string { @@ -89,6 +94,11 @@ pub fn gen(files []ast.File, table &table.Table, pref &pref.Preferences) string imports << imp.mod } graph.add(g.file.mod.name, imports) + // builtin types + if g.file.mod.name == 'builtin' && !g.generated_builtin { + g.gen_builtin_type_defs() + g.generated_builtin = true + } g.stmts(file.stmts) // store the current namespace g.escape_namespace() @@ -119,6 +129,12 @@ pub fn gen(files []ast.File, table &table.Table, pref &pref.Preferences) string out += '\n\t/* module exports */' } out += '\n\treturn {' + // export builtin types + if name == 'builtin' { + for typ in v_types { + out += '\n\t\t$typ,' + } + } for i, pub_var in g.namespaces[node.name].pub_vars { out += '\n\t\t$pub_var' if i < g.namespaces[node.name].pub_vars.len - 1 { @@ -136,13 +152,29 @@ pub fn gen(files []ast.File, table &table.Table, pref &pref.Preferences) string } out += key.replace('.', '_') } - out += ');\n\n' + out += ');\n' + // generate builtin basic type casts + if name == 'builtin' { + out += '// builtin type casts\n' + out += 'const [' + for i, typ in v_types { + if i > 0 { out += ', ' } + out += '$typ' + } + out += '] = [' + for i, typ in v_types { + if i > 0 { out += ',' } + out += '\n\tfunction(val) { return new builtin.${typ}(val) }' + } + out += '\n]\n' + } } if pref.is_shared { // Export, through CommonJS, the module of the entry file if `-shared` was passed export := nodes[nodes.len - 1].name - out += 'if (typeof module === "object" && module.exports) module.exports = $export;' + out += 'if (typeof module === "object" && module.exports) module.exports = $export;\n' } + out += '\n' return out } @@ -175,7 +207,7 @@ pub fn (mut g JsGen) find_class_methods(stmts []ast.Stmt) { ast.FnDecl { if stmt.is_method { // Found struct method, store it to be generated along with the class. - class_name := g.table.get_type_name(stmt.receiver.typ) + mut class_name := g.table.get_type_name(stmt.receiver.typ) // Workaround until `map[key] << val` works. mut arr := g.method_fn_decls[class_name] arr << stmt @@ -199,171 +231,6 @@ pub fn (g JsGen) hashes() string { return res } -// V type to JS type -pub fn (mut g JsGen) typ(t table.Type) string { - sym := g.table.get_type_symbol(t) - mut styp := '' - match sym.kind { - .placeholder { - // This should never happen: means checker bug - styp = 'any' - } - .void { - styp = 'void' - } - .voidptr { - styp = 'any' - } - .byteptr, .charptr { - styp = 'string' - } - .i8, .i16, .int, .i64, .byte, .u16, .u32, .u64, .f32, .f64, .any_int, .any_float, .size_t { - // TODO: Should u64 and i64 use BigInt rather than number? - styp = 'number' - } - .bool { - styp = 'boolean' - } - .none_ { - styp = 'undefined' - } - .string, .ustring, .char { - styp = 'string' - } - // 'array_array_int' => 'number[][]' - .array { - info := sym.info as table.Array - styp = g.typ(info.elem_type) + '[]' - } - .array_fixed { - info := sym.info as table.ArrayFixed - styp = g.typ(info.elem_type) + '[]' - } - .chan { - styp = 'chan' - } - // 'map[string]int' => 'Map' - .map { - info := sym.info as table.Map - key := g.typ(info.key_type) - val := g.typ(info.value_type) - styp = 'Map<$key, $val>' - } - .any { - styp = 'any' - } - // ns.Foo => alias["Foo"]["prototype"] - .struct_ { - styp = g.struct_typ(sym.name) - } - .generic_struct_inst {} - // 'multi_return_int_int' => '[number, number]' - .multi_return { - info := sym.info as table.MultiReturn - types := info.types.map(g.typ(it)) - joined := types.join(', ') - styp = '[$joined]' - } - .sum_type { - // TODO: Implement sumtypes - styp = 'union_sym_type' - } - .alias { - // TODO: Implement aliases - styp = 'alias' - } - .enum_ { - // NB: We could declare them as TypeScript enums but TS doesn't like - // our namespacing so these break if declared in a different module. - // Until this is fixed, We need to use the type of an enum's members - // rather than the enum itself, and this can only be 'number' for now - styp = 'number' - } - // 'anon_fn_7_7_1' => '(a number, b number) => void' - .function { - info := sym.info as table.FnType - styp = g.fn_typ(info.func.params, info.func.return_type) - } - .interface_ { - styp = g.js_name(sym.name) - } - .rune { - styp = 'any' - } - .aggregate { - panic('TODO: unhandled aggregate in JS') - } - } - /* - else { - println('jsgen.typ: Unhandled type $t') - styp = sym.name - } - */ - if styp.starts_with('JS.') { - return styp[3..] - } - return styp -} - -fn (mut g JsGen) fn_typ(args []table.Param, 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.ns.name { s.split('.').last() } else { g.get_alias(s) } - mut styp := '' - for i, v in name.split('.') { - if i == 0 { - styp = v - } else { - styp += '["$v"]' - } - } - if ns in ['', g.ns.name] { - return styp - } - return styp + '["prototype"]' -} - -fn (mut g JsGen) to_js_typ_val(t table.Type) string { - sym := g.table.get_type_symbol(t) - mut styp := '' - match sym.kind { - .i8, .i16, .int, .i64, .byte, .u16, .u32, .u64, .f32, .f64, .any_int, .any_float, .size_t { - styp = '0' - } - .bool { - styp = 'false' - } - .string { - styp = '""' - } - .map { - styp = 'new Map()' - } - .array { - styp = '[]' - } - .struct_ { - styp = 'new ${g.js_name(sym.name)}({})' - } - else { - // TODO - styp = 'undefined' - } - } - return styp -} - [inline] fn verror(msg string) { eprintln('jsgen error: $msg') @@ -576,9 +443,7 @@ fn (mut g JsGen) expr(node ast.Expr) { // TODO } ast.CastExpr { - // JS has no types, so no need to cast - // Just write the expression inside - g.expr(node.expr) + g.gen_type_cast_expr(node) } ast.CharLiteral { g.write("'$node.val'") @@ -593,7 +458,7 @@ fn (mut g JsGen) expr(node ast.Expr) { g.write('${styp}.$node.val') } ast.FloatLiteral { - g.write(node.val) + g.write('${g.typ(table.Type(table.f32_type))}($node.val)') } ast.Ident { g.gen_ident(node) @@ -611,7 +476,7 @@ fn (mut g JsGen) expr(node ast.Expr) { g.gen_infix_expr(node) } ast.IntegerLiteral { - g.write(node.val) + g.write('${g.typ(table.Type(table.int_type))}($node.val)') } ast.LockExpr { g.gen_lock_expr(node) @@ -664,8 +529,9 @@ fn (mut g JsGen) expr(node ast.Expr) { g.gen_string_inter_literal(node) } ast.StringLiteral { - text := node.val.replace('`', '\\`') - g.write('`$text`') + text := node.val.replace('\'', "\\'") + if g.file.mod.name == 'builtin' { g.write('new ') } + g.write("string('$text')") } ast.StructInit { // `user := User{name: 'Bob'}` @@ -904,6 +770,12 @@ fn (mut g JsGen) gen_method_decl(it ast.FnDecl) { } if !it.is_method { g.write('function ') + } else { + if it.attrs.contains('js_getter') { + g.write('get ') + } else if it.attrs.contains('js_setter') { + g.write('set ') + } } g.write('${name}(') if it.is_pub && !it.is_method { @@ -1093,12 +965,17 @@ fn (mut g JsGen) gen_hash_stmt(it ast.HashStmt) { } fn (mut g JsGen) gen_struct_decl(node ast.StructDecl) { - if node.name.starts_with('JS.') { + mut name := node.name + if name.starts_with('JS.') { return } + if name in v_types && g.ns.name == 'builtin' { + return + } + js_name := g.js_name(name) g.gen_attrs(node.attrs) g.doc.gen_fac_fn(node.fields) - g.write('function ${g.js_name(node.name)}({ ') + g.write('function ${js_name}({ ') for i, field in node.fields { g.write('$field.name = ') if field.has_default_expr { @@ -1117,31 +994,41 @@ fn (mut g JsGen) gen_struct_decl(node ast.StructDecl) { } g.dec_indent() g.writeln('};') - g.writeln('${g.js_name(node.name)}.prototype = {') + g.writeln('${js_name}.prototype = {') g.inc_indent() - fns := g.method_fn_decls[node.name] - for i, field in node.fields { + fns := g.method_fn_decls[name] + for field in node.fields { typ := g.typ(field.typ) g.doc.gen_typ(typ) g.write('$field.name: ${g.to_js_typ_val(field.typ)}') - if i < node.fields.len - 1 || fns.len > 0 { - g.writeln(',') - } else { - g.writeln('') - } + g.writeln(',') } - for i, cfn in fns { + for cfn in fns { g.gen_method_decl(cfn) - if i < fns.len - 1 { - g.writeln(',') - } else { - g.writeln('') - } + g.writeln(',') } + // gen toString method + fn_names := fns.map(it.name) + if !('toString' in fn_names) { + g.writeln('toString() {') + g.inc_indent() + g.write('return `${js_name} {') + for i, field in node.fields { + g.write(if i == 0 { ' ' } else { ', ' }) + match g.typ(field.typ).split('.').last() { + "string" { g.write('$field.name: "\${this["${field.name}"].toString()}"') } + else { g.write('$field.name: \${this["${field.name}"].toString()} ') } + } + } + g.writeln('}`') + g.dec_indent() + g.writeln('}') + } + g.dec_indent() g.writeln('};\n') if node.is_pub { - g.push_pub_var(node.name) + g.push_pub_var(name) } } @@ -1296,6 +1183,7 @@ fn (mut g JsGen) gen_ident(node ast.Ident) { // TODO `is` // TODO handle optionals g.write(name) + // TODO: Generate .val for basic types } fn (mut g JsGen) gen_lock_expr(node ast.LockExpr) { @@ -1392,6 +1280,7 @@ fn (mut g JsGen) gen_index_expr(expr ast.IndexExpr) { g.write('.get(') } g.expr(expr.index) + g.write('.toString()') if !expr.is_setter { g.write(')') } @@ -1401,7 +1290,7 @@ fn (mut g JsGen) gen_index_expr(expr ast.IndexExpr) { // 'string'[3] = `o` } else { g.expr(expr.left) - g.write('.charCodeAt(') + g.write('.str.charCodeAt(') g.expr(expr.index) g.write(')') } @@ -1433,10 +1322,15 @@ fn (mut g JsGen) gen_infix_expr(it ast.InfixExpr) { g.expr(it.right) g.write(if r_sym.kind == .map { '.has(' + } else if r_sym.kind == .string { + '.str.includes(' } else { '.includes(' }) g.expr(it.left) + if l_sym.kind == .string { + g.write('.str') + } g.write(')') if it.op == .not_in { g.write(')') @@ -1454,8 +1348,12 @@ fn (mut g JsGen) gen_infix_expr(it ast.InfixExpr) { } else { both_are_int := int(it.left_type) in table.integer_type_idxs && int(it.right_type) in table.integer_type_idxs + is_arithmetic := it.op in [token.Kind.plus, .minus, .mul, .div, .mod] + if is_arithmetic { + g.write('${g.typ(g.greater_typ(it.left_type, it.right_type))}(') + } if it.op == .div && both_are_int { - g.write('parseInt(') + g.write('((') } g.expr(it.left) // in js == is non-strict & === is strict, always do strict @@ -1467,13 +1365,47 @@ fn (mut g JsGen) gen_infix_expr(it ast.InfixExpr) { g.write(' $it.op ') } g.expr(it.right) - // Int division: 2.5 -> 2 by prepending |0 + // Int division: 2.5 -> 2 by appending |0 if it.op == .div && both_are_int { - g.write(',10)') + g.write(')|0)') + } + if is_arithmetic { + g.write(')') } } } +fn (mut g JsGen) greater_typ(left table.Type, right table.Type) table.Type { + l := int(left) + r := int(right) + lr := [l,r] + + if table.string_type_idx in lr { return table.Type(table.string_type_idx) } + + should_float := (l in table.integer_type_idxs && r in table.float_type_idxs) || (r in table.integer_type_idxs && l in table.float_type_idxs) + if should_float { + if table.f64_type_idx in lr { return table.Type(table.f64_type_idx) } + if table.f32_type_idx in lr { return table.Type(table.f32_type_idx) } + return table.Type(table.any_flt_type) + } + + should_int := (l in table.integer_type_idxs && r in table.integer_type_idxs) + if should_int { + // cant add to u64 - if (table.u64_type_idx in lr) { return table.Type(table.u64_type_idx) } + // just guessing this order + if table.i64_type_idx in lr { return table.Type(table.i64_type_idx) } + if table.u32_type_idx in lr { return table.Type(table.u32_type_idx) } + if table.int_type_idx in lr { return table.Type(table.int_type_idx) } + if table.u16_type_idx in lr { return table.Type(table.u16_type_idx) } + if table.i16_type_idx in lr { return table.Type(table.i16_type_idx) } + if table.byte_type_idx in lr { return table.Type(table.byte_type_idx) } + if table.i8_type_idx in lr { return table.Type(table.i8_type_idx) } + return table.Type(table.any_int_type_idx) + } + + return table.Type(l) +} + fn (mut g JsGen) gen_map_init_expr(it ast.MapInit) { // key_typ_sym := g.table.get_type_symbol(it.key_type) // value_typ_sym := g.table.get_type_symbol(it.value_type) @@ -1507,7 +1439,8 @@ fn (mut g JsGen) gen_selector_expr(it ast.SelectorExpr) { } fn (mut g JsGen) gen_string_inter_literal(it ast.StringInterLiteral) { - g.write('`') + if g.file.mod.name == 'builtin' { g.write('new ') } + g.write('string(`') for i, val in it.vals { escaped_val := val.replace('`', '\\`') g.write(escaped_val) @@ -1531,7 +1464,7 @@ fn (mut g JsGen) gen_string_inter_literal(it ast.StringInterLiteral) { } g.write('}') } - g.write('`') + g.write('`)') } fn (mut g JsGen) gen_struct_init(it ast.StructInit) { @@ -1582,3 +1515,24 @@ fn (mut g JsGen) gen_typeof_expr(it ast.TypeOf) { g.write('"$sym.name"') } } + +fn (mut g JsGen) gen_type_cast_expr(it ast.CastExpr) { + is_literal := ( + (it.expr is ast.IntegerLiteral && it.typ in table.integer_type_idxs) || + (it.expr is ast.FloatLiteral && it.typ in table.float_type_idxs) + ) + typ := g.typ(it.typ) + if !is_literal { + if !(typ in v_types) || g.ns.name == 'builtin' { + g.write('new ') + } + g.write('${typ}(') + } + g.expr(it.expr) + if typ == 'string' && !(it.expr is ast.StringLiteral) { + g.write('.toString()') + } + if !is_literal { + g.write(')') + } +} \ No newline at end of file diff --git a/vlib/v/gen/js/tests/array.v b/vlib/v/gen/js/tests/array.v index 0c08a31719..66fa87dbe7 100644 --- a/vlib/v/gen/js/tests/array.v +++ b/vlib/v/gen/js/tests/array.v @@ -78,6 +78,8 @@ for i, _ in arr2 { println(i) } println('\n\n4 to 5\t=> ') for _, v in slice3 { println(v) } +println(int(1.5)) + println('\n\n') // map diff --git a/vlib/v/gen/js/tests/js.v b/vlib/v/gen/js/tests/js.v index e927ecd441..0722e9f5d2 100644 --- a/vlib/v/gen/js/tests/js.v +++ b/vlib/v/gen/js/tests/js.v @@ -34,13 +34,16 @@ fn main() { mut a := 1 a *= 2 a += 3 - println(a) // TODO: Handle string interpolation + println(a) mut b := hl.Aaa{} b.update('an update') println(b) mut c := Foo{hl.Aaa{}} c.a.update('another update') println(c) + println('int(1.5) == "${int(1.5)}"') + d := int(10) + f32(127) + println('typeof (int + f32) == "${typeof(d)}"') _ = 'done' { _ = 'block' @@ -52,7 +55,7 @@ fn main() { await := '$super: $debugger' mut finally := 'implemented' println('$await $finally') - dun := i_am_a_const * 20 + dun := i_am_a_const * 20 + 2 dunn := hl.hello // External constant _ = hl1.nested() for i := 0; i < 10; i++ { diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index 85e9fa79bf..d7514759d6 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -213,7 +213,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl { if p.tok.kind == .name { // TODO high order fn name = if language == .js { p.check_js_name() } else { p.check_name() } - if language == .v && !p.pref.translated && util.contains_capital(name) { + if language == .v && !p.pref.translated && util.contains_capital(name) && p.mod != 'builtin' { p.error('function names cannot contain uppercase letters, use snake_case instead') return ast.FnDecl{} }