v2 / vlib / v / gen / js / js.v
4236 lines · 4010 sloc · 103.0 KB · 7f2f3ef0a6cf004e4920ccfdeb3db53b7e188846
Raw
1module js
2
3import strings
4import v.ast
5import v.token
6import v.pref
7import v.util
8import v.depgraph
9import encoding.base64
10import v.gen.js.sourcemap
11
12// https://ecma-international.org/ecma-262/#sec-reserved-words
13const js_reserved = ['await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger',
14 'default', 'delete', 'do', 'else', 'enum', 'export', 'extends', 'finally', 'for', 'function',
15 'if', 'implements', 'import', 'in', 'instanceof', 'interface', 'let', 'new', 'package', 'private',
16 'protected', 'public', 'return', 'static', 'super', 'switch', 'this', 'throw', 'try', 'typeof',
17 'var', 'void', 'while', 'with', 'yield', 'Number', 'String', 'Boolean', 'Array', 'Map',
18 'document', 'Promise']
19// used to generate type structs
20const v_types = ['i8', 'i16', 'i32', 'int', 'i64', 'u8', 'u16', 'u32', 'u64', 'f32', 'f64',
21 'int_literal', 'float_literal', 'bool', 'string', 'map', 'array', 'rune', 'char', 'any',
22 'voidptr']
23const shallow_equatables = [ast.Kind.i8, .i16, .i32, .int, .i64, .u8, .u16, .u32, .u64, .f32, .f64,
24 .int_literal, .float_literal, .bool, .string, .char]
25const option_name = '_option'
26
27struct SourcemapHelper {
28 src_path string
29 src_line u32
30 ns_pos u32
31}
32
33struct Namespace {
34 name string
35mut:
36 pub_vars []string
37 imports map[string]string
38 indent int
39 methods map[string][]ast.FnDecl
40 sourcemap_helper []SourcemapHelper
41}
42
43@[heap]
44struct JsGen {
45 pref &pref.Preferences
46mut:
47 table &ast.Table = unsafe { nil }
48 definitions strings.Builder
49 ns &Namespace = unsafe { nil }
50 namespaces map[string]&Namespace
51 doc &JsDoc = unsafe { nil }
52 enable_doc bool
53 file &ast.File = unsafe { nil }
54 tmp_count int
55 inside_ternary bool
56 inside_or bool
57 inside_loop bool
58 inside_left_shift bool
59 inside_map_set bool // map.set(key, value)
60 inside_builtin bool
61 inside_if_option bool
62 generated_builtin bool
63 generated_autostr_helpers bool
64 inside_def_typ_decl bool
65 is_test bool
66 stmt_start_pos int
67 defer_stmts []ast.DeferStmt
68 fn_decl &ast.FnDecl = unsafe { nil } // pointer to the FnDecl we are currently inside otherwise 0
69 generated_str_fns []StrType
70 str_types []StrType // types that need automatic str() generation
71 copy_types []StrType // types that need to be deep copied
72 generated_copy_fns []StrType
73 array_fn_definitions []string // array equality functions that have been defined
74 map_fn_definitions []string // map equality functions that have been defined
75 struct_fn_definitions []string // struct equality functions that have been defined
76 sumtype_fn_definitions []string // sumtype equality functions that have been defined
77 alias_fn_definitions []string // alias equality functions that have been defined
78 auto_fn_definitions []string // auto generated functions definition list
79 anon_fn_definitions []string // anon generated functions definition list
80 copy_fn_definitions []string
81 method_fn_decls map[string][]ast.FnDecl
82 builtin_fns []string // Functions defined in `builtin`
83 empty_line bool
84 cast_stack []ast.Type
85 call_stack []ast.CallExpr
86 is_vlines_enabled bool // is it safe to generate #line directives when -g is passed
87 sourcemap &sourcemap.SourceMap = unsafe { nil } // maps lines in generated javascrip file to original source files and line
88 comptime_var_type_map map[string]ast.Type
89 defer_ifdef string
90 cur_concrete_types []ast.Type
91 out strings.Builder = strings.new_builder(128)
92 array_sort_fn map[string]bool
93 wasm_export map[string][]string
94 wasm_import map[string][]string
95 init_global map[string]map[string]ast.Expr // initializers for constants or globals, should be invoked before module init.
96}
97
98fn (mut g JsGen) write_tests_definitions() {
99 g.definitions.writeln('globalThis.g_test_oks = 0;')
100 g.definitions.writeln('globalThis.g_test_fails = 0;')
101}
102
103pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) string {
104 mut g := &JsGen{
105 definitions: strings.new_builder(100)
106 table: table
107 pref: pref_
108 empty_line: true
109 enable_doc: true
110 }
111 g.doc = new_jsdoc(g)
112 // TODO: Add '[-no]-jsdoc' flag
113 if g.pref.is_prod {
114 g.enable_doc = false
115 g.is_vlines_enabled = false
116 }
117 g.init()
118 mut graph := depgraph.new_dep_graph()
119 if g.pref.sourcemap {
120 mut sg := sourcemap.generate_empty_map()
121 g.sourcemap = sg.add_map('', '', g.pref.sourcemap_src_included, 0, 0)
122 }
123 mut tests_inited := false
124
125 // Get class methods
126 for file in files {
127 g.file = file
128 g.enter_namespace(g.file.mod.name)
129 g.is_test = g.pref.is_test
130 g.find_class_methods(file.stmts)
131 g.escape_namespace()
132 }
133 for file in files {
134 g.file = file
135 g.enter_namespace(g.file.mod.name)
136 if g.enable_doc {
137 g.writeln('/** @namespace ${file.mod.name} */')
138 }
139 g.is_test = g.pref.is_test
140 // store imports
141 mut imports := []string{}
142 for imp in g.file.imports {
143 imports << imp.mod
144 }
145
146 graph.add(g.file.mod.name, imports)
147 // builtin types
148 if g.file.mod.name == 'builtin' && !g.generated_builtin {
149 g.gen_nil_const()
150 g.gen_builtin_type_defs()
151 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(); } }); ')
152 g.writeln('Object.defineProperty(map.prototype,"len", { get: function() {return new int(this.length);}, set: function(l) { } }); ')
153 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(); } }); ')
154 g.generated_builtin = true
155 }
156 if g.is_test && !tests_inited {
157 g.write_tests_definitions()
158 tests_inited = true
159 }
160 g.stmts(file.stmts)
161 // store the current namespace
162 g.escape_namespace()
163 }
164 for i := 0; i < g.str_types.len; i++ {
165 g.final_gen_str(g.str_types[i])
166 }
167 for i := 0; i < g.copy_types.len; i++ {
168 g.final_gen_copy(g.copy_types[i])
169 }
170 if g.pref.is_test {
171 g.gen_js_main_for_tests()
172 }
173 g.enter_namespace('main')
174 // generate JS methods for interface methods
175 for iface_name, iface_types in g.table.iface_types {
176 iface := g.table.find_sym(iface_name) or { panic('unreachable: interface must exist') }
177 for ty in iface_types {
178 sym := g.table.sym(ty)
179 for method in iface.methods {
180 if method.name == 'toString' {
181 continue
182 }
183 p_sym := g.table.sym(ty)
184
185 mname := if p_sym.has_method(method.name) {
186 g.js_name(p_sym.name) + '_' + method.name
187 } else {
188 g.js_name(iface_name) + '_' + method.name
189 }
190 g.write('${g.js_name(sym.name)}.prototype.${method.name} = function(')
191 for i, param in method.params {
192 if i == 0 {
193 continue
194 }
195 g.write('${g.js_name(param.name)}')
196 if i != method.params.len - 1 {
197 g.write(',')
198 }
199 }
200 g.writeln(') {')
201 g.inc_indent()
202 g.write('return ${mname}(')
203 for i, param in method.params {
204 if i == 0 {
205 g.write('this')
206 } else {
207 g.write('${g.js_name(param.name)}')
208 }
209 if i != method.params.len - 1 {
210 g.write(',')
211 }
212 }
213 g.writeln(')')
214 g.dec_indent()
215 g.writeln('}')
216 }
217 }
218 }
219
220 for mod_name in g.table.modules {
221 g.writeln('// Initializations for module ${mod_name}')
222 for global, expr in g.init_global[mod_name] {
223 g.write('${global} = ')
224 g.expr(expr)
225 g.writeln(';')
226 }
227 init_fn_name := '${mod_name}.init'
228 if initfn := g.table.find_fn(init_fn_name) {
229 if initfn.return_type == ast.void_type && initfn.params.len == 0 {
230 mod_c_name := util.no_dots(mod_name)
231 init_fn_c_name := '${mod_c_name}__init'
232 g.writeln('${init_fn_c_name}();')
233 }
234 }
235 }
236
237 if !g.pref.is_shared {
238 if g.pref.output_es5 {
239 g.write('js_main();')
240 } else {
241 g.write('loadRoutine().then(_ => js_main());')
242 }
243 }
244 g.escape_namespace()
245 // resolve imports
246 // deps_resolved := graph.resolve()
247 // nodes := deps_resolved.nodes
248
249 mut out := g.definitions.str()
250 if !g.pref.output_es5 {
251 out += '\nlet wasmExportObject;\n'
252
253 out += 'const loadRoutine = async () => {\n'
254 for mod, functions in g.wasm_import {
255 if g.pref.backend == .js_browser {
256 out += '\nawait fetch("${mod}").then(resp => resp.arrayBuffer()).then(bytes => '
257 out += 'WebAssembly.instantiate(bytes,'
258 exports := g.wasm_export[mod]
259 out += '{ imports: { \n'
260 for i, exp in exports {
261 out += g.js_name(exp) + ':' + '\$wasm' + g.js_name(exp)
262 if i != exports.len - 1 {
263 out += ',\n'
264 }
265 }
266 out += '}})).then(obj => wasmExportObject = obj.instance.exports);\n'
267 for fun in functions {
268 out += 'globalThis.${g.js_name(fun)} = wasmExportObject.${g.js_name(fun)};\n'
269 }
270 } else {
271 verror('WebAssembly export is supported only for browser backend at the moment')
272 }
273 }
274 out += '}\n'
275 }
276 // equality check for js objects
277 // TODO: Fix msvc bug that's preventing $embed_file('fast_deep_equal.js')
278 // unsafe {
279 // mut eq_fn := $embed_file('fast_deep_equal.js')
280 // out += eq_fn.data().vstring()
281 //}
282 out += fast_deep_eq_fn
283 /*
284 if pref.is_shared {
285 // Export, through CommonJS, the module of the entry file if `-shared` was passed
286 export := nodes.last().name
287 out += 'if (typeof module === "object" && module.exports) module.exports = ${export};\n'
288 }*/
289 out += '\n'
290
291 out += g.out.str()
292 /*
293 TODO(playX): Again add support for these doc comments
294 for node in nodes {
295 name := g.js_name(node.name).replace('.', '_')
296 if g.enable_doc {
297 out += '/** @namespace ${name} */\n'
298 }
299 // out += 'const ${name} = (function ('
300 mut namespace := g.namespaces[node.name]
301
302
303 if g.pref.sourcemap {
304 // calculate current output start line
305 mut current_line := u32(out.count('\n') + 1)
306 mut sm_pos := u32(0)
307 for sourcemap_ns_entry in namespace.sourcemap_helper {
308 // calculate final generated location in output based on position
309 current_segment := g.out.substr(int(sm_pos), int(sourcemap_ns_entry.ns_pos))
310 current_line += u32(current_segment.count('\n'))
311 mut current_column := u32(0)
312 last_nl_pos := current_segment.last_index_u8(`\n`)
313 if last_nl_pos != -1 {
314 current_column = u32(current_segment.len - last_nl_pos - 1)
315 }
316 g.sourcemap.add_mapping(sourcemap_ns_entry.src_path, sourcemap.SourcePosition{
317 source_line: sourcemap_ns_entry.src_line
318 source_column: 0 // sourcemap_ns_entry.src_column
319 }, current_line, current_column, '')
320 sm_pos = sourcemap_ns_entry.ns_pos
321 }
322 }
323
324
325 // public scope
326 out += '\n'
327 }*/
328
329 if g.pref.sourcemap {
330 out += g.create_sourcemap()
331 }
332
333 return out
334}
335
336fn (g JsGen) create_sourcemap() string {
337 mut sm := g.sourcemap
338 mut out := '\n//# sourceMappingURL=data:application/json;base64,'
339 out += base64.encode(sm.to_json().str().bytes())
340 out += '\n'
341
342 return out
343}
344
345pub fn (mut g JsGen) gen_js_main_for_tests() {
346 g.enter_namespace('main')
347 if !g.pref.output_es5 {
348 g.write('async ')
349 }
350 g.writeln('function js_main() { ')
351 g.inc_indent()
352 mut before_each_fn := ''
353 mut after_each_fn := ''
354 for _, f in g.table.fns {
355 short_tname := if f.name.contains('.') { f.name.all_after_last('.') } else { f.name }
356 if !f.is_test {
357 continue
358 }
359 if short_tname == 'before_each' {
360 before_each_fn = g.js_name(f.name)
361 continue
362 }
363 if short_tname == 'after_each' {
364 after_each_fn = g.js_name(f.name)
365 }
366 }
367 mut all_tfuncs := []string{}
368 for tname in g.get_all_test_function_names() {
369 all_tfuncs << tname
370 }
371
372 g.writeln('')
373 g.writeln('globalThis.VTEST=1')
374 if g.pref.is_stats {
375 g.writeln('let bt = main__start_testing(new int(${all_tfuncs.len}), new string("${g.pref.path}"))')
376 }
377 for i, tname in all_tfuncs {
378 tcname := g.js_name(tname)
379 short_tname := if tname.contains('.') { tname.all_after_last('.') } else { tname }
380 is_test_fn := short_tname.starts_with('test_')
381
382 if g.pref.is_stats {
383 g.writeln('main__BenchedTests_testing_step_start(bt,new string("${tcname}"))')
384 g.writeln('try {')
385 }
386 if is_test_fn && before_each_fn != '' {
387 g.writeln('let before_each_res_${i} = ${before_each_fn}(); if (before_each_res_${i} instanceof Promise) { await before_each_res_${i}; }')
388 }
389 g.writeln('let res_${i} = ${tcname}(); if (res_${i} instanceof Promise) { await res_${i}; }')
390 if is_test_fn && after_each_fn != '' {
391 g.writeln('let after_each_res_${i} = ${after_each_fn}(); if (after_each_res_${i} instanceof Promise) { await after_each_res_${i}; }')
392 }
393 if g.pref.is_stats {
394 g.writeln('} finally {')
395 g.writeln('main__BenchedTests_testing_step_end(bt);')
396 g.writeln('}')
397 }
398 }
399
400 g.writeln('')
401 if g.pref.is_stats {
402 g.writeln('main__BenchedTests_end_testing(bt);')
403 }
404 g.dec_indent()
405 g.writeln('}')
406 g.escape_namespace()
407}
408
409fn (g &JsGen) get_all_test_function_names() []string {
410 mut tfuncs := []string{}
411 mut tsuite_begin := ''
412 mut tsuite_end := ''
413 for _, f in g.table.fns {
414 if !f.is_test {
415 continue
416 }
417 if f.name.ends_with('.testsuite_begin') {
418 tsuite_begin = f.name
419 continue
420 }
421 if f.name.contains('.test_') {
422 tfuncs << f.name
423 continue
424 }
425 if f.name.ends_with('.testsuite_end') {
426 tsuite_end = f.name
427 continue
428 }
429 }
430 mut all_tfuncs := []string{}
431 if tsuite_begin != '' {
432 all_tfuncs << tsuite_begin
433 }
434 all_tfuncs << tfuncs
435 if tsuite_end != '' {
436 all_tfuncs << tsuite_end
437 }
438 return all_tfuncs
439}
440
441fn (mut g JsGen) write_js_default_value(typ ast.Type) {
442 sym := g.table.sym(g.table.unaliased_type(typ))
443 if sym.kind != .array_fixed {
444 g.write(g.to_js_typ_val(typ))
445 return
446 }
447 info := sym.info as ast.ArrayFixed
448 tmp := g.new_tmp_var()
449 idx := g.new_tmp_var()
450 g.writeln('(function() {')
451 g.inc_indent()
452 g.writeln('const ${tmp} = [];')
453 g.writeln('for (let ${idx} = 0; ${idx} < ${info.size}; ${idx}++) {')
454 g.inc_indent()
455 g.write('${tmp}.push(')
456 g.write_js_default_value(info.elem_type)
457 g.writeln(');')
458 g.dec_indent()
459 g.writeln('}')
460 g.writeln('return new array(new array_buffer({arr: ${tmp}, len: new int(${info.size}), cap: new int(${info.size})}));')
461 g.dec_indent()
462 g.write('})()')
463}
464
465pub fn (mut g JsGen) enter_namespace(name string) {
466 unsafe {
467 if g.namespaces[name] == 0 {
468 // create a new namespace
469 ns := &Namespace{
470 name: name
471 }
472 g.namespaces[name] = ns
473 g.ns = ns
474 } else {
475 g.ns = g.namespaces[name]
476 }
477 }
478 g.inside_builtin = name == 'builtin'
479}
480
481pub fn (mut g JsGen) escape_namespace() {
482 g.ns = &Namespace(unsafe { nil })
483 g.inside_builtin = false
484}
485
486pub fn (mut g JsGen) push_pub_var(s string) {
487 g.ns.pub_vars << g.js_name(s)
488}
489
490pub fn (mut g JsGen) find_class_methods(stmts []ast.Stmt) {
491 for stmt in stmts {
492 match stmt {
493 ast.FnDecl {
494 if stmt.is_method {
495 // Found struct method, store it to be generated along with the class.
496 mut class_name := g.table.get_type_name(stmt.receiver.typ)
497 // Workaround until `map[key] << val` works.
498 mut arr := unsafe { g.method_fn_decls[class_name] }
499 arr << stmt
500 g.method_fn_decls[class_name] = arr
501 }
502 }
503 else {}
504 }
505 }
506}
507
508pub fn (mut g JsGen) init() {
509 g.definitions.writeln('// Generated by the V compiler\n')
510 // g.definitions.writeln('"use strict";')
511 g.definitions.writeln('')
512 g.definitions.writeln('var \$global = (new Function("return this"))();')
513 if g.pref.output_es5 {
514 g.definitions.writeln('globalThis = \$global;')
515 }
516 g.definitions.writeln('let \$ref_id_gen = 0;')
517 g.definitions.writeln('function \$ref(value) { if (value instanceof \$ref) { return value; } this.val = value; } ')
518 g.definitions.writeln('\$ref.prototype.valueOf = function() { return this.val; } ')
519 g.definitions.writeln('\$ref.prototype.\$toJS = function() {')
520 g.definitions.writeln('\tconst value = this.val;')
521 g.definitions.writeln('\tif (value === null || value === undefined) { return value; }')
522 g.definitions.writeln('\tif (typeof value === "object" || typeof value === "function") {')
523 g.definitions.writeln('\t\tif (!Object.prototype.hasOwnProperty.call(value, "__v_ref_id")) {')
524 g.definitions.writeln('\t\t\tObject.defineProperty(value, "__v_ref_id", { value: ++\$ref_id_gen, enumerable: false, configurable: false, writable: false });')
525 g.definitions.writeln('\t\t}')
526 g.definitions.writeln('\t\treturn "__v_ref_" + value.__v_ref_id;')
527 g.definitions.writeln('\t}')
528 g.definitions.writeln('\treturn value;')
529 g.definitions.writeln('} ')
530 g.definitions.writeln('function \$ref_index(value, parent, index) { let ref = new \$ref(value); ref._v_array = parent; ref._v_index = index; return ref; } ')
531 if g.pref.backend != .js_node {
532 g.definitions.writeln('const \$process = {')
533 g.definitions.writeln(' arch: "js",')
534 if g.pref.backend == .js_freestanding {
535 g.definitions.writeln(' platform: "freestanding",')
536 } else {
537 g.definitions.writeln(' platform: "browser",')
538 }
539 g.definitions.writeln(' cwd: function() { return "" }')
540 g.definitions.writeln('}')
541
542 g.definitions.writeln('const \$os = {')
543 g.definitions.writeln(' endianness: "LE",')
544 g.definitions.writeln('}')
545 } else {
546 g.definitions.writeln('const \$os = require("os");')
547 g.definitions.writeln('const \$process = process;')
548 g.definitions.writeln('if (typeof print === "undefined") { globalThis.print = function() {}; }')
549 }
550 g.definitions.writeln('function checkDefine(key) {')
551 g.definitions.writeln('\tif (globalThis.hasOwnProperty(key)) { return !!globalThis[key]; } return false;')
552 g.definitions.writeln('}')
553
554 g.definitions.writeln('function BreakException() {}')
555 g.definitions.writeln('function ContinueException() {}')
556 g.definitions.writeln('function ReturnException(val) { this.val = val; }')
557}
558
559@[noreturn]
560fn verror(msg string) {
561 eprintln('jsgen error: ${msg}')
562 exit(1)
563}
564
565@[inline]
566pub fn (mut g JsGen) gen_indent() {
567 if g.ns.indent > 0 && g.empty_line {
568 g.out.write_string(util.tabs(g.ns.indent))
569 }
570 g.empty_line = false
571}
572
573@[inline]
574pub fn (mut g JsGen) inc_indent() {
575 g.ns.indent++
576}
577
578@[inline]
579pub fn (mut g JsGen) dec_indent() {
580 g.ns.indent--
581}
582
583@[inline]
584pub fn (mut g JsGen) write(s string) {
585 if unsafe { g.ns == 0 } {
586 verror('g.write: not in a namespace')
587 }
588 g.gen_indent()
589 g.out.write_string(s)
590}
591
592@[inline]
593pub fn (mut g JsGen) writeln(s string) {
594 if unsafe { g.ns == 0 } {
595 verror('g.writeln: not in a namespace')
596 }
597 g.gen_indent()
598 g.out.writeln(s)
599 g.empty_line = true
600}
601
602@[inline]
603pub fn (mut g JsGen) new_tmp_var() string {
604 g.tmp_count++
605 return '_tmp${g.tmp_count}'
606}
607
608// 'mod1.mod2.fn' => 'mod1.mod2'
609// 'fn' => ''
610@[inline]
611fn get_ns(s string) string {
612 idx := s.last_index_u8(`.`)
613 if idx == -1 {
614 return ''
615 }
616 return s.substr(0, idx)
617}
618
619fn (mut g JsGen) get_alias(name string) string {
620 ns := get_ns(name)
621 if ns == '' {
622 return name
623 }
624 alias := g.ns.imports[ns]
625 if alias == '' {
626 return name
627 }
628 return alias + '.' + name.split('.').last()
629}
630
631fn (mut g JsGen) js_name(name_ string) string {
632 mut name := name_
633 if name.starts_with('JS.') {
634 name = name[3..]
635 return name
636 }
637 name = name_.replace('[]', '').replace('.', '__')
638 if name in js_reserved {
639 return '_v_${name}'
640 }
641 return name
642}
643
644fn (mut g JsGen) stmts(stmts []ast.Stmt) {
645 for stmt in stmts {
646 g.stmt(stmt)
647 }
648}
649
650@[inline]
651fn (mut g JsGen) write_v_source_line_info(pos token.Pos) {
652 // g.inside_ternary == 0 &&
653 if g.pref.sourcemap {
654 g.ns.sourcemap_helper << SourcemapHelper{
655 src_path: util.vlines_escape_path(g.file.path, g.pref.ccompiler)
656 src_line: u32(pos.line_nr + 1)
657 ns_pos: u32(g.out.len)
658 }
659 }
660 if g.pref.is_vlines && g.is_vlines_enabled {
661 g.write(' /* ${pos.line_nr + 1} ${g.out.len} */ ')
662 }
663}
664
665fn (mut g JsGen) gen_exported_global_alias(export_name string, global_name string, is_enumerable bool) {
666 g.writeln('Object.defineProperty(globalThis,"${export_name}", {')
667 g.writeln('\tconfigurable: false,')
668 g.writeln('\tenumerable: ${is_enumerable},')
669 g.writeln('\tget: function() { return \$global["${global_name}"]; },')
670 g.writeln('\tset: function(value) { \$global["${global_name}"] = value; }')
671 g.writeln('\t}); // exported global')
672}
673
674fn (mut g JsGen) gen_global_decl(node ast.GlobalDecl) {
675 is_enumerable := g.pref.build_mode != .build_module
676 export_name := if export_attr := node.attrs.find_first('export') {
677 if export_attr.arg.len > 0 { export_attr.arg } else { '' }
678 } else {
679 ''
680 }
681 for field in node.fields {
682 if field.has_expr {
683 tmp_var := g.new_tmp_var()
684 g.write('const ${tmp_var} = ')
685 g.expr(field.expr)
686 g.writeln(';')
687 g.writeln('Object.defineProperty(\$global,"${field.name}", {
688 configurable: false,
689 enumerable: ${is_enumerable},
690 writable: true,
691 value: ${tmp_var}
692 }
693 ); // global')
694 } else {
695 // TODO(playXE): Initialize with default value of type
696
697 if field.typ.is_ptr() {
698 g.writeln('Object.defineProperty(\$global,"${field.name}", {
699 configurable: false,
700 enumerable: ${is_enumerable},
701 writable: true,
702 value: new \$ref({})
703 }
704 ); // global')
705 } else {
706 g.writeln('Object.defineProperty(\$global,"${field.name}", {
707 configurable: false,
708 enumerable: ${is_enumerable},
709 writable: true,
710 value: {}
711 }
712 ); // global')
713 }
714 }
715 if field.is_exported {
716 final_export_name := if export_name.len > 0 { export_name } else { field.name }
717 if final_export_name != field.name {
718 g.gen_exported_global_alias(final_export_name, field.name, is_enumerable)
719 }
720 }
721 }
722}
723
724fn (mut g JsGen) gen_alias_type_decl(node ast.AliasTypeDecl) {
725 name := if g.ns.name == 'builtin' { node.name } else { '${g.js_name(g.ns.name)}__${node.name}' }
726 g.writeln('function ${name}(val) { return val; }')
727}
728
729fn (mut g JsGen) stmt_no_semi(node_ ast.Stmt) {
730 g.stmt_start_pos = g.out.len
731 mut node := unsafe { node_ }
732 match mut node {
733 ast.EmptyStmt {}
734 ast.AsmStmt {
735 panic('inline asm is not supported by js')
736 }
737 ast.AssertStmt {
738 g.write_v_source_line_info(node.pos)
739 g.gen_assert_stmt(mut node)
740 }
741 ast.AssignStmt {
742 g.write_v_source_line_info(node.pos)
743 g.gen_assign_stmt(node, false)
744 }
745 ast.Block {
746 g.write_v_source_line_info(node.pos)
747 g.gen_block(node)
748 g.writeln('')
749 }
750 ast.BranchStmt {
751 g.write_v_source_line_info(node.pos)
752 g.gen_branch_stmt(node)
753 }
754 ast.ComptimeFor {}
755 ast.ConstDecl {
756 g.write_v_source_line_info(node.pos)
757 g.gen_const_decl(node)
758 }
759 ast.DebuggerStmt {}
760 ast.DeferStmt {
761 g.defer_stmts << node
762 }
763 ast.EnumDecl {
764 g.write_v_source_line_info(node.pos)
765 g.gen_enum_decl(node)
766 g.writeln('')
767 }
768 ast.ExprStmt {
769 g.write_v_source_line_info(node.pos)
770 g.gen_expr_stmt_no_semi(node)
771 }
772 ast.FnDecl {
773 g.write_v_source_line_info(node.pos)
774
775 g.gen_fn_decl(node)
776 }
777 ast.ForCStmt {
778 g.write_v_source_line_info(node.pos)
779 g.gen_for_c_stmt(node)
780 g.writeln('')
781 }
782 ast.ForInStmt {
783 g.write_v_source_line_info(node.pos)
784 g.gen_for_in_stmt(node)
785 g.writeln('')
786 }
787 ast.ForStmt {
788 g.write_v_source_line_info(node.pos)
789 g.gen_for_stmt(node)
790 g.writeln('')
791 }
792 ast.GlobalDecl {
793 g.write_v_source_line_info(node.pos)
794 g.gen_global_decl(node)
795 g.writeln('')
796 }
797 ast.GotoLabel {
798 g.write_v_source_line_info(node.pos)
799 g.writeln('${g.js_name(node.name)}:')
800 }
801 ast.GotoStmt {
802 // skip: JS has no goto
803 }
804 ast.HashStmt {
805 g.write_v_source_line_info(node.pos)
806 g.gen_hash_stmt(node)
807 }
808 ast.Import {
809 g.ns.imports[node.mod] = node.alias
810 }
811 ast.InterfaceDecl {
812 g.write_v_source_line_info(node.pos)
813 g.gen_interface_decl(node)
814 }
815 ast.Module {
816 // skip: namespacing implemented externally
817 }
818 ast.NodeError {}
819 ast.Return {
820 if g.defer_stmts.len > 0 {
821 g.gen_defer_stmts()
822 }
823 g.gen_return_stmt(node)
824 }
825 ast.SemicolonStmt {
826 g.writeln(';')
827 }
828 ast.SqlStmt {}
829 ast.StructDecl {
830 g.write_v_source_line_info(node.pos)
831 g.gen_struct_decl(node)
832 }
833 ast.TypeDecl {}
834 }
835}
836
837fn (mut g JsGen) stmt(node_ ast.Stmt) {
838 g.stmt_start_pos = g.out.len
839 mut node := unsafe { node_ }
840 match mut node {
841 ast.EmptyStmt {}
842 ast.AsmStmt {
843 panic('inline asm is not supported by js')
844 }
845 ast.AssertStmt {
846 g.write_v_source_line_info(node.pos)
847 g.gen_assert_stmt(mut node)
848 }
849 ast.AssignStmt {
850 g.write_v_source_line_info(node.pos)
851 g.gen_assign_stmt(node, true)
852 }
853 ast.Block {
854 g.write_v_source_line_info(node.pos)
855 g.gen_block(node)
856 g.writeln('')
857 }
858 ast.BranchStmt {
859 g.write_v_source_line_info(node.pos)
860 g.gen_branch_stmt(node)
861 }
862 ast.ComptimeFor {}
863 ast.ConstDecl {
864 g.write_v_source_line_info(node.pos)
865 g.gen_const_decl(node)
866 }
867 ast.DebuggerStmt {}
868 ast.DeferStmt {
869 g.defer_stmts << node
870 }
871 ast.EnumDecl {
872 g.write_v_source_line_info(node.pos)
873 g.gen_enum_decl(node)
874 g.writeln('')
875 }
876 ast.ExprStmt {
877 g.write_v_source_line_info(node.pos)
878 g.gen_expr_stmt(node)
879 }
880 ast.FnDecl {
881 g.write_v_source_line_info(node.pos)
882 g.gen_fn_decl(node)
883 }
884 ast.ForCStmt {
885 g.write_v_source_line_info(node.pos)
886 g.gen_for_c_stmt(node)
887 g.writeln('')
888 }
889 ast.ForInStmt {
890 g.write_v_source_line_info(node.pos)
891 g.gen_for_in_stmt(node)
892 g.writeln('')
893 }
894 ast.ForStmt {
895 g.write_v_source_line_info(node.pos)
896 g.gen_for_stmt(node)
897 g.writeln('')
898 }
899 ast.GlobalDecl {
900 g.write_v_source_line_info(node.pos)
901 g.gen_global_decl(node)
902 g.writeln('')
903 }
904 ast.GotoLabel {
905 g.write_v_source_line_info(node.pos)
906 g.writeln('${g.js_name(node.name)}:')
907 }
908 ast.GotoStmt {
909 // skip: JS has no goto
910 }
911 ast.HashStmt {
912 g.write_v_source_line_info(node.pos)
913 g.gen_hash_stmt(node)
914 }
915 ast.Import {
916 g.ns.imports[node.mod] = node.alias
917 }
918 ast.InterfaceDecl {
919 g.write_v_source_line_info(node.pos)
920 g.gen_interface_decl(node)
921 }
922 ast.Module {
923 // skip: namespacing implemented externally
924 }
925 ast.NodeError {}
926 ast.Return {
927 if g.defer_stmts.len > 0 {
928 g.gen_defer_stmts()
929 }
930 g.gen_return_stmt(node)
931 }
932 ast.SemicolonStmt {
933 g.writeln(';')
934 }
935 ast.SqlStmt {}
936 ast.StructDecl {
937 g.write_v_source_line_info(node.pos)
938 g.gen_struct_decl(node)
939 }
940 ast.TypeDecl {
941 match mut node {
942 ast.AliasTypeDecl {
943 g.gen_alias_type_decl(node)
944 }
945 else {}
946 }
947 }
948 }
949}
950
951fn (mut g JsGen) expr(node_ ast.Expr) {
952 // Note: please keep the type names in the match here in alphabetical order:
953 mut node := unsafe { node_ }
954 match mut node {
955 ast.ComptimeType {
956 verror('not yet implemented')
957 }
958 ast.EmptyExpr {}
959 ast.AnonFn {
960 g.gen_anon_fn(mut node)
961 }
962 ast.ArrayDecompose {}
963 ast.ArrayInit {
964 g.gen_array_init_expr(node)
965 }
966 ast.AsCast {
967 // TODO: Is jsdoc needed here for TS support?
968 g.expr(node.expr)
969 }
970 ast.Assoc {
971 // TODO
972 }
973 ast.AtExpr {
974 g.write('"${node.val}"')
975 }
976 ast.BoolLiteral {
977 g.write('new bool(')
978 if node.val == true {
979 g.write('true')
980 } else {
981 g.write('false')
982 }
983 g.write(')')
984 }
985 ast.CallExpr {
986 g.gen_call_expr(node)
987 }
988 ast.CastExpr {
989 g.gen_type_cast_expr(node)
990 }
991 ast.ChanInit {
992 // TODO
993 }
994 ast.CharLiteral {
995 g.write("new rune('${node.val}')")
996 }
997 ast.Comment {}
998 ast.ComptimeCall {
999 // TODO
1000 }
1001 ast.ComptimeSelector {
1002 // TODO
1003 }
1004 ast.ConcatExpr {
1005 // TODO
1006 }
1007 ast.CTempVar {
1008 g.write('${node.name}')
1009 }
1010 ast.DumpExpr {
1011 g.write('/* ast.DumpExpr: ${node.expr} */')
1012 }
1013 ast.EnumVal {
1014 sym := g.table.sym(node.typ)
1015 styp := g.js_name(sym.name)
1016 g.write('${styp}.${node.val}')
1017 }
1018 ast.FloatLiteral {
1019 g.gen_float_literal_expr(node)
1020 }
1021 ast.GoExpr {
1022 g.gen_go_expr(node)
1023 }
1024 ast.SpawnExpr {
1025 g.gen_spawn_expr(node)
1026 }
1027 ast.Ident {
1028 g.gen_ident(node)
1029 }
1030 ast.IfExpr {
1031 g.gen_if_expr(node)
1032 }
1033 ast.IfGuardExpr {
1034 // TODO: no options yet
1035 }
1036 ast.IndexExpr {
1037 g.gen_index_expr(node)
1038 }
1039 ast.InfixExpr {
1040 g.infix_expr(node)
1041 }
1042 ast.IntegerLiteral {
1043 g.gen_integer_literal_expr(node)
1044 }
1045 ast.LambdaExpr {
1046 eprintln('> TODO: implement short lambda expressions in the JS backend')
1047 }
1048 ast.Likely {
1049 g.write('(')
1050 g.expr(node.expr)
1051 g.write(')')
1052 }
1053 ast.LockExpr {
1054 g.gen_lock_expr(node)
1055 }
1056 ast.Nil {
1057 g.write('nil__')
1058 }
1059 ast.NodeError {}
1060 ast.None {
1061 g.write('none__')
1062 }
1063 ast.MapInit {
1064 g.gen_map_init_expr(node)
1065 }
1066 ast.MatchExpr {
1067 g.match_expr(node)
1068 }
1069 ast.OrExpr {
1070 // TODO
1071 }
1072 ast.ParExpr {
1073 g.write('(')
1074 g.expr(node.expr)
1075 g.write(')')
1076 }
1077 ast.PostfixExpr {
1078 // match node.expr {
1079 // ast.IndexExpr {
1080 // g.gen_postfix_index_expr(node.expr,node.op)
1081 // } else {
1082 g.expr(node.expr)
1083 if node.op in [.inc, .dec] {
1084 g.write('.val ${node.op}')
1085 } else {
1086 g.write(node.op.str())
1087 }
1088 // }
1089 // }
1090 }
1091 ast.PrefixExpr {
1092 if node.op in [.amp, .mul] {
1093 if node.op == .amp {
1094 mut is_array_index_ref := false
1095 mut right := node.right
1096 if mut right is ast.IndexExpr {
1097 mut parent_type := right.left_type
1098 if parent_type.is_ptr() {
1099 parent_type = parent_type.deref()
1100 }
1101 parent_sym := g.table.final_sym(parent_type)
1102 if parent_sym.kind in [.array, .array_fixed] && !right.is_map {
1103 is_array_index_ref = true
1104 g.write('\$ref_index(')
1105 g.expr(right)
1106 g.write(', ')
1107 g.expr(right.left)
1108 if right.left_type.is_ptr() {
1109 g.write('.valueOf()')
1110 }
1111 g.write(', ')
1112 g.expr(right.index)
1113 g.write(')')
1114 }
1115 }
1116 if !is_array_index_ref {
1117 // kind of weird way to handle references but it allows us to access type methods easily.
1118 g.write('new \$ref(')
1119 g.expr(node.right)
1120 g.write(')')
1121 }
1122 } else {
1123 g.write('(')
1124 g.expr(node.right)
1125 g.write(').valueOf()')
1126 }
1127 } else {
1128 g.write(node.op.str())
1129 g.expr(node.right)
1130 g.write('.val ')
1131 }
1132 }
1133 ast.RangeExpr {
1134 // Only used in IndexExpr, requires index type info
1135 }
1136 ast.SelectExpr {
1137 // TODO: to be implemented
1138 }
1139 ast.SelectorExpr {
1140 g.gen_selector_expr(node)
1141 }
1142 ast.SizeOf, ast.IsRefType {
1143 // TODO
1144 }
1145 ast.OffsetOf {
1146 // TODO
1147 }
1148 ast.SqlExpr {
1149 // TODO
1150 }
1151 ast.SqlQueryDataExpr {
1152 // TODO
1153 }
1154 ast.StringInterLiteral {
1155 g.gen_string_inter_literal(node)
1156 }
1157 ast.StringLiteral {
1158 g.gen_string_literal(node)
1159 }
1160 ast.StructInit {
1161 if node.unresolved {
1162 resolved := g.table.resolve_init(node, g.unwrap_generic(node.typ))
1163 g.expr(resolved)
1164 } else {
1165 g.gen_struct_init(node)
1166 }
1167 }
1168 ast.TypeNode {
1169 typ := g.unwrap_generic(node.typ)
1170 sym := g.table.sym(typ)
1171
1172 g.write('${g.js_name(sym.name)}')
1173 }
1174 ast.TypeOf {
1175 g.gen_typeof_expr(node)
1176 // TODO: Should this print the V type or the JS type?
1177 }
1178 ast.UnsafeExpr {
1179 g.expr(node.expr)
1180 }
1181 }
1182}
1183
1184struct UnsupportedAssertCtempTransform {
1185 Error
1186}
1187
1188const unsupported_ctemp_assert_transform = IError(UnsupportedAssertCtempTransform{})
1189
1190fn (mut g JsGen) assert_subexpression_to_ctemp(expr ast.Expr, expr_type ast.Type) !ast.Expr {
1191 match expr {
1192 ast.CallExpr {
1193 return g.new_ctemp_var_then_gen(expr, expr_type)
1194 }
1195 ast.ParExpr {
1196 if expr.expr is ast.CallExpr {
1197 return g.new_ctemp_var_then_gen(expr.expr, expr_type)
1198 }
1199 }
1200 ast.SelectorExpr {
1201 if expr.expr is ast.CallExpr {
1202 sym := g.table.final_sym(g.unwrap_generic(expr.expr.return_type))
1203 if sym.kind == .struct {
1204 if (sym.info as ast.Struct).is_union {
1205 return unsupported_ctemp_assert_transform
1206 }
1207 }
1208 return g.new_ctemp_var_then_gen(expr, expr_type)
1209 }
1210 }
1211 else {}
1212 }
1213
1214 return unsupported_ctemp_assert_transform
1215}
1216
1217fn (mut g JsGen) new_ctemp_var(expr ast.Expr, expr_type ast.Type) ast.CTempVar {
1218 return ast.CTempVar{
1219 name: g.new_tmp_var()
1220 typ: expr_type
1221 is_ptr: expr_type.is_ptr()
1222 orig: expr
1223 }
1224}
1225
1226fn (mut g JsGen) new_ctemp_var_then_gen(expr ast.Expr, expr_type ast.Type) ast.CTempVar {
1227 x := g.new_ctemp_var(expr, expr_type)
1228 g.gen_ctemp_var(x)
1229 return x
1230}
1231
1232fn (mut g JsGen) gen_ctemp_var(tvar ast.CTempVar) {
1233 g.write('let ${tvar.name} = ')
1234 g.expr(tvar.orig)
1235 g.writeln(';')
1236}
1237
1238fn (mut g JsGen) gen_assert_metainfo(node ast.AssertStmt) string {
1239 mod_path := g.file.path
1240 fn_name := if g.fn_decl == unsafe { nil } || g.fn_decl.is_anon { 'anon' } else { g.fn_decl.name }
1241 line_nr := node.pos.line_nr
1242 src := node.expr.str()
1243 metaname := 'v_assert_meta_info_${g.new_tmp_var()}'
1244 g.writeln('let ${metaname} = {}')
1245 g.writeln('${metaname}.fpath = new string("${mod_path}");')
1246 g.writeln('${metaname}.line_nr = new int("${line_nr}")')
1247 g.writeln('${metaname}.fn_name = new string("${fn_name}")')
1248 metasrc := src
1249 g.writeln('${metaname}.src = "${metasrc}"')
1250
1251 match node.expr {
1252 ast.InfixExpr {
1253 expr_op_str := node.expr.op.str()
1254 expr_left_str := node.expr.left.str()
1255 expr_right_str := node.expr.right.str()
1256 g.writeln('\t${metaname}.op = new string("${expr_op_str}");')
1257 g.writeln('\t${metaname}.llabel = new string("${expr_left_str}");')
1258 g.writeln('\t${metaname}.rlabel = new string("${expr_right_str}");')
1259 g.write('\t${metaname}.lvalue = ')
1260 g.gen_assert_single_expr(node.expr.left, node.expr.left_type)
1261 g.writeln(';')
1262 g.write('\t${metaname}.rvalue = ')
1263 g.gen_assert_single_expr(node.expr.right, node.expr.right_type)
1264 g.writeln(';')
1265 }
1266 ast.CallExpr {
1267 g.writeln('\t${metaname}.op = new string("call");')
1268 }
1269 else {}
1270 }
1271
1272 return metaname
1273}
1274
1275fn (mut g JsGen) gen_assert_single_expr(expr ast.Expr, typ ast.Type) {
1276 // eprintln('> gen_assert_single_expr typ: ${typ} | expr: ${expr} | typeof(expr): ${typeof(expr)}')
1277 unknown_value := '*unknown value*'
1278 match expr {
1279 ast.CastExpr, ast.IfExpr, ast.IndexExpr, ast.MatchExpr {
1280 g.write('new string("${unknown_value}")')
1281 }
1282 ast.PrefixExpr {
1283 if expr.right is ast.CastExpr {
1284 // TODO: remove this check;
1285 // vlib/builtin/map_test.v (a map of &int, set to &int(0)) fails
1286 // without special casing ast.CastExpr here
1287 g.write('new string("${unknown_value}")')
1288 } else {
1289 g.gen_expr_to_string(expr, typ)
1290 }
1291 }
1292 ast.TypeNode {
1293 sym := g.table.sym(g.unwrap_generic(typ))
1294 g.write('new string("${sym.name}"')
1295 }
1296 else {
1297 mut should_clone := true
1298 if typ == ast.string_type && expr is ast.StringLiteral {
1299 should_clone = false
1300 }
1301 if expr is ast.CTempVar {
1302 if expr.orig is ast.CallExpr {
1303 should_clone = false
1304 if expr.orig.or_block.kind == .propagate_option {
1305 should_clone = true
1306 }
1307 if expr.orig.is_method && expr.orig.args.len == 0
1308 && expr.orig.name == 'type_name' {
1309 should_clone = true
1310 }
1311 }
1312 }
1313 if should_clone {
1314 g.write('string_clone(')
1315 }
1316 g.gen_expr_to_string(expr, typ)
1317 if should_clone {
1318 g.write(')')
1319 }
1320 }
1321 }
1322
1323 // g.writeln(' /* typeof: ' + expr.type_name() + ' type: ' + typ.str() + ' */ ')
1324}
1325
1326// TODO
1327fn (mut g JsGen) gen_assert_stmt(mut node ast.AssertStmt) {
1328 if !node.is_used {
1329 return
1330 }
1331
1332 g.writeln('// assert')
1333
1334 if mut node.expr is ast.InfixExpr {
1335 if subst_expr := g.assert_subexpression_to_ctemp(node.expr.left, node.expr.left_type) {
1336 node.expr.left = subst_expr
1337 }
1338 if subst_expr := g.assert_subexpression_to_ctemp(node.expr.right, node.expr.right_type) {
1339 node.expr.right = subst_expr
1340 }
1341 }
1342
1343 g.write('if( ')
1344 g.expr(node.expr)
1345 g.write('.valueOf() ) {')
1346 s_assertion := node.expr.str().replace('"', "'")
1347 mut mod_path := g.file.path.replace('\\', '\\\\')
1348 if g.is_test {
1349 metaname_ok := g.gen_assert_metainfo(node)
1350 g.writeln(' g_test_oks++;')
1351 g.writeln(' main__cb_assertion_ok(${metaname_ok});')
1352 g.writeln('} else {')
1353 metaname_fail := g.gen_assert_metainfo(node)
1354 g.writeln(' g_test_fails++;')
1355 g.writeln(' main__cb_assertion_failed(${metaname_fail});')
1356 g.writeln(' builtin__exit(1);')
1357 g.writeln('}')
1358 return
1359 }
1360 g.writeln('} else {')
1361 g.inc_indent()
1362 fname := if g.fn_decl == unsafe { nil } || g.fn_decl.is_anon { 'anon' } else { g.fn_decl.name }
1363 g.writeln('builtin__eprintln(new string("${mod_path}:${node.pos.line_nr + 1}: FAIL: fn ${fname}(): assert ${s_assertion}"));')
1364 g.writeln('builtin__exit(1);')
1365 g.dec_indent()
1366 g.writeln('}')
1367}
1368
1369fn (mut g JsGen) gen_assign_stmt(stmt ast.AssignStmt, semicolon bool) {
1370 if stmt.left.len > stmt.right.len {
1371 // multi return, e.g.
1372 // fn foo() (int, int, int) { return 0, 1, 2 }
1373 // a, _, c := foo()
1374
1375 // TODO: const
1376 g.write('let [')
1377 for i, left in stmt.left {
1378 if !left.is_blank_ident() {
1379 g.expr(left)
1380 }
1381 if i < stmt.left.len - 1 {
1382 g.write(', ')
1383 }
1384 }
1385 g.write('] = ')
1386 g.expr(stmt.right[0])
1387 if semicolon {
1388 g.writeln(';')
1389 }
1390 } else {
1391 // e.g. `a := 1`, `a,b := 1,2`, `o['foo'] = 'bar'`
1392
1393 for i, left in stmt.left {
1394 val := stmt.right[i]
1395 mut is_mut := false
1396 if left is ast.Ident {
1397 is_mut = left.is_mut
1398 if left.kind == .blank_ident || left.name in ['', '_'] {
1399 tmp_var := g.new_tmp_var()
1400 // TODO: Can the tmp_var declaration be omitted?
1401 g.write('const ${tmp_var} = ')
1402 g.expr(val)
1403 g.writeln(';')
1404 continue
1405 }
1406 }
1407
1408 mut styp := if stmt.left_types.len > i { g.styp(stmt.left_types[i]) } else { '' }
1409 // l_sym := g.table.sym(stmt.left_types[i])
1410 if !g.inside_loop && styp.len > 0 {
1411 g.doc.gen_typ(styp)
1412 }
1413 if stmt.op == .decl_assign {
1414 g.write(if g.inside_loop || is_mut { 'let ' } else { 'const ' })
1415 }
1416 if left is ast.IndexExpr && left.is_index_operator {
1417 if stmt.op == .assign {
1418 g.index_operator_call(left.left, left.left_type, left.index, left.index_type,
1419 '[]=', val, stmt.right_types[i])
1420 } else {
1421 infix_op := token.assign_op_to_infix_op(stmt.op)
1422 op_expr := ast.InfixExpr{
1423 left: ast.Expr(left)
1424 right: val
1425 op: infix_op
1426 pos: stmt.pos
1427 left_type: left.typ
1428 right_type: stmt.right_types[i]
1429 promoted_type: left.typ
1430 }
1431 g.index_operator_call(left.left, left.left_type, left.index, left.index_type,
1432 '[]=', ast.Expr(op_expr), left.typ)
1433 }
1434 if semicolon {
1435 if g.inside_loop {
1436 g.write('; ')
1437 } else {
1438 g.writeln(';')
1439 }
1440 }
1441 continue
1442 }
1443
1444 mut array_set := false
1445 mut map_set := false
1446 if left is ast.IndexExpr {
1447 array_set = true
1448
1449 if left.is_map {
1450 map_set = true
1451 g.write('if (!')
1452 g.expr(left.left)
1453 if left.left_type.is_ptr() {
1454 g.write('.valueOf()')
1455 }
1456 g.write('.has(')
1457 g.expr(left.index)
1458 g.write('.\$toJS())) ')
1459 g.expr(left.left)
1460 if left.left_type.is_ptr() {
1461 g.write('.valueOf()')
1462 }
1463 g.writeln('.length++;')
1464 g.expr(left.left)
1465 if left.left_type.is_ptr() {
1466 g.write('.valueOf()')
1467 }
1468 g.write('.map[')
1469 g.expr(left.index)
1470 g.write('.\$toJS()] = { val: ')
1471 } else {
1472 g.expr(left.left)
1473 if left.left_type.is_ptr() {
1474 g.write('.valueOf()')
1475 }
1476 g.write('.arr.set(')
1477 g.write('new int(')
1478 g.cast_stack << ast.int_type_idx
1479 g.expr(left.index)
1480 g.write('.valueOf()')
1481 g.cast_stack.delete_last()
1482 g.write('),')
1483 }
1484 } else {
1485 g.expr(left)
1486 }
1487 if stmt.op == .power_assign {
1488 left_info := g.unwrap(stmt.left_types[i])
1489 right_info := g.unwrap(stmt.right_types[i])
1490 if method := g.table.find_method(left_info.sym, '**') {
1491 if !array_set {
1492 g.write(' = ')
1493 }
1494 left_styp := g.styp(left_info.typ.set_nr_muls(0))
1495 g.write('${left_styp}_${util.replace_op('**')}(')
1496 g.op_arg(left, method.params[0].typ, left_info.typ)
1497 g.write(', ')
1498 g.op_arg(val, method.params[1].typ, right_info.typ)
1499 g.write(')')
1500 if array_set && !map_set {
1501 g.write(')')
1502 }
1503 if map_set {
1504 g.write('}')
1505 }
1506 if semicolon {
1507 g.writeln(';')
1508 }
1509 continue
1510 }
1511 }
1512 if stmt.op == .power_assign {
1513 left_sym := g.table.final_sym(stmt.left_types[i])
1514 if !array_set {
1515 g.write('.val = ')
1516 }
1517 g.write('new ${styp}(')
1518 needs_floor := left_sym.name !in ['f32', 'f64', 'i64', 'u64']
1519 if needs_floor {
1520 g.write('Math.floor(')
1521 }
1522 if !g.pref.output_es5 && (left_sym.kind == .i64 || left_sym.kind == .u64) {
1523 g.write('BigInt(')
1524 g.expr(left)
1525 g.gen_deref_ptr(stmt.left_types[i])
1526 g.write('.valueOf()) ** BigInt(')
1527 g.expr(val)
1528 g.gen_deref_ptr(stmt.right_types[i])
1529 g.write('.valueOf())')
1530 } else {
1531 g.write('Math.pow(')
1532 g.expr(left)
1533 g.gen_deref_ptr(stmt.left_types[i])
1534 g.write('.valueOf(), ')
1535 g.expr(val)
1536 g.gen_deref_ptr(stmt.right_types[i])
1537 g.write('.valueOf())')
1538 }
1539 if needs_floor {
1540 g.write(')')
1541 }
1542 g.write(')')
1543 if array_set && !map_set {
1544 g.write(')')
1545 }
1546 if map_set {
1547 g.write('}')
1548 }
1549 if semicolon {
1550 g.writeln(';')
1551 }
1552 continue
1553 }
1554
1555 left_type := if stmt.left_types.len > i { stmt.left_types[i] } else { ast.no_type }
1556 is_ptr := stmt.op == .assign && left_type.is_ptr() && !left_type.has_option_or_result()
1557 && !array_set
1558 if is_ptr {
1559 g.write('.val')
1560 }
1561
1562 mut floor := false
1563 mut is_special_assign := false
1564 match stmt.op {
1565 .plus_assign, .minus_assign, .mult_assign, .div_assign, .xor_assign, .mod_assign,
1566 .or_assign, .and_assign, .right_shift_assign, .left_shift_assign {
1567 is_special_assign = true
1568 if array_set {
1569 g.write('new ${styp}(')
1570 g.expr(left)
1571 }
1572
1573 l_sym := g.table.sym(stmt.left_types[i])
1574 if l_sym.kind == .string {
1575 g.write('.str')
1576 } else {
1577 g.write('.val')
1578 }
1579
1580 if !array_set {
1581 g.write(' = ')
1582 if (l_sym.name != 'f64' || l_sym.name != 'f32')
1583 && (l_sym.name != 'i64' && l_sym.name != 'u64')
1584 && l_sym.name != 'string' {
1585 g.write('Math.floor(')
1586 floor = true
1587 }
1588 g.expr(left)
1589 }
1590
1591 g.write(match stmt.op {
1592 .plus_assign { ' + ' }
1593 .minus_assign { ' - ' }
1594 .mult_assign { ' * ' }
1595 .div_assign { ' / ' }
1596 .mod_assign { ' % ' }
1597 .xor_assign { ' ^ ' }
1598 .and_assign { ' & ' }
1599 .right_shift_assign { ' >> ' }
1600 .left_shift_assign { ' << ' }
1601 .or_assign { ' | ' }
1602 else { panic('unexpected op ${stmt.op}') }
1603 })
1604 }
1605 .assign, .decl_assign {
1606 if !array_set {
1607 g.write(' = ')
1608 }
1609 }
1610 else {
1611 panic('unsupported assignment operator provided: ${stmt.op}')
1612 }
1613 }
1614
1615 // TODO: Multiple types??
1616
1617 should_cast := stmt.left_types.len != 0
1618 && g.table.type_kind(stmt.left_types.first()) in shallow_equatables
1619 && (g.cast_stack.len <= 0 || stmt.left_types.first() != g.cast_stack.last())
1620 if should_cast {
1621 g.cast_stack << stmt.left_types.first()
1622 g.write('new ${g.styp(stmt.left_types.first())}(')
1623 }
1624 g.expr(val)
1625 if is_ptr {
1626 g.write('.val')
1627 }
1628 if should_cast {
1629 g.write(')')
1630 g.cast_stack.delete_last()
1631 }
1632 if is_special_assign && array_set {
1633 g.write(')')
1634 }
1635 if floor {
1636 g.write(')')
1637 }
1638 if array_set && !map_set {
1639 g.write(')')
1640 }
1641 if left is ast.IndexExpr && left.is_map {
1642 g.write(', key: ')
1643 g.write_map_stored_key(left.index, left.index_type)
1644 g.write(' }')
1645 }
1646 if semicolon {
1647 if g.inside_loop {
1648 g.write('; ')
1649 } else {
1650 g.writeln(';')
1651 }
1652 }
1653 }
1654 }
1655}
1656
1657fn (mut g JsGen) gen_attrs(attrs []ast.Attr) {
1658 for attr in attrs {
1659 g.writeln('/* [${attr.name}] */')
1660 }
1661}
1662
1663fn (mut g JsGen) gen_block(it ast.Block) {
1664 g.writeln('{')
1665 g.inc_indent()
1666 g.stmts(it.stmts)
1667 g.dec_indent()
1668 g.writeln('}')
1669}
1670
1671fn (mut g JsGen) gen_branch_stmt(it ast.BranchStmt) {
1672 // continue or break
1673 if g.inside_or {
1674 match it.kind {
1675 .key_break {
1676 g.writeln('throw new BreakException();')
1677 }
1678 .key_continue {
1679 g.writeln('throw new ContinueException();')
1680 }
1681 else {
1682 verror('unexpected branch stmt: ${it.kind}')
1683 }
1684 }
1685
1686 return
1687 }
1688 g.write(it.kind.str())
1689 g.writeln(';')
1690}
1691
1692fn (mut g JsGen) gen_const_decl(it ast.ConstDecl) {
1693 for field in it.fields {
1694 g.doc.gen_const(g.styp(field.typ))
1695 if field.is_pub {
1696 g.push_pub_var(field.name)
1697 }
1698
1699 if field.expr in [ast.StringInterLiteral, ast.StringLiteral, ast.IntegerLiteral, ast.FloatLiteral,
1700 ast.BoolLiteral] {
1701 g.write('const ${g.js_name(field.name)} = ')
1702 g.expr(field.expr)
1703 } else {
1704 g.write('let ${g.js_name(field.name)} = ')
1705 g.write('undefined')
1706 g.init_global[g.ns.name][g.js_name(field.name)] = field.expr
1707 }
1708 g.writeln(';')
1709 }
1710 g.writeln('')
1711}
1712
1713fn (mut g JsGen) gen_defer_stmts() {
1714 g.writeln('(function defer() {')
1715 for defer_stmt in g.defer_stmts {
1716 g.stmts(defer_stmt.stmts)
1717 }
1718 g.defer_stmts = []
1719 g.writeln('})();')
1720}
1721
1722fn (mut g JsGen) gen_enum_decl(it ast.EnumDecl) {
1723 g.doc.gen_enum()
1724 g.writeln('const ${g.js_name(it.name)} = {')
1725 g.inc_indent()
1726 mut i := 0
1727 for field in it.fields {
1728 g.write('${field.name}: ')
1729 if field.has_expr && field.expr is ast.IntegerLiteral {
1730 i = field.expr.val.int()
1731 }
1732 g.writeln('new int(${i}),')
1733 i++
1734 }
1735 g.dec_indent()
1736 g.writeln('};')
1737 if it.is_pub {
1738 g.push_pub_var(it.name)
1739 }
1740}
1741
1742fn (mut g JsGen) gen_expr_stmt(it ast.ExprStmt) {
1743 g.expr(it.expr)
1744 if !it.is_expr && it.expr !is ast.IfExpr && !g.inside_ternary && !g.inside_if_option {
1745 g.writeln(';')
1746 }
1747}
1748
1749fn (mut g JsGen) gen_expr_stmt_no_semi(it ast.ExprStmt) {
1750 g.expr(it.expr)
1751}
1752
1753// cc_type whether to prefix 'struct' or not (C__Foo -> struct Foo)
1754fn (mut g JsGen) cc_type(typ ast.Type, _is_prefix_struct bool) string {
1755 sym := g.table.sym(g.unwrap_generic(typ))
1756 mut styp := sym.cname.replace('>', '').replace('<', '')
1757 match sym.info {
1758 ast.Struct, ast.Interface, ast.SumType {
1759 if sym.info.is_generic {
1760 mut sgtyps := '_T'
1761 for gt in sym.info.generic_types {
1762 gts := g.table.sym(g.unwrap_generic(gt))
1763 sgtyps += '_${gts.cname}'
1764 }
1765 styp += sgtyps
1766 }
1767 }
1768 else {}
1769 }
1770
1771 if styp.starts_with('JS__') {
1772 styp = styp[4..]
1773 }
1774 return styp
1775}
1776
1777fn (mut g JsGen) gen_for_c_stmt(it ast.ForCStmt) {
1778 g.inside_loop = true
1779 g.write('for (')
1780 if it.has_init {
1781 g.stmt(it.init)
1782 } else {
1783 g.write('; ')
1784 }
1785 if it.has_cond {
1786 g.write('+') // convert to number or boolean
1787 g.expr(it.cond)
1788 }
1789 g.write('; ')
1790 if it.has_inc {
1791 g.stmt_no_semi(it.inc)
1792 }
1793 g.writeln(') {')
1794 g.inc_indent()
1795 g.writeln('try { ')
1796 g.inc_indent()
1797 g.stmts(it.stmts)
1798 g.dec_indent()
1799 g.writeln('} catch (e) {')
1800 g.writeln(' if (e instanceof BreakException) { break; }')
1801 g.writeln(' else if (e instanceof ContinueException) { continue; }')
1802 g.writeln(' else { throw e; } }')
1803 g.dec_indent()
1804 g.writeln('}')
1805
1806 g.inside_loop = false
1807}
1808
1809fn (mut g JsGen) gen_for_in_stmt(it ast.ForInStmt) {
1810 if it.is_range {
1811 // `for x in 1..10 {`
1812 mut i := it.val_var
1813 if i in ['', '_'] {
1814 i = g.new_tmp_var()
1815 }
1816 g.inside_loop = true
1817 g.write('for (let ${i} = ')
1818 g.expr(it.cond)
1819 g.write('; ${i} < ')
1820 g.expr(it.high)
1821 g.writeln('; ${i}.val++) {')
1822 g.inside_loop = false
1823 g.inc_indent()
1824 g.writeln('try { ')
1825 g.inc_indent()
1826 g.stmts(it.stmts)
1827 g.dec_indent()
1828 g.writeln('} catch (e) {')
1829 g.writeln(' if (e instanceof BreakException) { break; }')
1830 g.writeln(' else if (e instanceof ContinueException) { continue; }')
1831 g.writeln(' else { throw e; } }')
1832 g.dec_indent()
1833 g.writeln('}')
1834 } else if it.kind in [.array, .string] || it.cond_type.has_flag(.variadic) {
1835 // `for num in nums {`
1836 val := if it.val_var in ['', '_'] { '_' } else { it.val_var }
1837 // styp := g.styp(it.val_type)
1838 if it.key_var.len > 0 {
1839 g.write('for (const [${it.key_var}, ${val}] of ')
1840 if it.kind == .string {
1841 g.write('Array.from(')
1842 g.expr(it.cond)
1843 if it.cond_type.is_ptr() {
1844 g.write('.valueOf()')
1845 }
1846 g.write('.str.split(\'\').entries(), ([${it.key_var}, ${val}]) => [${it.key_var}, ')
1847
1848 g.write('new ')
1849
1850 g.write('u8(${val})])')
1851 } else {
1852 g.expr(it.cond)
1853 if it.cond_type.is_ptr() {
1854 g.write('.valueOf()')
1855 }
1856 g.write('.entries()')
1857 }
1858 } else {
1859 g.write('for (const ${val} of ')
1860 g.expr(it.cond)
1861 if it.cond_type.is_ptr() {
1862 g.write('.valueOf()')
1863 }
1864 if it.kind == .string {
1865 g.write(".str.split('')")
1866 }
1867 // cast characters to bytes
1868 if val !in ['', '_'] && it.kind == .string {
1869 g.write('.map(c => ')
1870
1871 g.write('new ')
1872
1873 g.write('u8(c))')
1874 }
1875 }
1876 g.writeln(') {')
1877 g.inc_indent()
1878 g.writeln('try { ')
1879 g.inc_indent()
1880 g.stmts(it.stmts)
1881 g.dec_indent()
1882 g.writeln('} catch (e) {')
1883 g.writeln(' if (e instanceof BreakException) { break; }')
1884 g.writeln(' else if (e instanceof ContinueException) { continue; }')
1885 g.writeln(' else { throw e; } }')
1886 g.dec_indent()
1887 g.writeln('}')
1888 } else if it.kind == .map {
1889 // `for key, val in map[string]int {`
1890 // key_styp := g.styp(it.key_type)
1891 // val_styp := g.styp(it.val_type)
1892 key := if it.key_var in ['', '_'] { '' } else { it.key_var }
1893 val := if it.val_var in ['', '_'] { '' } else { it.val_var }
1894 tmp := g.new_tmp_var()
1895 tmp2 := g.new_tmp_var()
1896 if g.pref.output_es5 {
1897 tmp3 := g.new_tmp_var()
1898 g.write('let ${tmp2} = ')
1899 g.expr(it.cond)
1900 if it.cond_type.is_ptr() {
1901 g.write('.valueOf()')
1902 }
1903 g.writeln(';')
1904
1905 g.write('for (var ${tmp3} = 0; ${tmp3} < Object.keys(${tmp2}.map).length; ${tmp3}++) ')
1906 g.write('{')
1907 g.writeln('\tlet ${tmp} = Object.keys(${tmp2}.map)')
1908 g.writeln('\tlet ${key} = ${tmp}[${tmp3}];')
1909 g.writeln('\tlet ${val} = ${tmp2}.map[${tmp}[${tmp3}]];')
1910 g.inc_indent()
1911 g.writeln('try { ')
1912 g.stmts(it.stmts)
1913 g.writeln('} catch (e) {')
1914 g.writeln(' if (e instanceof BreakException) { break; }')
1915 g.writeln(' else if (e instanceof ContinueException) { continue; }')
1916 g.writeln(' else { throw e; } }')
1917 g.dec_indent()
1918 g.writeln('}')
1919 } else {
1920 g.write('let ${tmp} = ')
1921 g.expr(it.cond)
1922 if it.cond_type.is_ptr() {
1923 g.write('.valueOf()')
1924 }
1925 g.writeln(';')
1926 g.writeln('for (var ${tmp2} in ${tmp}.map) {')
1927
1928 g.inc_indent()
1929 g.writeln('let ${val} = ${tmp}.map[${tmp2}].val;')
1930 sym := g.table.sym(it.key_type)
1931 if sym.is_number() {
1932 g.writeln('let ${key} = new ${g.styp(it.key_type)}(+${tmp2})')
1933 } else {
1934 g.writeln('let ${key} = new ${g.styp(it.key_type)}(${tmp2})')
1935 }
1936 g.writeln('try { ')
1937 g.inc_indent()
1938 g.stmts(it.stmts)
1939 g.dec_indent()
1940 g.writeln('} catch (e) {')
1941 g.writeln(' if (e instanceof BreakException) { break; }')
1942 g.writeln(' else if (e instanceof ContinueException) { continue; }')
1943 g.writeln(' else { throw e; } } ')
1944 g.dec_indent()
1945 g.writeln('}')
1946 }
1947 }
1948}
1949
1950fn (mut g JsGen) gen_for_stmt(it ast.ForStmt) {
1951 g.write('while (')
1952 if it.is_inf {
1953 g.write('true')
1954 } else {
1955 g.write('+') // convert expr to number or boolean
1956 g.expr(it.cond)
1957 }
1958 g.writeln(') {')
1959 g.inc_indent()
1960 g.writeln('try { ')
1961 g.inc_indent()
1962 g.stmts(it.stmts)
1963 g.dec_indent()
1964 g.writeln('} catch (e) {')
1965 g.writeln(' if (e instanceof BreakException) { break; }')
1966 g.writeln(' else if (e instanceof ContinueException) { continue; }')
1967 g.writeln(' else { throw e; } }')
1968 g.dec_indent()
1969 g.writeln('}')
1970}
1971
1972fn (mut g JsGen) gen_go_expr(node ast.GoExpr) {
1973 if g.pref.output_es5 {
1974 verror('No support for goroutines on ES5 output')
1975 return
1976 }
1977 g.writeln('new _v_Promise({promise: new Promise(function(resolve){')
1978 g.inc_indent()
1979 g.write('resolve(')
1980 g.expr(node.call_expr)
1981 g.write(');')
1982 g.dec_indent()
1983 g.writeln('})});')
1984}
1985
1986fn (mut g JsGen) gen_spawn_expr(node ast.SpawnExpr) {
1987 if g.pref.output_es5 {
1988 verror('No support for goroutines on ES5 output')
1989 return
1990 }
1991 g.writeln('new _v_Promise({promise: new Promise(function(resolve){')
1992 g.inc_indent()
1993 g.write('resolve(')
1994 g.expr(node.call_expr)
1995 g.write(');')
1996 g.dec_indent()
1997 g.writeln('})});')
1998}
1999
2000fn (mut g JsGen) gen_import_stmt(it ast.Import) {
2001 g.ns.imports[it.mod] = it.alias
2002}
2003
2004fn (mut g JsGen) gen_interface_decl(it ast.InterfaceDecl) {
2005 if it.language != .v {
2006 // JS interfaces do not need codegen
2007 return
2008 }
2009 // JS is dynamically typed, so we don't need any codegen at all
2010 // We just need the JSDoc so TypeScript type checking works
2011 g.doc.gen_interface(it)
2012 // This is a hack to make the interface's type accessible outside its namespace
2013 // TODO: interfaces are always `pub`?
2014 name := g.js_name(it.name)
2015 g.push_pub_var('/** @type ${name} */\n\t\t${name}')
2016 g.writeln('function ${g.js_name(it.name)} (arg) { return new \$ref(arg); }')
2017}
2018
2019fn (mut g JsGen) gen_option_error(expr ast.Expr) {
2020 g.write('new Option({ state: new u8(2),err: ')
2021 g.expr(expr)
2022 g.write('})')
2023}
2024
2025fn (mut g JsGen) gen_return_stmt(it ast.Return) {
2026 node := it
2027 // sym := g.table.sym(g.fn_decl.return_type)
2028 fn_return_is_option := g.fn_decl.return_type.has_flag(.option)
2029 if node.exprs.len == 0 {
2030 if fn_return_is_option {
2031 if g.inside_or {
2032 g.writeln('throw new ReturnException({state: new int(0)});')
2033 } else {
2034 g.writeln('return {state: new int(0)}')
2035 }
2036 } else {
2037 if g.inside_or {
2038 g.writeln('throw new ReturnException(undefined);')
2039 } else {
2040 g.writeln('return;')
2041 }
2042 }
2043 return
2044 }
2045
2046 if fn_return_is_option {
2047 option_none := node.exprs[0] is ast.None
2048 ftyp := g.styp(node.types[0])
2049 mut is_regular_option := ftyp == option_name
2050 if option_none || is_regular_option || node.types[0] == ast.error_type_idx {
2051 if !isnil(g.fn_decl) && g.fn_decl.is_test {
2052 test_error_var := g.new_tmp_var()
2053 g.writeln('let ${test_error_var} = "TODO";')
2054 g.writeln('return ${test_error_var};')
2055 return
2056 }
2057 if !g.inside_or {
2058 g.write('return ')
2059 } else {
2060 g.write('throw new ReturnException(')
2061 }
2062 g.gen_option_error(it.exprs[0])
2063 if g.inside_or {
2064 g.writeln(')')
2065 }
2066 g.writeln(';')
2067 return
2068 }
2069 }
2070 if fn_return_is_option {
2071 tmp := g.new_tmp_var()
2072 g.write('const ${tmp} = new ')
2073
2074 g.writeln('${option_name}({});')
2075 g.write('${tmp}.state = new u8(0);')
2076 g.write('${tmp}.data = ')
2077 if it.exprs.len == 1 {
2078 g.expr_with_expected_type(it.exprs[0], node.types[0])
2079 } else { // Multi return
2080 g.gen_array_init_values(it.exprs)
2081 }
2082 g.writeln('')
2083 if g.inside_or {
2084 g.write('throw new ReturnException(${tmp});')
2085 } else {
2086 g.write('return ${tmp};')
2087 }
2088 return
2089 }
2090 if !g.inside_or {
2091 g.write('return ')
2092 } else {
2093 g.write('throw new ReturnException(')
2094 }
2095 if it.exprs.len == 1 {
2096 g.expr_with_expected_type(it.exprs[0], node.types[0])
2097 } else { // Multi return
2098 g.gen_array_init_values(it.exprs)
2099 }
2100 if g.inside_or {
2101 g.writeln(')')
2102 }
2103 g.writeln(';')
2104}
2105
2106fn (mut g JsGen) gen_hash_stmt(it ast.HashStmt) {
2107 g.writeln(it.val)
2108}
2109
2110fn (mut g JsGen) gen_sumtype_decl(it ast.SumTypeDecl) {
2111 name := g.js_name(it.name)
2112 g.push_pub_var('/** @type ${name} */\n\t\t${name}')
2113 g.writeln('function ${g.js_name(it.name)} (arg) { return arg; }')
2114}
2115
2116fn (mut g JsGen) gen_struct_decl(node ast.StructDecl) {
2117 mut name := node.name
2118 if name.starts_with('JS.') {
2119 return
2120 }
2121 if name in v_types && g.ns.name == 'builtin' {
2122 return
2123 }
2124 js_name := g.js_name(name)
2125 g.gen_attrs(node.attrs)
2126 g.doc.gen_fac_fn(node.fields)
2127 if g.pref.output_es5 {
2128 obj := g.new_tmp_var()
2129 g.writeln('function ${js_name}(${obj}) {')
2130 g.inc_indent()
2131 g.writeln('if (${obj} === undefined) { obj = {}; }')
2132 for field in node.fields {
2133 mut keep := true
2134 for attr in field.attrs {
2135 if attr.name == 'noinit' {
2136 keep = false
2137 }
2138 }
2139 if keep {
2140 g.writeln('if (${obj}.${field.name} === undefined) {')
2141 g.write('${obj}.${field.name} = ')
2142 if field.has_default_expr {
2143 g.expr(field.default_expr)
2144 } else {
2145 g.write_js_default_value(field.typ)
2146 }
2147 g.writeln('\n}')
2148 }
2149 g.writeln('var ${field.name} = ${obj}.${field.name};')
2150 }
2151
2152 g.dec_indent()
2153 } else {
2154 g.write('function ${js_name}({ ')
2155 for i, field in node.fields {
2156 g.write('${field.name}')
2157 mut keep := true
2158 for attr in field.attrs {
2159 if attr.name == 'noinit' {
2160 keep = false
2161 }
2162 }
2163 if keep {
2164 g.write(' = ')
2165
2166 if field.has_default_expr {
2167 g.expr(field.default_expr)
2168 } else if field.typ.has_flag(.option) {
2169 g.write('none__')
2170 } else {
2171 g.write_js_default_value(field.typ)
2172 }
2173 }
2174 if i < node.fields.len - 1 {
2175 g.write(', ')
2176 }
2177 }
2178 g.writeln(' }) {')
2179 }
2180 g.inc_indent()
2181 for field in node.fields {
2182 g.writeln('this.${field.name} = ${field.name}')
2183 }
2184 g.dec_indent()
2185 g.writeln('};')
2186 g.writeln('${js_name}.prototype = {')
2187 g.inc_indent()
2188 for embed in node.embeds {
2189 etyp := g.styp(embed.typ)
2190 g.writeln('...${g.js_name(etyp)}.prototype,')
2191 }
2192 for iface, iface_types in g.table.iface_types {
2193 if iface.starts_with('JS.') {
2194 for ty in iface_types {
2195 sym := g.table.sym(ty)
2196
2197 if sym.name == node.name {
2198 g.writeln('...${g.js_name(iface)}.prototype,')
2199 }
2200 }
2201 }
2202 }
2203 fns := unsafe { g.method_fn_decls[name] }
2204 // gen toString method
2205 fn_names := fns.map(it.name)
2206 if 'toString' !in fn_names {
2207 if g.pref.output_es5 {
2208 g.writeln('toString: (function() {')
2209 } else {
2210 g.writeln('toString() {')
2211 }
2212 g.inc_indent()
2213 g.write('return `${js_name} {')
2214 for i, field in node.fields {
2215 if i == 0 {
2216 g.write(' ')
2217 } else {
2218 g.write(', ')
2219 }
2220 match g.styp(field.typ).split('.').last() {
2221 'string' { g.write('${field.name}: "\${this["${field.name}"].toString()}"') }
2222 else { g.write('${field.name}: \${this["${field.name}"].toString()} ') }
2223 }
2224 }
2225 g.writeln('}`')
2226 g.dec_indent()
2227 if g.pref.output_es5 {
2228 g.writeln('}).bind(this),')
2229 } else {
2230 g.writeln('},')
2231 }
2232 }
2233 for field in node.fields {
2234 typ := g.styp(field.typ)
2235 g.doc.gen_typ(typ)
2236 mut keep := true
2237 for attr in field.attrs {
2238 if attr.name == 'noinit' {
2239 keep = false
2240 }
2241 }
2242 if keep {
2243 g.write('${field.name}: ${g.to_js_typ_val(field.typ)}')
2244 g.writeln(',')
2245 }
2246 }
2247 if g.pref.output_es5 {
2248 g.writeln('\$toJS: (function() { return this; }).bind(this)')
2249 } else {
2250 g.writeln('\$toJS() { return this; }')
2251 }
2252 g.writeln('};\n')
2253 g.dec_indent()
2254
2255 if node.is_pub {
2256 g.push_pub_var(name)
2257 }
2258}
2259
2260fn (mut g JsGen) gen_array_init_expr(it ast.ArrayInit) {
2261 // Note: Fixed arrays and regular arrays are handled the same, since fixed arrays:
2262 // 1) Are only available for number types
2263 // 2) Give the code unnecessary complexity
2264 // 3) Have several limitations like missing most `Array.prototype` methods
2265 // 4) Modern engines can optimize regular arrays into typed arrays anyways,
2266 // offering similar performance
2267 g.write('new array(new array_buffer({arr: ')
2268 g.inc_indent()
2269
2270 if it.has_len {
2271 t1 := g.new_tmp_var()
2272 g.writeln('(function(length) {')
2273 g.inc_indent()
2274 g.writeln('const ${t1} = [];')
2275 g.write('for (let it = 0, index = 0; index < length')
2276 g.writeln('; it++, index++) {')
2277 g.inc_indent()
2278 g.write('${t1}.push(')
2279 if it.has_init {
2280 g.expr(it.init_expr)
2281 } else {
2282 // Fill the array with the default values for its type
2283 g.write_js_default_value(it.elem_type)
2284 }
2285 g.writeln(');')
2286 g.dec_indent()
2287 g.writeln('};')
2288 g.writeln('return ${t1};')
2289 g.dec_indent()
2290 g.write('})(')
2291 g.expr(it.len_expr)
2292 g.write('),len: new int(')
2293 g.expr(it.len_expr)
2294 g.write(')')
2295 g.write(', cap: new int(')
2296 g.expr(it.len_expr)
2297 g.write(')')
2298 } else if it.is_fixed && it.exprs.len == 1 {
2299 // [100]u8 codegen
2300 t1 := g.new_tmp_var()
2301 t2 := g.new_tmp_var()
2302 g.writeln('(function() {')
2303 g.inc_indent()
2304 g.writeln('const ${t1} = [];')
2305 g.write('for (let ${t2} = 0; ${t2} < ')
2306 g.expr(it.exprs[0])
2307 g.writeln('; ${t2}++) {')
2308 g.inc_indent()
2309 g.write('${t1}.push(')
2310 if it.has_init {
2311 g.expr(it.init_expr)
2312 } else {
2313 // Fill the array with the default values for its type
2314 g.write_js_default_value(it.elem_type)
2315 }
2316 g.writeln(');')
2317 g.dec_indent()
2318 g.writeln('};')
2319 g.writeln('return ${t1};')
2320 g.dec_indent()
2321 g.write('})(), len: new int(')
2322 g.expr(it.exprs[0])
2323 g.write('), cap: new int(')
2324 g.expr(it.exprs[0])
2325 g.write(')')
2326 } else {
2327 styp := g.styp(it.elem_type)
2328
2329 c := if styp in v_types {
2330 g.gen_array_init_values_prim(it.exprs, styp)
2331 } else {
2332 g.gen_array_init_values(it.exprs)
2333 }
2334 g.write(', len: new int(${c}), cap: new int(${c})')
2335 }
2336 g.dec_indent()
2337 g.write('}))')
2338}
2339
2340fn (mut g JsGen) gen_array_init_values(exprs []ast.Expr) int {
2341 g.write('[')
2342 mut c := 0
2343 for i, expr in exprs {
2344 g.expr(expr)
2345 if i < exprs.len - 1 {
2346 g.write(', ')
2347 }
2348 c++
2349 }
2350 g.write(']')
2351 return c
2352}
2353
2354fn (mut g JsGen) gen_array_init_values_prim(exprs []ast.Expr, typ string) int {
2355 g.write('[')
2356 mut c := 0
2357 for i, expr in exprs {
2358 g.write('new ${typ}(')
2359 g.expr(expr)
2360 g.write(')')
2361 if i < exprs.len - 1 {
2362 g.write(', ')
2363 }
2364 c++
2365 }
2366 g.write(']')
2367 return c
2368}
2369
2370fn (mut g JsGen) gen_ident(node ast.Ident) {
2371 mut name := g.js_name(node.name)
2372 if node.kind == .blank_ident || name in ['', '_'] {
2373 name = g.new_tmp_var()
2374 }
2375 // TODO: `is`
2376 // TODO: handle options
2377 g.write(name)
2378
2379 // TODO: Generate .val for basic types
2380}
2381
2382fn (mut g JsGen) gen_lock_expr(_node ast.LockExpr) {
2383 // TODO: implement this
2384}
2385
2386fn (mut g JsGen) need_tmp_var_in_match(node ast.MatchExpr) bool {
2387 if node.is_expr && node.return_type != ast.void_type && node.return_type != 0 {
2388 cond_sym := g.table.final_sym(node.cond_type)
2389 sym := g.table.sym(node.return_type)
2390 if sym.kind == .multi_return {
2391 return false
2392 }
2393 if cond_sym.kind == .enum && node.branches.len > 5 {
2394 return true
2395 }
2396 for branch in node.branches {
2397 if branch.stmts.len > 1 {
2398 return true
2399 }
2400 if branch.stmts.len == 1 {
2401 if branch.stmts[0] is ast.ExprStmt {
2402 stmt := branch.stmts[0] as ast.ExprStmt
2403 if stmt.expr in [ast.CallExpr, ast.IfExpr, ast.MatchExpr]
2404 || (stmt.expr is ast.IndexExpr && stmt.expr.or_expr.kind != .absent) {
2405 return true
2406 }
2407 }
2408 }
2409 }
2410 }
2411 return false
2412}
2413
2414fn (mut g JsGen) match_expr_classic(node ast.MatchExpr, is_expr bool, cond_var MatchCond, tmp_var string) {
2415 type_sym := g.table.sym(node.cond_type)
2416 for j, branch in node.branches {
2417 is_last := j == node.branches.len - 1
2418 if branch.is_else || (node.is_expr && is_last && tmp_var.len == 0) {
2419 if node.branches.len > 1 {
2420 if is_expr && tmp_var.len == 0 {
2421 // TODO: too many branches. maybe separate ?: matches
2422 g.write(' : ')
2423 } else {
2424 g.writeln('')
2425 g.write_v_source_line_info(branch.pos)
2426 g.writeln('else {')
2427 }
2428 }
2429 } else {
2430 if j > 0 {
2431 if is_expr && tmp_var.len == 0 {
2432 g.write(' : ')
2433 } else {
2434 g.writeln('')
2435 g.write_v_source_line_info(branch.pos)
2436 g.write('else ')
2437 }
2438 }
2439 if is_expr && tmp_var.len == 0 {
2440 g.write('(')
2441 } else {
2442 if j == 0 {
2443 g.writeln('')
2444 }
2445 g.write_v_source_line_info(branch.pos)
2446 g.write('if (')
2447 }
2448 for i, expr in branch.exprs {
2449 if i > 0 {
2450 g.write(' || ')
2451 }
2452 match type_sym.kind {
2453 .array {
2454 ptr_typ := g.gen_array_equality_fn(node.cond_type)
2455
2456 g.write('${ptr_typ}_arr_eq(')
2457 g.match_cond(cond_var)
2458 g.write(',')
2459 g.expr(expr)
2460 g.write(').val')
2461 }
2462 .array_fixed {
2463 ptr_typ := g.gen_fixed_array_equality_fn(node.cond_type)
2464
2465 g.write('${ptr_typ}_arr_eq(')
2466 g.match_cond(cond_var)
2467 g.write(',')
2468 g.expr(expr)
2469 g.write(').val')
2470 }
2471 .map {
2472 ptr_typ := g.gen_map_equality_fn(node.cond_type)
2473
2474 g.write('${ptr_typ}_map_eq(')
2475 g.match_cond(cond_var)
2476 g.write(',')
2477 g.expr(expr)
2478 g.write(').val')
2479 }
2480 .string {
2481 g.match_cond(cond_var)
2482 g.write('.str === ')
2483 g.expr(expr)
2484 g.write('.str')
2485 }
2486 .struct {
2487 ptr_typ := g.gen_struct_equality_fn(node.cond_type)
2488
2489 g.write('${ptr_typ}_struct_eq(')
2490 g.match_cond(cond_var)
2491 g.write(',')
2492 g.expr(expr)
2493 g.write(').val')
2494 }
2495 .sum_type {
2496 ptr_typ := g.gen_sumtype_equality_fn(node.cond_type)
2497
2498 g.write('${ptr_typ}_sumtype_eq(')
2499 g.match_cond(cond_var)
2500 g.write(',')
2501 g.expr(expr)
2502 g.write(').val')
2503 }
2504 .alias {
2505 ptr_typ := g.gen_alias_equality_fn(node.cond_type)
2506
2507 g.write('${ptr_typ}_alias_eq(')
2508 g.match_cond(cond_var)
2509 g.write(',')
2510 g.expr(expr)
2511 g.write(').val')
2512 }
2513 else {
2514 has_operator_overloading := g.table.has_method(type_sym, '==')
2515 if has_operator_overloading {
2516 left := g.unwrap(node.cond_type)
2517 g.write(g.styp(left.unaliased.set_nr_muls(0)))
2518 g.write('__eq(')
2519 g.match_cond(cond_var)
2520 g.gen_deref_ptr(node.cond_type)
2521 g.write(',')
2522 g.expr(expr)
2523 g.write(')')
2524 g.write('.valueOf()')
2525 } else if expr is ast.RangeExpr {
2526 // if type is unsigned and low is 0, check is unneeded
2527 mut skip_low := false
2528 if expr.low is ast.IntegerLiteral {
2529 if node.cond_type in [ast.u16_type, ast.u32_type, ast.u64_type]
2530 && expr.low.val == '0' {
2531 skip_low = true
2532 }
2533 }
2534 g.write('(')
2535 if !skip_low {
2536 g.match_cond(cond_var)
2537 g.write(' >= ')
2538 g.expr(expr.low)
2539 g.write(' && ')
2540 }
2541 g.match_cond(cond_var)
2542 g.write(' <= ')
2543 g.expr(expr.high)
2544 g.write(')')
2545 } else {
2546 g.write('vEq(')
2547 g.match_cond(cond_var)
2548 g.write(',')
2549 g.expr(expr)
2550 g.write(')')
2551 }
2552 }
2553 }
2554 }
2555 if is_expr && tmp_var.len == 0 {
2556 g.write(')? ')
2557 } else {
2558 g.writeln(') {')
2559 }
2560 }
2561 g.stmts_with_tmp_var(branch.stmts, tmp_var)
2562 if !g.inside_ternary && node.branches.len >= 1 {
2563 g.write('}')
2564 }
2565 }
2566}
2567
2568type MatchCond = CondExpr | CondString
2569
2570struct CondString {
2571 s string
2572}
2573
2574struct CondExpr {
2575 expr ast.Expr
2576}
2577
2578fn (mut g JsGen) match_cond(cond MatchCond) {
2579 match cond {
2580 CondString {
2581 g.write(cond.s)
2582 }
2583 CondExpr {
2584 g.expr(cond.expr)
2585 }
2586 }
2587}
2588
2589fn (mut g JsGen) match_expr(node ast.MatchExpr) {
2590 if node.cond_type == 0 {
2591 g.writeln('// match 0')
2592 return
2593 }
2594 prev := g.inside_ternary
2595 need_tmp_var := g.need_tmp_var_in_match(node)
2596 is_expr := (node.is_expr && node.return_type != ast.void_type) || g.inside_ternary
2597 mut cond_var := MatchCond(CondString{''})
2598 mut tmp_var := ''
2599 mut cur_line := ''
2600 if is_expr && !need_tmp_var {
2601 g.inside_ternary = true
2602 }
2603
2604 if node.cond in [ast.Ident, ast.SelectorExpr, ast.IntegerLiteral, ast.StringLiteral, ast.FloatLiteral,
2605 ast.BoolLiteral, ast.CallExpr, ast.EnumVal] {
2606 cond_var = CondExpr{node.cond}
2607 } else {
2608 s := g.new_tmp_var()
2609 cond_var = CondString{s}
2610 g.write('let ${s} = ')
2611 g.expr(node.cond)
2612 g.writeln(';')
2613 }
2614 if need_tmp_var {
2615 g.empty_line = true
2616 cur_line = g.out.cut_to(g.stmt_start_pos).trim_left(' \t')
2617 tmp_var = g.new_tmp_var()
2618 g.writeln('let ${tmp_var} = undefined;')
2619 }
2620 if is_expr && !need_tmp_var {
2621 g.write('(')
2622 }
2623 cond_fsym := g.table.final_sym(node.cond_type)
2624 if node.is_sum_type {
2625 g.match_expr_sumtype(node, is_expr, cond_var, tmp_var)
2626 } else if cond_fsym.kind == .enum && !g.inside_loop && node.branches.len > 5
2627 && unsafe { g.fn_decl != 0 } { // do not optimize while in top-level
2628 g.match_expr_switch(node, is_expr, cond_var, tmp_var, cond_fsym)
2629 } else {
2630 g.match_expr_classic(node, is_expr, cond_var, tmp_var)
2631 }
2632 g.write(cur_line)
2633 if need_tmp_var {
2634 g.write('${tmp_var}')
2635 }
2636 if is_expr && !need_tmp_var {
2637 g.write(')')
2638 g.inside_ternary = prev
2639 }
2640}
2641
2642fn (mut g JsGen) stmts_with_tmp_var(stmts []ast.Stmt, tmp_var string) {
2643 g.inc_indent()
2644 if g.inside_ternary {
2645 g.write('(')
2646 }
2647 prev := g.inside_ternary
2648 for i, stmt in stmts {
2649 if i == stmts.len - 1 && tmp_var != '' {
2650 if g.inside_if_option {
2651 if stmt is ast.ExprStmt {
2652 if stmt.typ == ast.error_type_idx || stmt.expr is ast.None {
2653 g.writeln('${tmp_var}.state = 2;')
2654 g.write('${tmp_var}.err = ')
2655 g.expr(stmt.expr)
2656 g.writeln(';')
2657 } else {
2658 g.write('opt_ok(')
2659 g.stmt(stmt)
2660 g.writeln(', ${tmp_var});')
2661 }
2662 }
2663 } else {
2664 g.write('${tmp_var} = ')
2665 g.stmt(stmt)
2666 g.writeln('')
2667 }
2668 } else {
2669 g.stmt(stmt)
2670 if g.inside_if_option && stmt is ast.ExprStmt {
2671 g.writeln(';')
2672 }
2673 }
2674 if g.inside_ternary && i < stmts.len - 1 {
2675 g.write(',')
2676 }
2677 }
2678 g.dec_indent()
2679 g.inside_ternary = prev
2680 if g.inside_ternary {
2681 g.write(')')
2682 }
2683}
2684
2685fn (mut g JsGen) match_expr_sumtype(node ast.MatchExpr, is_expr bool, cond_var MatchCond, tmp_var string) {
2686 for j, branch in node.branches {
2687 mut sumtype_index := 0
2688 for {
2689 is_last := j == node.branches.len - 1
2690 sym := g.table.sym(node.cond_type)
2691 if branch.is_else || (node.is_expr && is_last && tmp_var.len == 0) {
2692 if is_expr && tmp_var.len == 0 {
2693 g.write(' : ')
2694 } else {
2695 g.writeln('')
2696 g.writeln('else {')
2697 }
2698 } else {
2699 if j > 0 || sumtype_index > 0 {
2700 if is_expr && tmp_var.len == 0 {
2701 g.write(' : ')
2702 } else {
2703 g.write('else ')
2704 }
2705 }
2706
2707 if is_expr && tmp_var.len == 0 {
2708 g.write('(')
2709 } else {
2710 g.write('if (')
2711 }
2712 if sym.kind == .sum_type || sym.kind == .interface {
2713 x := branch.exprs[sumtype_index]
2714
2715 if x is ast.TypeNode {
2716 typ := g.unwrap_generic(x.typ)
2717
2718 tsym := g.table.sym(typ)
2719 if tsym.language == .js && (tsym.name == 'JS.Number'
2720 || tsym.name == 'JS.Boolean' || tsym.name == 'JS.String') {
2721 g.write('typeof ')
2722 }
2723 }
2724 }
2725 g.match_cond(cond_var)
2726 if sym.kind == .sum_type {
2727 x := branch.exprs[sumtype_index]
2728 if x is ast.TypeNode {
2729 typ := g.unwrap_generic(x.typ)
2730 tsym := g.table.sym(typ)
2731 if tsym.language == .js && (tsym.name == 'JS.Number'
2732 || tsym.name == 'JS.Boolean' || tsym.name == 'JS.String') {
2733 g.write(' === "${tsym.name[3..].to_lower_ascii()}"')
2734 } else if tsym.kind == .array {
2735 g.write(' && ')
2736 g.match_cond(cond_var)
2737 g.write('.arr.arr.every(x => x instanceof ')
2738 g.expr(branch.exprs[sumtype_index])
2739 g.write(')')
2740 } else {
2741 g.write(' instanceof ')
2742 g.expr(branch.exprs[sumtype_index])
2743 }
2744 } else {
2745 g.write(' instanceof ')
2746 g.expr(branch.exprs[sumtype_index])
2747 }
2748 } else if sym.kind == .interface {
2749 if !sym.name.starts_with('JS.') {
2750 g.write('.val')
2751 }
2752 x := branch.exprs[sumtype_index]
2753 if x is ast.TypeNode {
2754 typ := g.unwrap_generic(x.typ)
2755 tsym := g.table.sym(typ)
2756 if tsym.language == .js && (tsym.name == 'Number'
2757 || tsym.name == 'Boolean' || tsym.name == 'String') {
2758 g.write(' === ${tsym.name.to_lower_ascii()}')
2759 } else {
2760 g.write(' instanceof ')
2761 g.expr(branch.exprs[sumtype_index])
2762 }
2763 } else {
2764 g.write(' instanceof ')
2765 g.write('None__')
2766 }
2767 }
2768 if is_expr && tmp_var.len == 0 {
2769 g.write(')? ')
2770 } else {
2771 g.writeln(') {')
2772 }
2773 }
2774 g.stmts_with_tmp_var(branch.stmts, tmp_var)
2775 if !g.inside_ternary {
2776 g.writeln('}')
2777 }
2778 sumtype_index++
2779 if branch.exprs.len == 0 || sumtype_index == branch.exprs.len {
2780 break
2781 }
2782 }
2783 }
2784}
2785
2786fn (mut g JsGen) match_expr_switch(node ast.MatchExpr, _is_expr bool, cond_var MatchCond, tmp_var string,
2787 _enum_typ ast.TypeSymbol) {
2788 mut range_branches := []ast.MatchBranch{cap: node.branches.len} // branches have RangeExpr cannot emit as switch case branch, we handle it in default branch
2789 mut default_generated := false
2790 g.empty_line = true
2791 g.write('switch (')
2792 g.match_cond(cond_var)
2793 g.writeln(') {')
2794 g.inc_indent()
2795 for branch in node.branches {
2796 if branch.is_else {
2797 g.writeln('default:')
2798 default_generated = true
2799 if range_branches.len > 0 {
2800 g.inc_indent()
2801 for range_branch in range_branches {
2802 g.write('if (')
2803 for i, expr in range_branch.exprs {
2804 if i > 0 {
2805 g.write(' || ')
2806 }
2807 if expr is ast.RangeExpr {
2808 // if type is unsigned and low is 0, check is unneeded
2809 mut skip_low := false
2810 if expr.low is ast.IntegerLiteral {
2811 if node.cond_type in [ast.u16_type, ast.u32_type, ast.u64_type]
2812 && expr.low.val == '0' {
2813 skip_low = true
2814 }
2815 }
2816 g.write('(')
2817 if !skip_low {
2818 g.match_cond(cond_var)
2819 g.write(' >= ')
2820 g.expr(expr.low)
2821 g.write(' && ')
2822 }
2823 g.match_cond(cond_var)
2824 g.write(' <= ')
2825 g.expr(expr.high)
2826 g.write(')')
2827 } else {
2828 g.match_cond(cond_var)
2829 g.write(' == (')
2830 g.expr(expr)
2831 g.write(')')
2832 }
2833 }
2834 g.writeln(') {')
2835 g.stmts_with_tmp_var(range_branch.stmts, tmp_var)
2836 g.writeln('break;')
2837 g.writeln('}')
2838 }
2839 g.dec_indent()
2840 }
2841 } else {
2842 if branch.exprs.any(it is ast.RangeExpr) {
2843 range_branches << branch
2844 continue
2845 }
2846 for expr in branch.exprs {
2847 if expr is ast.EnumVal {
2848 g.write('case ')
2849 g.expr(expr)
2850 g.writeln(': ')
2851 }
2852 }
2853 }
2854 g.inc_indent()
2855 g.writeln('{')
2856 g.stmts_with_tmp_var(branch.stmts, tmp_var)
2857 g.writeln('} break;')
2858 g.dec_indent()
2859 }
2860 if range_branches.len > 0 && !default_generated {
2861 g.writeln('default:')
2862 g.inc_indent()
2863 for range_branch in range_branches {
2864 g.write('if (')
2865 for i, expr in range_branch.exprs {
2866 if i > 0 {
2867 g.write(' || ')
2868 }
2869 if expr is ast.RangeExpr {
2870 // if type is unsigned and low is 0, check is unneeded
2871 mut skip_low := false
2872 if expr.low is ast.IntegerLiteral {
2873 if node.cond_type in [ast.u16_type, ast.u32_type, ast.u64_type]
2874 && expr.low.val == '0' {
2875 skip_low = true
2876 }
2877 }
2878 g.write('(')
2879 if !skip_low {
2880 g.match_cond(cond_var)
2881 g.write(' >= ')
2882 g.expr(expr.low)
2883 g.write(' && ')
2884 }
2885 g.match_cond(cond_var)
2886 g.write(' <= ')
2887 g.expr(expr.high)
2888 g.write(')')
2889 } else {
2890 g.match_cond(cond_var)
2891 g.write(' == (')
2892 g.expr(expr)
2893 g.write(')')
2894 }
2895 }
2896 g.writeln(') {')
2897 g.stmts_with_tmp_var(range_branch.stmts, tmp_var)
2898 g.writeln('break;')
2899 g.writeln('}')
2900 }
2901 g.dec_indent()
2902 }
2903 g.dec_indent()
2904 g.writeln('}')
2905}
2906
2907fn (mut g JsGen) need_tmp_var_in_if(node ast.IfExpr) bool {
2908 if node.is_expr && g.inside_ternary {
2909 if node.typ.has_flag(.option) {
2910 return true
2911 }
2912
2913 for branch in node.branches {
2914 if branch.cond is ast.IfGuardExpr || branch.stmts.len > 1 {
2915 return true
2916 }
2917
2918 if branch.stmts.len == 1 {
2919 if branch.stmts[0] is ast.ExprStmt {
2920 stmt := branch.stmts[0] as ast.ExprStmt
2921 if stmt.expr is ast.CallExpr {
2922 if stmt.expr.is_method {
2923 left_sym := g.table.sym(stmt.expr.receiver_type)
2924 if left_sym.kind in [.array, .array_fixed, .map] {
2925 return true
2926 }
2927 }
2928 }
2929 }
2930 }
2931 }
2932 }
2933 return false
2934}
2935
2936fn (mut g JsGen) gen_if_expr(node ast.IfExpr) {
2937 // For simpe if expressions we can use C's `?:`
2938 // `if x > 0 { 1 } else { 2 }` => `(x > 0)? (1) : (2)`
2939 // For if expressions with multiple statements or another if expression inside, it's much
2940 // easier to use a temp var, than do C tricks with commas, introduce special vars etc
2941 // (as it used to be done).
2942 // Always use this in -autofree, since ?: can have tmp expressions that have to be freed.
2943 needs_tmp_var := g.need_tmp_var_in_if(node)
2944 tmp := if needs_tmp_var { g.new_tmp_var() } else { '' }
2945 mut is_true := false
2946 mut comptime_has_true_branch := false
2947
2948 if needs_tmp_var {
2949 if node.typ.has_flag(.option) {
2950 g.inside_if_option = true
2951 }
2952
2953 g.writeln('let ${tmp}; /* if prepend */')
2954 } else if node.is_expr || g.inside_ternary {
2955 g.write('(')
2956 prev := g.inside_ternary
2957 g.inside_ternary = true
2958 for i, branch in node.branches {
2959 if node.is_comptime {
2960 $if debug_comptime_branch_context ? {
2961 g.write('/* ${branch.cond} */')
2962 }
2963 // comptime $if, only generate the true branch
2964 if i < node.branches.len - 1 || !node.has_else {
2965 if !g.comptime_if_result(branch) {
2966 continue
2967 }
2968 comptime_has_true_branch = true
2969 } else {
2970 // else branch
2971 if comptime_has_true_branch {
2972 continue
2973 }
2974 }
2975 } else {
2976 if i > 0 {
2977 g.write(' : ')
2978 }
2979 if i < node.branches.len - 1 || !node.has_else {
2980 g.write('(')
2981 g.expr(branch.cond)
2982 g.write(').valueOf()')
2983 g.write(' ? ')
2984 }
2985 }
2986 g.stmts(branch.stmts)
2987 }
2988 g.inside_ternary = prev
2989 g.write(')')
2990 return
2991 }
2992
2993 mut is_guard := false
2994 mut guard_idx := 0
2995 mut guard_vars := []string{}
2996
2997 for i, branch in node.branches {
2998 cond := branch.cond
2999 if cond is ast.IfGuardExpr {
3000 if !is_guard {
3001 is_guard = true
3002 guard_idx = i
3003 guard_vars = []string{len: node.branches.len}
3004 }
3005 if cond.expr !in [ast.IndexExpr, ast.PrefixExpr] {
3006 var_name := g.new_tmp_var()
3007 guard_vars[i] = var_name
3008 g.writeln('let ${var_name};')
3009 } else {
3010 guard_vars[i] = ''
3011 }
3012 }
3013 }
3014
3015 is_true = false
3016 comptime_has_true_branch = false
3017 for i, branch in node.branches {
3018 if i > 0 {
3019 g.write('} else ')
3020 }
3021 // if last branch is `else {`
3022 if i == node.branches.len - 1 && node.has_else {
3023 g.writeln('{')
3024 // define `err` only for simple `if val := opt {...} else {`
3025 if is_guard && guard_idx == i - 1 {
3026 cvar_name := guard_vars[guard_idx]
3027 g.writeln('\tlet err = ${cvar_name}.err;')
3028 }
3029 if node.is_comptime && !comptime_has_true_branch {
3030 is_true = true
3031 }
3032 } else {
3033 match branch.cond {
3034 ast.IfGuardExpr {
3035 mut var_name := guard_vars[i]
3036 mut short_opt := false
3037 if var_name == '' {
3038 short_opt = true // we don't need a further tmp, so use the one we'll get later
3039 var_name = g.new_tmp_var()
3040 guard_vars[i] = var_name // for `else`
3041 g.tmp_count--
3042 g.writeln('if (${var_name}.state == 0) {')
3043 } else {
3044 g.write('if (${var_name} = ')
3045 if node.is_comptime {
3046 $if debug_comptime_branch_context ? {
3047 g.write('/* ${branch.cond} */')
3048 }
3049 is_true = g.comptime_if_result(branch)
3050 if is_true {
3051 g.write('1')
3052 comptime_has_true_branch = true
3053 } else {
3054 g.write('0')
3055 }
3056 } else {
3057 g.expr(branch.cond.expr)
3058 }
3059 g.writeln(', ${var_name}.state == 0) {')
3060 }
3061 if short_opt || branch.cond.vars[0].name != '_' {
3062 if short_opt {
3063 cond_var_name := if branch.cond.vars[0].name == '_' {
3064 '_dummy_${g.tmp_count + 1}'
3065 } else {
3066 branch.cond.vars[0].name
3067 }
3068 g.write('\tlet ${cond_var_name} = ')
3069 if node.is_comptime {
3070 is_true = g.comptime_if_result(branch)
3071 if is_true {
3072 g.write('1')
3073 comptime_has_true_branch = true
3074 } else {
3075 g.write('0')
3076 }
3077 } else {
3078 g.expr(branch.cond.expr)
3079 }
3080 g.writeln(';')
3081 } else {
3082 g.writeln('\tlet ${branch.cond.vars}[0].name = ${var_name}.data;')
3083 }
3084 }
3085 }
3086 else {
3087 if node.is_comptime {
3088 g.write('if (')
3089 $if debug_comptime_branch_context ? {
3090 g.write('/* ${branch.cond} */')
3091 }
3092 is_true = g.comptime_if_result(branch)
3093 if is_true {
3094 g.write('1')
3095 comptime_has_true_branch = true
3096 } else {
3097 g.write('0')
3098 }
3099 g.writeln(') {')
3100 } else {
3101 g.write('if ((')
3102 g.expr(branch.cond)
3103 g.writeln(').valueOf()) {')
3104 }
3105 }
3106 }
3107 }
3108 g.inc_indent()
3109 if needs_tmp_var {
3110 g.stmts_with_tmp_var(branch.stmts, tmp)
3111 } else if (node.is_comptime && is_true) || !node.is_comptime {
3112 g.stmts(branch.stmts)
3113 }
3114 g.dec_indent()
3115 }
3116 if node.branches.len > 0 {
3117 g.writeln('}')
3118 }
3119 if needs_tmp_var {
3120 g.write('${tmp}')
3121 }
3122 if node.typ.has_flag(.option) {
3123 g.inside_if_option = false
3124 }
3125}
3126
3127fn (mut g JsGen) gen_index_expr(expr ast.IndexExpr) {
3128 if expr.is_index_operator {
3129 g.index_operator_call(expr.left, expr.left_type, expr.index, expr.index_type, '[]',
3130 ast.empty_expr, ast.void_type)
3131 return
3132 }
3133 left_sym := g.table.sym(expr.left_type)
3134 // TODO: Handle splice setting if it's implemented
3135 if expr.index is ast.RangeExpr {
3136 if left_sym.kind == .string {
3137 g.write('string_slice(')
3138 } else {
3139 g.write('array_slice(')
3140 }
3141 g.expr(expr.left)
3142 if expr.left_type.is_ptr() {
3143 g.write('.valueOf()')
3144 }
3145 g.write(',')
3146
3147 if expr.index.has_low {
3148 g.expr(expr.index.low)
3149 } else {
3150 g.write('new int(0)')
3151 }
3152 g.write(', ')
3153 if expr.index.has_high {
3154 g.expr(expr.index.high)
3155 } else {
3156 g.expr(expr.left)
3157 if expr.left_type.is_ptr() {
3158 g.write('.valueOf()')
3159 }
3160 g.write('.len')
3161 }
3162 g.write(')')
3163 } else if left_sym.kind == .map {
3164 if expr.is_setter && !g.inside_left_shift {
3165 g.expr(expr.left)
3166 if expr.left_type.is_ptr() {
3167 g.write('.valueOf()')
3168 }
3169 g.inside_map_set = true
3170 g.write('.getOrSet(')
3171 g.expr(expr.index)
3172 g.write('.\$toJS()')
3173 // g.write(', ${g.to_js_typ_val(left_typ.)')
3174 match left_sym.info {
3175 ast.Map {
3176 g.write(', ${g.to_js_typ_val(left_sym.info.value_type)}')
3177 }
3178 else {
3179 verror('unreachable')
3180 }
3181 }
3182
3183 g.write(')')
3184 } else {
3185 match left_sym.info {
3186 ast.Map {
3187 tmp := g.new_tmp_var()
3188 g.write('(function() { let ${tmp} = ')
3189 g.expr(expr.left)
3190 if expr.left_type.is_ptr() {
3191 g.write('.valueOf()')
3192 }
3193 g.write('.get(')
3194 g.expr(expr.index)
3195 g.write('.\$toJS()); return js_is_undefined(${tmp}).valueOf() ? ')
3196 g.write(g.to_js_typ_val(left_sym.info.value_type))
3197 g.write(' : ${tmp}; })()')
3198 }
3199 else {
3200 verror('unreachable')
3201 }
3202 }
3203 }
3204 } else if left_sym.kind == .string {
3205 if expr.is_setter {
3206 // TODO: What's the best way to do this?
3207 // 'string'[3] = `o`
3208 } else {
3209 // TODO: Maybe use u16 there? JS String returns values up to 2^16-1
3210 g.write('new u8(')
3211 g.expr(expr.left)
3212 if expr.left_type.is_ptr() {
3213 g.write('.valueOf()')
3214 }
3215 g.write('.str.charCodeAt(')
3216 g.expr(expr.index)
3217 g.write('))')
3218 }
3219 } else {
3220 // TODO: Does this cover all cases?
3221 g.expr(expr.left)
3222 if expr.left_type.is_ptr() {
3223 g.write('.valueOf()')
3224 }
3225 g.write('.arr.get(')
3226 g.write('new int(')
3227 g.cast_stack << ast.int_type_idx
3228 g.expr(expr.index)
3229 g.write('.valueOf()')
3230 g.cast_stack.delete_last()
3231 g.write('))')
3232 }
3233}
3234
3235fn (mut g JsGen) gen_deref_ptr(ty ast.Type) {
3236 mut t := ty
3237 for t.is_ptr() {
3238 g.write('.valueOf()')
3239 t = t.deref()
3240 }
3241}
3242
3243fn (mut g JsGen) expr_string(expr ast.Expr) string {
3244 pos := g.out.len
3245 g.expr(expr)
3246 return g.out.cut_to(pos).trim_space()
3247}
3248
3249fn (mut g JsGen) write_map_stored_key(expr ast.Expr, typ ast.Type) {
3250 if typ == 0 || typ == ast.invalid_type {
3251 g.write('v_clone_value(')
3252 g.expr(expr)
3253 g.write(')')
3254 return
3255 }
3256 copy_fn := g.get_copy_fn(typ)
3257 g.write('${copy_fn}(')
3258 g.expr(expr)
3259 g.write(')')
3260}
3261
3262fn (mut g JsGen) should_wrap_js_selector_rvalue(expr ast.Expr, expected_type ast.Type) bool {
3263 if expected_type == 0 || expected_type.is_ptr() {
3264 return false
3265 }
3266 match expr {
3267 ast.SelectorExpr {}
3268 else { return false }
3269 }
3270
3271 target_sym := g.table.final_sym(g.unwrap_generic(expected_type))
3272 if target_sym.language == .js || target_sym.name.starts_with('JS.') {
3273 return false
3274 }
3275 return target_sym.kind in shallow_equatables
3276}
3277
3278fn (mut g JsGen) expr_with_expected_type(expr ast.Expr, expected_type ast.Type) {
3279 if g.should_wrap_js_selector_rvalue(expr, expected_type) {
3280 g.write('new ${g.styp(expected_type)}(')
3281 g.expr(expr)
3282 g.write(')')
3283 return
3284 }
3285 g.expr(expr)
3286}
3287
3288fn (mut g JsGen) gen_infix_expr(it ast.InfixExpr) {
3289 l_sym := g.table.final_sym(it.left_type)
3290 r_sym := g.table.final_sym(it.right_type)
3291
3292 is_not := it.op in [.not_in, .not_is, .ne]
3293 if is_not {
3294 g.write('!(')
3295 }
3296 is_arithmetic := it.op in [token.Kind.plus, .minus, .mul, .power, .div, .mod, .right_shift,
3297 .left_shift, .amp, .pipe, .xor]
3298
3299 if !g.pref.output_es5 && is_arithmetic && ((l_sym.kind == .i64 || l_sym.kind == .u64)
3300 || (r_sym.kind == .i64 || r_sym.kind == .u64)) {
3301 // if left or right is i64 or u64 we convert them to bigint to perform operation.
3302 greater_typ := if l_sym.kind == .i64 || l_sym.kind == .u64 {
3303 it.left_type
3304 } else {
3305 it.right_type
3306 } // g.greater_typ(it.left_type, it.right_type)
3307 g.write('new ')
3308
3309 g.write('${g.styp(greater_typ)}(')
3310 g.cast_stack << greater_typ
3311 g.write('BigInt((')
3312 g.expr(it.left)
3313 g.gen_deref_ptr(it.left_type)
3314 g.write(').\$toJS())')
3315 g.write(' ${it.op} ')
3316 g.write('BigInt((')
3317 g.expr(it.right)
3318 g.gen_deref_ptr(it.right_type)
3319 g.write(').\$toJS())')
3320 g.cast_stack.delete_last()
3321 g.write(')')
3322 if is_not {
3323 g.write(')')
3324 }
3325 return
3326 }
3327 if it.op == .logical_or || it.op == .and {
3328 g.write('new bool(')
3329 g.expr(it.left)
3330 g.write('.valueOf()')
3331 g.write(it.op.str())
3332 g.expr(it.right)
3333 g.write('.valueOf()')
3334 g.write(')')
3335 } else if it.op == .eq || it.op == .ne {
3336 node := it
3337 left := g.unwrap(node.left_type)
3338 right := g.unwrap(node.right_type)
3339 has_operator_overloading := g.table.has_method(left.sym, '==')
3340 if has_operator_overloading
3341 || (l_sym.kind in shallow_equatables && r_sym.kind in shallow_equatables) {
3342 if node.op == .ne {
3343 g.write('!')
3344 }
3345 g.write(g.styp(left.unaliased.set_nr_muls(0)))
3346 g.write('__eq(')
3347 g.expr(node.left)
3348 g.gen_deref_ptr(left.typ)
3349 g.write(',')
3350 g.expr(node.right)
3351 g.gen_deref_ptr(right.typ)
3352 g.write(')')
3353 } else {
3354 g.write('vEq(')
3355 g.expr(it.left)
3356 g.gen_deref_ptr(it.left_type)
3357 g.write(', ')
3358 g.expr(it.right)
3359 g.gen_deref_ptr(it.right_type)
3360 g.write(')')
3361 }
3362 } else if l_sym.kind == .array && it.op == .left_shift { // arr << 1
3363 g.write('array_push(')
3364 old_inside_left_shift := g.inside_left_shift
3365 g.inside_left_shift = true
3366 g.expr(it.left)
3367 g.inside_left_shift = old_inside_left_shift
3368 mut ltyp := it.left_type
3369 for ltyp.is_ptr() {
3370 g.write('.val')
3371 ltyp = ltyp.deref()
3372 }
3373 g.write('.arr.arr,')
3374 array_info := l_sym.info as ast.Array
3375 // arr << [1, 2]
3376 if r_sym.kind == .array && array_info.elem_type != it.right_type {
3377 g.write('...')
3378 }
3379 g.expr(it.right)
3380 g.write(')')
3381 } else if r_sym.kind in [.array, .map, .string] && it.op in [.key_in, .not_in] {
3382 g.expr(it.right)
3383
3384 mut ltyp := it.right_type
3385 for ltyp.is_ptr() {
3386 g.write('.val')
3387 ltyp = ltyp.deref()
3388 }
3389 if r_sym.kind == .map {
3390 g.write('.map.has(')
3391 } else if r_sym.kind == .string {
3392 g.write('.str.includes(')
3393 } else {
3394 g.write('.\$includes(')
3395 }
3396 g.expr(it.left)
3397 if l_sym.kind == .string {
3398 g.write('.str')
3399 }
3400 g.write(')')
3401 } else if it.op in [.key_is, .not_is] { // foo is Foo
3402 g.expr(it.left)
3403 g.gen_deref_ptr(it.left_type)
3404 g.write(' instanceof ')
3405 g.write(g.styp(it.right_type))
3406 } else if it.op in [.lt, .gt, .ge, .le] && g.table.has_method(l_sym, '<')
3407 && l_sym.kind == r_sym.kind {
3408 if it.op in [.le, .ge] {
3409 g.write('!')
3410 }
3411 if it.op in [.lt, .ge] {
3412 g.expr(it.left)
3413 g.gen_deref_ptr(it.left_type)
3414 g.write('.\$lt (')
3415 g.expr(it.right)
3416 g.gen_deref_ptr(it.right_type)
3417 g.write(')')
3418 } else {
3419 g.expr(it.right)
3420 g.gen_deref_ptr(it.right_type)
3421 g.write('.\$lt (')
3422 g.expr(it.left)
3423 g.gen_deref_ptr(it.left_type)
3424 g.write(')')
3425 }
3426 } else {
3427 has_operator_overloading := g.table.has_method(l_sym, it.op.str())
3428 if has_operator_overloading {
3429 g.expr(it.left)
3430 g.gen_deref_ptr(it.left_type)
3431 name := match it.op.str() {
3432 '+' {
3433 '\$add'
3434 }
3435 '-' {
3436 '\$sub'
3437 }
3438 '/' {
3439 '\$div'
3440 }
3441 '*' {
3442 '\$mul'
3443 }
3444 '%' {
3445 '\$mod'
3446 }
3447 else {
3448 panic('unreachable')
3449 ''
3450 }
3451 }
3452
3453 g.write('.${name} (')
3454 g.expr(it.right)
3455 g.gen_deref_ptr(it.right_type)
3456 g.write(')')
3457 } else {
3458 mut greater_typ := ast.no_type
3459 // todo(playX): looks like this cast is always required to perform .eq operation on types.
3460 if is_arithmetic {
3461 greater_typ = g.greater_typ(it.left_type, it.right_type)
3462 if g.cast_stack.len > 0 {
3463 // needs_cast = g.cast_stack.last() != greater_typ
3464 }
3465 }
3466
3467 if is_arithmetic {
3468 g.write('new ')
3469
3470 g.write('${g.styp(greater_typ)}(')
3471 g.cast_stack << greater_typ
3472 }
3473
3474 g.expr(it.left)
3475
3476 g.gen_deref_ptr(it.left_type)
3477 // g.write('.val')
3478 g.write(' ${it.op} ')
3479
3480 g.expr(it.right)
3481 g.gen_deref_ptr(it.right_type)
3482 // g.write('.val')
3483
3484 if is_arithmetic {
3485 g.cast_stack.delete_last()
3486 g.write(')')
3487 }
3488 }
3489 }
3490
3491 if is_not {
3492 g.write(')')
3493 }
3494}
3495
3496fn (mut g JsGen) greater_typ(left ast.Type, right ast.Type) ast.Type {
3497 l := int(left)
3498 r := int(right)
3499 lr := [l, r]
3500 if ast.string_type_idx in lr {
3501 return ast.Type(ast.string_type_idx)
3502 }
3503 should_float := (l in ast.integer_type_idxs && r in ast.float_type_idxs)
3504 || (r in ast.integer_type_idxs && l in ast.float_type_idxs)
3505 if should_float {
3506 if ast.f64_type_idx in lr {
3507 return ast.Type(ast.f64_type_idx)
3508 }
3509 if ast.f32_type_idx in lr {
3510 return ast.Type(ast.f32_type_idx)
3511 }
3512 return ast.Type(ast.float_literal_type)
3513 }
3514 should_int := (l in ast.integer_type_idxs && r in ast.integer_type_idxs)
3515 if should_int {
3516 if ast.u64_type_idx in lr {
3517 return ast.Type(ast.u64_type_idx)
3518 }
3519 // just guessing this order
3520 if ast.i64_type_idx in lr {
3521 return ast.Type(ast.i64_type_idx)
3522 }
3523 if ast.u32_type_idx in lr {
3524 return ast.Type(ast.u32_type_idx)
3525 }
3526 if ast.i32_type_idx in lr {
3527 return ast.Type(ast.i32_type_idx)
3528 }
3529 if ast.int_type_idx in lr {
3530 $if new_int ? && x64 {
3531 return ast.Type(ast.i64_type_idx)
3532 } $else {
3533 return ast.Type(ast.i32_type_idx)
3534 }
3535 }
3536 if ast.u16_type_idx in lr {
3537 return ast.Type(ast.u16_type_idx)
3538 }
3539 if ast.i16_type_idx in lr {
3540 return ast.Type(ast.i16_type_idx)
3541 }
3542 if ast.u8_type_idx in lr {
3543 return ast.Type(ast.u8_type_idx)
3544 }
3545 if ast.i8_type_idx in lr {
3546 return ast.Type(ast.i8_type_idx)
3547 }
3548 return ast.Type(ast.int_literal_type_idx)
3549 }
3550 return ast.idx_to_type(l)
3551}
3552
3553fn (mut g JsGen) gen_map_init_expr(it ast.MapInit) {
3554 // key_typ_sym := g.table.sym(it.key_type)
3555 // value_typ_sym := g.table.sym(it.value_type)
3556 // key_typ_str := util.no_dots(key_typ_sym.name)
3557 // value_typ_str := util.no_dots(value_typ_sym.name)
3558 g.writeln('new map(')
3559 g.inc_indent()
3560 if it.vals.len > 0 {
3561 g.writeln('{')
3562 g.inc_indent()
3563 for i, key in it.keys {
3564 val := it.vals[i]
3565 g.write('[')
3566 g.expr(key)
3567 g.write('.\$toJS()]')
3568 g.write(': { val: ')
3569 g.expr(val)
3570 g.write(', key: ')
3571 g.write_map_stored_key(key, it.key_type)
3572 g.write(' }')
3573 if i < it.keys.len - 1 {
3574 g.write(',')
3575 }
3576 g.writeln('')
3577 }
3578 g.dec_indent()
3579 g.write('}')
3580 } else {
3581 g.write('{}')
3582 }
3583 g.dec_indent()
3584 g.write(')')
3585}
3586
3587fn (mut g JsGen) type_name(raw_type ast.Type) {
3588 typ := raw_type
3589 sym := g.table.sym(typ)
3590 mut s := ''
3591 if sym.kind == .function {
3592 // todo: properly print function signatures
3593 if typ.is_ptr() {
3594 s = '&function'
3595 } else {
3596 s = 'function'
3597 }
3598 } else {
3599 s = g.table.type_to_str(g.unwrap_generic(typ))
3600 }
3601 g.write('new string("${s}")')
3602}
3603
3604fn (mut g JsGen) gen_selector_expr(it ast.SelectorExpr) {
3605 if it.name_type > 0 {
3606 node := it
3607 match node.gkind_field {
3608 .name {
3609 g.type_name(it.name_type)
3610 return
3611 }
3612 .typ {
3613 g.write('new int(')
3614 g.write('${int(g.unwrap_generic(it.name_type))}')
3615 g.write(')')
3616 return
3617 }
3618 .unaliased_typ {
3619 g.write('new int(')
3620 g.write('${int(g.table.unaliased_type(g.unwrap_generic(it.name_type)))}')
3621 g.write(')')
3622 return
3623 }
3624 .indirections {
3625 g.write('new int(')
3626 g.write('${int(g.unwrap_generic(it.name_type).nr_muls())}')
3627 g.write(')')
3628 return
3629 }
3630 .unknown {
3631 if node.field_name == 'name' {
3632 g.type_name(it.name_type)
3633 return
3634 } else if node.field_name in ['idx', 'typ'] {
3635 g.write('new int(')
3636 g.write('${int(g.unwrap_generic(it.name_type))}')
3637 g.write(')')
3638 return
3639 }
3640 panic('unknown generic field ${it.pos}')
3641 }
3642 }
3643 }
3644 g.expr(it.expr)
3645 mut ltyp := it.expr_type
3646 lsym := g.table.sym(ltyp)
3647 if lsym.kind != .interface && lsym.language != .js {
3648 for ltyp.is_ptr() {
3649 g.write('.val')
3650 ltyp = ltyp.deref()
3651 }
3652 }
3653 g.write('.${it.field_name}')
3654}
3655
3656fn (mut g JsGen) gen_string_inter_literal(it ast.StringInterLiteral) {
3657 should_cast := !(g.cast_stack.len > 0 && g.cast_stack.last() == ast.string_type_idx)
3658 if should_cast {
3659 g.write('new ')
3660
3661 g.write('string(')
3662 }
3663 g.write('`')
3664 for i, val in it.vals {
3665 escaped_val := val.replace('`', '\\`')
3666 g.write(escaped_val)
3667 if i >= it.exprs.len {
3668 continue
3669 }
3670 expr := it.exprs[i]
3671 if expr is ast.AtExpr {
3672 g.write(escape_template_literal_value(expr.val))
3673 continue
3674 }
3675 // fmt := it.fmts[i]
3676 // fwidth := it.fwidths[i]
3677 // precision := it.precisions[i]
3678 g.write('\${')
3679 typ := g.unwrap_generic(it.expr_types[i])
3680 /*
3681 g.expr(expr)
3682 if sym.kind == .struct && sym.has_method('str') {
3683 g.write('.str()')
3684 }*/
3685 g.gen_expr_to_string(expr, typ)
3686 g.write('}')
3687 }
3688 g.write('`')
3689 if should_cast {
3690 g.write(')')
3691 }
3692}
3693
3694fn (mut g JsGen) gen_string_literal(it ast.StringLiteral) {
3695 text := it.val.replace("'", "'").replace('"', '\\"')
3696 if it.language != .js {
3697 g.write('new string(')
3698 }
3699 if it.is_raw {
3700 g.writeln('(function() { let s = String(); ')
3701 for x in text {
3702 g.writeln('s += String.fromCharCode(${x});')
3703 }
3704 g.writeln('return s; })()')
3705 } else {
3706 g.write('"')
3707 for ch in text {
3708 if ch == `\n` {
3709 g.write('\\n')
3710 } else {
3711 g.write('${ch.ascii_str()}')
3712 }
3713 }
3714 g.write('"')
3715 }
3716 if it.language != .js {
3717 g.write(')')
3718 }
3719}
3720
3721fn (mut g JsGen) gen_struct_init(it ast.StructInit) {
3722 type_sym := g.table.sym(it.typ)
3723 mut name := type_sym.name
3724 if name.contains('<') {
3725 name = name[0..name.index('<') or { name.len }]
3726 }
3727 if it.init_fields.len == 0 && type_sym.kind != .interface {
3728 if type_sym.kind == .struct && type_sym.language == .js {
3729 g.write('{}')
3730 } else {
3731 g.write('new ${g.js_name(name)}({})')
3732 }
3733 } else if it.init_fields.len == 0 && type_sym.kind == .interface {
3734 g.write('new ${g.js_name(name)}()') // JS interfaces can be instantiated with default ctor
3735 } else if type_sym.kind == .interface && it.init_fields.len != 0 {
3736 g.writeln('(function () {')
3737 g.inc_indent()
3738 g.writeln('let tmp = new ${g.js_name(name)}()')
3739
3740 for init_field in it.init_fields {
3741 g.write('tmp.${init_field.name} = ')
3742 g.expr(init_field.expr)
3743 g.writeln(';')
3744 }
3745 g.writeln('return tmp')
3746 g.dec_indent()
3747 g.writeln('})()')
3748 } else if type_sym.kind == .struct && type_sym.language == .js {
3749 g.writeln('{')
3750 g.inc_indent()
3751 for i, init_field in it.init_fields {
3752 if init_field.name.len != 0 {
3753 g.write('${init_field.name}: ')
3754 }
3755 g.expr(init_field.expr)
3756 if i < it.init_fields.len - 1 {
3757 g.write(',')
3758 }
3759 g.writeln('')
3760 }
3761 g.dec_indent()
3762
3763 g.writeln('}')
3764 } else {
3765 g.writeln('(function() {')
3766 g.inc_indent()
3767 tmp := g.new_tmp_var()
3768 g.writeln('let ${tmp} = new ${g.js_name(name)}({});')
3769
3770 for init_field in it.init_fields {
3771 if init_field.name.len != 0 {
3772 g.write('${tmp}.${init_field.name} = ')
3773 g.expr(init_field.expr)
3774 }
3775 g.write(';')
3776
3777 g.writeln('')
3778 }
3779 g.writeln('return ${tmp};')
3780 g.dec_indent()
3781 g.writeln('})()')
3782 }
3783}
3784
3785fn (mut g JsGen) gen_typeof_expr(it ast.TypeOf) {
3786 sym := g.table.sym(it.typ)
3787 if sym.kind == .sum_type {
3788 // TODO: JS sumtypes not implemented yet
3789 } else if sym.kind == .array_fixed {
3790 fixed_info := sym.info as ast.ArrayFixed
3791 typ_name := g.table.get_type_name(fixed_info.elem_type)
3792 g.write('"[${fixed_info.size}]${typ_name}"')
3793 } else if sym.kind == .function {
3794 info := sym.info as ast.FnType
3795 fn_info := info.func
3796 mut repr := 'fn ('
3797 for i, arg in fn_info.params {
3798 if i > 0 {
3799 repr += ', '
3800 }
3801 if arg.typ.has_flag(.option) {
3802 repr += '?'
3803 }
3804 repr += g.table.get_type_name(arg.typ)
3805 }
3806 repr += ')'
3807 if fn_info.return_type != ast.void_type {
3808 if fn_info.return_type.has_flag(.option) {
3809 repr += '?'
3810 }
3811 repr += ' ${g.table.get_type_name(fn_info.return_type)}'
3812 }
3813 g.write('"${repr}"')
3814 } else {
3815 g.write('"${sym.name}"')
3816 }
3817}
3818
3819fn (mut g JsGen) gen_cast_tmp(tmp string, typ_ ast.Type) {
3820 // Skip cast if type is the same as the parent caster
3821 tsym := g.table.final_sym(typ_)
3822 if !g.pref.output_es5 && (tsym.kind == .i64 || tsym.kind == .u64) {
3823 g.write('new ')
3824
3825 g.write('${tsym.kind.str()}')
3826 g.write('(BigInt(')
3827 g.write(tmp)
3828 g.write('n))')
3829 return
3830 }
3831 g.cast_stack << typ_
3832 typ := g.styp(typ_)
3833
3834 if typ_.is_ptr() {
3835 g.write('new \$ref(')
3836 }
3837
3838 g.write('new ')
3839 g.write('${typ}(')
3840 g.write(tmp)
3841 if typ == 'string' {
3842 g.write('.toString()')
3843 }
3844
3845 g.write(')')
3846 if typ_.is_ptr() {
3847 g.write(')')
3848 }
3849
3850 g.cast_stack.delete_last()
3851}
3852
3853fn (mut g JsGen) gen_type_cast_expr(it ast.CastExpr) {
3854 is_literal := ((it.expr is ast.IntegerLiteral && it.typ in ast.integer_type_idxs)
3855 || (it.expr is ast.FloatLiteral && it.typ in ast.float_type_idxs))
3856 from_type_sym := g.table.sym(it.expr_type)
3857 to_type_sym := g.table.sym(it.typ) // type to be used as cast
3858 if it.typ.is_bool() && from_type_sym.name == 'JS.Boolean' {
3859 g.write('new bool(')
3860 g.expr(it.expr)
3861 g.write(')')
3862 return
3863 }
3864 if it.expr_type.is_bool() && to_type_sym.name == 'JS.Boolean' {
3865 g.expr(it.expr)
3866 g.write('.\$toJS()')
3867 return
3868 }
3869 if (to_type_sym.is_number() && from_type_sym.name == 'JS.Number')
3870 || (to_type_sym.is_number() && from_type_sym.name == 'JS.BigInt')
3871 || (to_type_sym.is_string() && from_type_sym.name == 'JS.String') {
3872 g.write('new ${to_type_sym.kind.str()}(')
3873 g.expr(it.expr)
3874 g.write(')')
3875 return
3876 }
3877
3878 if (from_type_sym.is_number() && to_type_sym.name == 'JS.Number')
3879 || (from_type_sym.is_number() && to_type_sym.name == 'JS.BigInt')
3880 || (from_type_sym.is_string() && to_type_sym.name == 'JS.String') {
3881 g.write('${g.styp(it.typ)}(')
3882 g.expr(it.expr)
3883 g.write('.\$toJS())')
3884 return
3885 }
3886
3887 if (from_type_sym.name == 'Any' && from_type_sym.language == .js)
3888 || from_type_sym.name == 'JS.Any' || from_type_sym.name == 'voidptr' {
3889 if it.typ.is_ptr() {
3890 g.write('new \$ref(')
3891 }
3892 g.expr(it.expr)
3893 if it.typ.is_ptr() {
3894 g.write(')')
3895 }
3896 return
3897 }
3898
3899 // Skip cast if type is the same as the parent caster
3900 tsym := to_type_sym
3901 if tsym.kind == .sum_type {
3902 g.expr(it.expr)
3903 return
3904 }
3905 if !g.pref.output_es5 && it.expr is ast.IntegerLiteral
3906 && (tsym.kind == .i64 || tsym.kind == .u64) {
3907 g.write('new ')
3908
3909 g.write('${tsym.kind.str()}')
3910 g.write('(BigInt(')
3911 g.write(it.expr.val)
3912 g.write('n))')
3913 return
3914 }
3915 if g.cast_stack.len > 0 && is_literal {
3916 if it.typ == g.cast_stack.last() {
3917 g.expr(it.expr)
3918 return
3919 }
3920 }
3921 g.cast_stack << it.typ
3922 typ := g.styp(it.typ)
3923 if !is_literal {
3924 if it.typ.is_ptr() {
3925 g.write('new \$ref(')
3926 }
3927
3928 g.write('new ')
3929
3930 g.write('${typ}(')
3931 }
3932 g.expr(it.expr)
3933 if typ == 'string' && it.expr !is ast.StringLiteral {
3934 g.write('.toString()')
3935 }
3936 if !is_literal {
3937 g.write(')')
3938 if it.typ.is_ptr() {
3939 g.write(')')
3940 }
3941 }
3942 g.cast_stack.delete_last()
3943}
3944
3945fn (mut g JsGen) gen_integer_literal_expr(it ast.IntegerLiteral) {
3946 typ := ast.Type(ast.int_type)
3947
3948 // Don't wrap integers for use in JS.foo functions.
3949 // TODO: call.language always seems to be "v", parser bug?
3950 if g.call_stack.len > 0 {
3951 call := g.call_stack.last()
3952 if call.language == .js {
3953 for t in call.args {
3954 if t.expr is ast.IntegerLiteral {
3955 if t.expr == it {
3956 g.write(it.val)
3957 return
3958 }
3959 }
3960 }
3961 }
3962 }
3963
3964 // Skip cast if type is the same as the parent caster
3965 if g.cast_stack.len > 0 {
3966 cast_type := g.cast_stack.last()
3967 if cast_type in ast.integer_type_idxs {
3968 cast_sym := g.table.final_sym(cast_type)
3969 g.write('new ')
3970 g.write(g.styp(cast_type))
3971 g.write('(')
3972 if cast_sym.kind in [.i64, .u64] {
3973 g.write('"${it.val}"')
3974 } else {
3975 g.write(it.val)
3976 }
3977 g.write(')')
3978 return
3979 }
3980 }
3981 g.write('new ')
3982
3983 g.write('${g.styp(typ)}(${it.val})')
3984}
3985
3986fn (mut g JsGen) gen_float_literal_expr(it ast.FloatLiteral) {
3987 typ := ast.Type(ast.f32_type)
3988
3989 // Don't wrap integers for use in JS.foo functions.
3990 // TODO: call.language always seems to be "v", parser bug?
3991 if g.call_stack.len > 0 {
3992 call := g.call_stack.last()
3993 if call.language == .js {
3994 for i, t in call.args {
3995 if t.expr is ast.FloatLiteral {
3996 if t.expr == it {
3997 if call.expected_arg_types[i] in ast.integer_type_idxs {
3998 g.write(int(it.val.f64()).str())
3999 } else {
4000 g.write(it.val)
4001 }
4002 return
4003 }
4004 }
4005 }
4006 }
4007 }
4008
4009 // Skip cast if type is the same as the parent caster
4010 if g.cast_stack.len > 0 {
4011 if g.cast_stack.last() in ast.float_type_idxs {
4012 g.write('new f32(${it.val})')
4013 return
4014 } else if g.cast_stack.last() in ast.integer_type_idxs {
4015 g.write(int(it.val.f64()).str())
4016 return
4017 }
4018 }
4019 g.write('new ')
4020
4021 g.write('${g.styp(typ)}(${it.val})')
4022}
4023
4024fn (mut g JsGen) unwrap_generic(typ ast.Type) ast.Type {
4025 if typ.has_flag(.generic) {
4026 /*
4027 convert_generic_type should not mutate the table.
4028 It mutates if the generic type is for example []T and the
4029 concrete type is an array type that has not been registered
4030 yet. This should have already happened in the checker, since
4031 it also calls convert_generic_type. g.table is made
4032 non-mut to make sure no one else can accidentally mutates the table.
4033 */
4034 mut muttable := unsafe { &ast.Table(g.table) }
4035 if t_typ := muttable.convert_generic_type(typ, if unsafe { g.fn_decl != 0 } {
4036 g.fn_decl.generic_names
4037 } else {
4038 []string{}
4039 }, g.cur_concrete_types)
4040 {
4041 return t_typ
4042 }
4043 }
4044 return typ
4045}
4046
4047fn replace_op(s string) string {
4048 return match s {
4049 '+' { '_plus' }
4050 '-' { '_minus' }
4051 '*' { '_mult' }
4052 '**' { '_pow' }
4053 '/' { '_div' }
4054 '%' { '_mod' }
4055 '[]' { '_index' }
4056 '[]=' { '_index_set' }
4057 '<' { '_lt' }
4058 '>' { '_gt' }
4059 '==' { '_eq' }
4060 else { '' }
4061 }
4062}
4063
4064struct JsIndexOperatorMethodInfo {
4065 method ast.Fn
4066 name string
4067 receiver_type ast.Type
4068}
4069
4070fn (mut g JsGen) resolved_index_operator_receiver_type(receiver ast.Expr, receiver_type ast.Type) ast.Type {
4071 _ = receiver
4072 return g.unwrap_generic(receiver_type)
4073}
4074
4075fn (mut g JsGen) index_operator_method_info(receiver ast.Expr, receiver_type ast.Type, op string) ?JsIndexOperatorMethodInfo {
4076 resolved_receiver_type := g.resolved_index_operator_receiver_type(receiver, receiver_type)
4077 receiver_info := g.unwrap(if resolved_receiver_type != 0 {
4078 resolved_receiver_type
4079 } else {
4080 receiver_type
4081 })
4082 mut method := ast.Fn{}
4083 if receiver_info.sym.has_method(op) || receiver_info.sym.has_method_with_generic_parent(op) {
4084 method = receiver_info.sym.find_method_with_generic_parent(op) or {
4085 receiver_info.sym.find_method(op) or { return none }
4086 }
4087 } else if receiver_info.unaliased_sym.has_method_with_generic_parent(op) {
4088 method = receiver_info.unaliased_sym.find_method_with_generic_parent(op) or { return none }
4089 } else {
4090 return none
4091 }
4092 method_name := g.styp(receiver_info.unaliased.set_nr_muls(0)) + '_' + util.replace_op(op)
4093 return JsIndexOperatorMethodInfo{
4094 method: method
4095 name: method_name
4096 receiver_type: receiver_info.typ
4097 }
4098}
4099
4100fn (mut g JsGen) index_operator_call(receiver ast.Expr, receiver_type ast.Type, index ast.Expr, index_type ast.Type, op string, value ast.Expr, value_type ast.Type) {
4101 info := g.index_operator_method_info(receiver, receiver_type, op) or {
4102 verror('missing `${op}` overload for `${g.table.type_to_str(receiver_type)}`')
4103 return
4104 }
4105 g.write(info.name)
4106 g.write('(')
4107 g.op_arg(receiver, info.method.params[0].typ, info.receiver_type)
4108 g.write(', ')
4109 g.op_arg(index, info.method.params[1].typ, index_type)
4110 if op == '[]=' {
4111 g.write(', ')
4112 g.op_arg(value, info.method.params[2].typ, value_type)
4113 }
4114 g.write(')')
4115}
4116
4117fn (mut g JsGen) gen_postfix_index_expr(expr ast.IndexExpr, op token.Kind) {
4118 left_sym := g.table.sym(expr.left_type)
4119 left_sym_kind := left_sym.kind
4120 // TODO: Handle splice setting if it's implemented
4121 if expr.index is ast.RangeExpr {
4122 if left_sym_kind == .array {
4123 g.write('array_slice(')
4124 } else {
4125 g.write('string_slice(')
4126 }
4127 g.expr(expr.left)
4128 if expr.left_type.is_ptr() {
4129 g.write('.valueOf()')
4130 }
4131 g.write(',')
4132
4133 if expr.index.has_low {
4134 g.expr(expr.index.low)
4135 } else {
4136 g.write('new int(0)')
4137 }
4138 g.write(', ')
4139 if expr.index.has_high {
4140 g.expr(expr.index.high)
4141 } else {
4142 g.expr(expr.left)
4143 if expr.left_type.is_ptr() {
4144 g.write('.valueOf()')
4145 }
4146 g.write('.len')
4147 }
4148 g.write(')')
4149 } else if left_sym_kind == .map {
4150 lsym := g.table.sym(expr.left_type)
4151 value_typ := match lsym.info {
4152 ast.Map {
4153 lsym.info.value_type
4154 }
4155 else {
4156 verror('unreachable')
4157 ast.void_type
4158 }
4159 }
4160
4161 if expr.is_setter {
4162 g.write('if (!')
4163 g.expr(expr.left)
4164 if expr.left_type.is_ptr() {
4165 g.write('.valueOf()')
4166 }
4167 g.write('.has(')
4168 g.expr(expr.index)
4169 g.write('.\$toJS())) ')
4170 g.expr(expr.left)
4171 if expr.left_type.is_ptr() {
4172 g.write('.valueOf()')
4173 }
4174 g.writeln('.length++;')
4175 g.expr(expr.left)
4176 if expr.left_type.is_ptr() {
4177 g.write('.valueOf()')
4178 }
4179 g.write('.map[')
4180 g.expr(expr.index)
4181 g.write('.\$toJS()] = { val: ')
4182 g.write('new ${g.styp(value_typ)}(')
4183 g.expr(expr.left)
4184 if expr.left_type.is_ptr() {
4185 g.write('.valueOf()')
4186 }
4187 g.write('.getOrSet(')
4188 g.expr(expr.index)
4189 g.write('.\$toJS(), ')
4190 g.write(g.to_js_typ_val(value_typ))
4191 g.write(')')
4192 match op {
4193 .inc {
4194 g.write('.val + 1)')
4195 }
4196 .dec {
4197 g.write('.val - 1)')
4198 }
4199 else {
4200 verror('not yet implemented')
4201 }
4202 }
4203
4204 g.write(', key: ')
4205 g.write_map_stored_key(expr.index, expr.index_type)
4206 g.write(' }')
4207 } else {
4208 tmp := g.new_tmp_var()
4209 g.write('(function() { let ${tmp} = ')
4210 g.expr(expr.left)
4211 if expr.left_type.is_ptr() {
4212 g.write('.valueOf()')
4213 }
4214 g.write('.get(')
4215 g.expr(expr.index)
4216 g.write('.\$toJS()); return js_is_undefined(${tmp}).valueOf() ? ')
4217 g.write(g.to_js_typ_val(value_typ))
4218 g.write(' : ${tmp}; })()')
4219 }
4220 } else if left_sym_kind == .string {
4221 if expr.is_setter {
4222 // TODO: What's the best way to do this?
4223 // 'string'[3] = `o`
4224 } else {
4225 // TODO: Maybe use u16 there? JS String returns values up to 2^16-1
4226 g.write('new u8(')
4227 g.expr(expr.left)
4228 if expr.left_type.is_ptr() {
4229 g.write('.valueOf()')
4230 }
4231 g.write('.str.charCodeAt(')
4232 g.expr(expr.index)
4233 g.write('))')
4234 }
4235 }
4236}
4237