mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
3810 lines
89 KiB
V
3810 lines
89 KiB
V
module js
|
|
|
|
import strings
|
|
import v.ast
|
|
import v.token
|
|
import v.pref
|
|
import v.util
|
|
import v.util.version
|
|
import v.depgraph
|
|
import encoding.base64
|
|
import v.gen.js.sourcemap
|
|
|
|
const (
|
|
// https://ecma-international.org/ecma-262/#sec-reserved-words
|
|
js_reserved = ['await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger',
|
|
'default', 'delete', 'do', 'else', 'enum', 'export', 'extends', 'finally', 'for', 'function',
|
|
'if', 'implements', 'import', 'in', 'instanceof', 'interface', 'let', 'new', 'package',
|
|
'private', 'protected', 'public', 'return', 'static', 'super', 'switch', 'this', 'throw',
|
|
'try', 'typeof', 'var', 'void', 'while', 'with', 'yield', 'Number', 'String', 'Boolean',
|
|
'Array', 'Map', 'document', 'Promise']
|
|
// used to generate type structs
|
|
v_types = ['i8', 'i16', 'int', 'i64', 'byte', 'u16', 'u32', 'u64', 'f32', 'f64',
|
|
'int_literal', 'float_literal', 'bool', 'string', 'map', 'array', 'rune', 'any', 'voidptr']
|
|
shallow_equatables = [ast.Kind.i8, .i16, .int, .i64, .byte, .u16, .u32, .u64, .f32, .f64,
|
|
.int_literal, .float_literal, .bool, .string]
|
|
)
|
|
|
|
struct SourcemapHelper {
|
|
src_path string
|
|
src_line u32
|
|
ns_pos u32
|
|
}
|
|
|
|
struct Namespace {
|
|
name string
|
|
mut:
|
|
pub_vars []string
|
|
imports map[string]string
|
|
indent int
|
|
methods map[string][]ast.FnDecl
|
|
sourcemap_helper []SourcemapHelper
|
|
}
|
|
|
|
[heap]
|
|
struct JsGen {
|
|
pref &pref.Preferences
|
|
mut:
|
|
table &ast.Table
|
|
definitions strings.Builder
|
|
ns &Namespace
|
|
namespaces map[string]&Namespace
|
|
doc &JsDoc
|
|
enable_doc bool
|
|
file &ast.File
|
|
tmp_count int
|
|
inside_ternary bool
|
|
inside_or bool
|
|
inside_loop bool
|
|
inside_map_set bool // map.set(key, value)
|
|
inside_builtin bool
|
|
inside_if_optional bool
|
|
generated_builtin bool
|
|
inside_def_typ_decl bool
|
|
is_test bool
|
|
stmt_start_pos int
|
|
defer_stmts []ast.DeferStmt
|
|
fn_decl &ast.FnDecl // pointer to the FnDecl we are currently inside otherwise 0
|
|
generated_str_fns []StrType
|
|
str_types []StrType // types that need automatic str() generation
|
|
copy_types []StrType // types that need to be deep copied
|
|
generated_copy_fns []StrType
|
|
array_fn_definitions []string // array equality functions that have been defined
|
|
map_fn_definitions []string // map equality functions that have been defined
|
|
struct_fn_definitions []string // struct equality functions that have been defined
|
|
sumtype_fn_definitions []string // sumtype equality functions that have been defined
|
|
alias_fn_definitions []string // alias equality functions that have been defined
|
|
auto_fn_definitions []string // auto generated functions defination list
|
|
anon_fn_definitions []string // anon generated functions defination list
|
|
copy_fn_definitions []string
|
|
method_fn_decls map[string][]ast.FnDecl
|
|
builtin_fns []string // Functions defined in `builtin`
|
|
empty_line bool
|
|
cast_stack []ast.Type
|
|
call_stack []ast.CallExpr
|
|
is_vlines_enabled bool // is it safe to generate #line directives when -g is passed
|
|
sourcemap &sourcemap.SourceMap // maps lines in generated javascrip file to original source files and line
|
|
comptime_var_type_map map[string]ast.Type
|
|
defer_ifdef string
|
|
cur_concrete_types []ast.Type
|
|
out strings.Builder = strings.new_builder(128)
|
|
array_sort_fn map[string]bool
|
|
wasm_export map[string][]string
|
|
wasm_import map[string][]string
|
|
init_global map[string]map[string]ast.Expr // initializers for constants or globals, should be invoked before module init.
|
|
}
|
|
|
|
fn (mut g JsGen) write_tests_definitions() {
|
|
g.definitions.writeln('globalThis.g_test_oks = 0;')
|
|
g.definitions.writeln('globalThis.g_test_fails = 0;')
|
|
}
|
|
|
|
pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string {
|
|
mut g := &JsGen{
|
|
definitions: strings.new_builder(100)
|
|
table: table
|
|
pref: pref
|
|
fn_decl: 0
|
|
empty_line: true
|
|
doc: 0
|
|
ns: 0
|
|
enable_doc: true
|
|
file: 0
|
|
sourcemap: 0
|
|
}
|
|
g.doc = new_jsdoc(g)
|
|
// TODO: Add '[-no]-jsdoc' flag
|
|
if pref.is_prod {
|
|
g.enable_doc = false
|
|
g.is_vlines_enabled = false
|
|
}
|
|
g.init()
|
|
mut graph := depgraph.new_dep_graph()
|
|
if g.pref.sourcemap {
|
|
mut sg := sourcemap.generate_empty_map()
|
|
g.sourcemap = sg.add_map('', '', g.pref.sourcemap_src_included, 0, 0)
|
|
}
|
|
mut tests_inited := false
|
|
|
|
// Get class methods
|
|
for file in files {
|
|
g.file = file
|
|
g.enter_namespace(g.file.mod.name)
|
|
g.is_test = g.pref.is_test
|
|
g.find_class_methods(file.stmts)
|
|
g.escape_namespace()
|
|
}
|
|
for file in files {
|
|
g.file = file
|
|
g.enter_namespace(g.file.mod.name)
|
|
if g.enable_doc {
|
|
g.writeln('/** @namespace $file.mod.name */')
|
|
}
|
|
g.is_test = g.pref.is_test
|
|
// store imports
|
|
mut imports := []string{}
|
|
for imp in g.file.imports {
|
|
imports << imp.mod
|
|
}
|
|
|
|
graph.add(g.file.mod.name, imports)
|
|
// builtin types
|
|
if g.file.mod.name == 'builtin' && !g.generated_builtin {
|
|
g.gen_builtin_type_defs()
|
|
g.writeln('Object.defineProperty(array.prototype,"len", { get: function() {return new int(this.arr.arr.length);}, set: function(l) { this.arr.arr.length = l.valueOf(); } }); ')
|
|
g.writeln('Object.defineProperty(map.prototype,"len", { get: function() {return new int(this.length);}, set: function(l) { } }); ')
|
|
g.writeln('Object.defineProperty(array.prototype,"length", { get: function() {return new int(this.arr.arr.length);}, set: function(l) { this.arr.arr.length = l.valueOf(); } }); ')
|
|
g.generated_builtin = true
|
|
}
|
|
if g.is_test && !tests_inited {
|
|
g.write_tests_definitions()
|
|
tests_inited = true
|
|
}
|
|
g.stmts(file.stmts)
|
|
// store the current namespace
|
|
g.escape_namespace()
|
|
}
|
|
for i := 0; i < g.str_types.len; i++ {
|
|
g.final_gen_str(g.str_types[i])
|
|
}
|
|
for i := 0; i < g.copy_types.len; i++ {
|
|
g.final_gen_copy(g.copy_types[i])
|
|
}
|
|
if g.pref.is_test {
|
|
g.gen_js_main_for_tests()
|
|
}
|
|
g.enter_namespace('main')
|
|
// generate JS methods for interface methods
|
|
for iface_name, iface_types in g.table.iface_types {
|
|
iface := g.table.find_sym(iface_name) or { panic('unreachable: interface must exist') }
|
|
for ty in iface_types {
|
|
sym := g.table.sym(ty)
|
|
for method in iface.methods {
|
|
p_sym := g.table.sym(ty)
|
|
|
|
mname := if p_sym.has_method(method.name) {
|
|
g.js_name(p_sym.name) + '_' + method.name
|
|
} else {
|
|
g.js_name(iface_name) + '_' + method.name
|
|
}
|
|
g.write('${g.js_name(sym.name)}.prototype.$method.name = function(')
|
|
for i, param in method.params {
|
|
if i == 0 {
|
|
continue
|
|
}
|
|
g.write('${g.js_name(param.name)}')
|
|
if i != method.params.len - 1 {
|
|
g.write(',')
|
|
}
|
|
}
|
|
g.writeln(') {')
|
|
g.inc_indent()
|
|
g.write('return ${mname}(')
|
|
for i, param in method.params {
|
|
if i == 0 {
|
|
g.write('this')
|
|
} else {
|
|
g.write('${g.js_name(param.name)}')
|
|
}
|
|
if i != method.params.len - 1 {
|
|
g.write(',')
|
|
}
|
|
}
|
|
g.writeln(')')
|
|
g.dec_indent()
|
|
g.writeln('}')
|
|
}
|
|
}
|
|
}
|
|
|
|
for mod_name in g.table.modules {
|
|
g.writeln('// Initializations for module $mod_name')
|
|
for global, expr in g.init_global[mod_name] {
|
|
g.write('$global = ')
|
|
g.expr(expr)
|
|
g.writeln(';')
|
|
}
|
|
init_fn_name := '${mod_name}.init'
|
|
if initfn := g.table.find_fn(init_fn_name) {
|
|
if initfn.return_type == ast.void_type && initfn.params.len == 0 {
|
|
mod_c_name := util.no_dots(mod_name)
|
|
init_fn_c_name := '${mod_c_name}__init'
|
|
g.writeln('${init_fn_c_name}();')
|
|
}
|
|
}
|
|
}
|
|
|
|
if !g.pref.is_shared {
|
|
if g.pref.output_es5 {
|
|
g.write('js_main();')
|
|
} else {
|
|
g.write('loadRoutine().then(_ => js_main());')
|
|
}
|
|
}
|
|
g.escape_namespace()
|
|
// resolve imports
|
|
// deps_resolved := graph.resolve()
|
|
// nodes := deps_resolved.nodes
|
|
|
|
mut out := g.definitions.str() + g.hashes()
|
|
if !g.pref.output_es5 {
|
|
out += '\nlet wasmExportObject;\n'
|
|
|
|
out += 'const loadRoutine = async () => {\n'
|
|
for mod, functions in g.wasm_import {
|
|
if g.pref.backend == .js_browser {
|
|
out += '\nawait fetch("$mod").then(respone => respone.arrayBuffer()).then(bytes => '
|
|
out += 'WebAssembly.instantiate(bytes,'
|
|
exports := g.wasm_export[mod]
|
|
out += '{ imports: { \n'
|
|
for i, exp in exports {
|
|
out += g.js_name(exp) + ':' + '\$wasm' + g.js_name(exp)
|
|
if i != exports.len - 1 {
|
|
out += ',\n'
|
|
}
|
|
}
|
|
out += '}})).then(obj => wasmExportObject = obj.instance.exports);\n'
|
|
for fun in functions {
|
|
out += 'globalThis.${g.js_name(fun)} = wasmExportObject.${g.js_name(fun)};\n'
|
|
}
|
|
} else {
|
|
verror('WebAssembly export is supported only for browser backend at the moment')
|
|
}
|
|
}
|
|
out += '}\n'
|
|
}
|
|
// equality check for js objects
|
|
// TODO: Fix msvc bug that's preventing $embed_file('fast_deep_equal.js')
|
|
// unsafe {
|
|
// mut eq_fn := $embed_file('fast_deep_equal.js')
|
|
// out += eq_fn.data().vstring()
|
|
//}
|
|
out += fast_deep_eq_fn
|
|
/*
|
|
if pref.is_shared {
|
|
// Export, through CommonJS, the module of the entry file if `-shared` was passed
|
|
export := nodes[nodes.len - 1].name
|
|
out += 'if (typeof module === "object" && module.exports) module.exports = $export;\n'
|
|
}*/
|
|
out += '\n'
|
|
|
|
out += g.out.str()
|
|
/*
|
|
TODO(playX): Again add support for these doc comments
|
|
for node in nodes {
|
|
name := g.js_name(node.name).replace('.', '_')
|
|
if g.enable_doc {
|
|
out += '/** @namespace $name */\n'
|
|
}
|
|
// out += 'const $name = (function ('
|
|
mut namespace := g.namespaces[node.name]
|
|
|
|
|
|
if g.pref.sourcemap {
|
|
// calculate current output start line
|
|
mut current_line := u32(out.count('\n') + 1)
|
|
mut sm_pos := u32(0)
|
|
for sourcemap_ns_entry in namespace.sourcemap_helper {
|
|
// calculate final generated location in output based on position
|
|
current_segment := g.out.substr(int(sm_pos), int(sourcemap_ns_entry.ns_pos))
|
|
current_line += u32(current_segment.count('\n'))
|
|
current_column := if last_nl_pos := current_segment.last_index('\n') {
|
|
u32(current_segment.len - last_nl_pos - 1)
|
|
} else {
|
|
u32(0)
|
|
}
|
|
g.sourcemap.add_mapping(sourcemap_ns_entry.src_path, sourcemap.SourcePosition{
|
|
source_line: sourcemap_ns_entry.src_line
|
|
source_column: 0 // sourcemap_ns_entry.src_column
|
|
}, current_line, current_column, '')
|
|
sm_pos = sourcemap_ns_entry.ns_pos
|
|
}
|
|
}
|
|
|
|
|
|
// public scope
|
|
out += '\n'
|
|
}*/
|
|
|
|
if g.pref.sourcemap {
|
|
out += g.create_sourcemap()
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
fn (g JsGen) create_sourcemap() string {
|
|
mut sm := g.sourcemap
|
|
mut out := '\n//# sourceMappingURL=data:application/json;base64,'
|
|
out += base64.encode(sm.to_json().str().bytes())
|
|
out += '\n'
|
|
|
|
return out
|
|
}
|
|
|
|
pub fn (mut g JsGen) gen_js_main_for_tests() {
|
|
g.enter_namespace('main')
|
|
if !g.pref.output_es5 {
|
|
g.write('async ')
|
|
}
|
|
g.writeln('function js_main() { ')
|
|
g.inc_indent()
|
|
all_tfuncs := g.get_all_test_function_names()
|
|
|
|
g.writeln('')
|
|
g.writeln('globalThis.VTEST=1')
|
|
if g.pref.is_stats {
|
|
g.writeln('let bt = main__start_testing(new int($all_tfuncs.len), new string("$g.pref.path"))')
|
|
}
|
|
for tname in all_tfuncs {
|
|
tcname := g.js_name(tname)
|
|
|
|
if g.pref.is_stats {
|
|
g.writeln('main__BenchedTests_testing_step_start(bt,new string("$tcname"))')
|
|
}
|
|
|
|
g.writeln('try { let res = ${tcname}(); if (res instanceof Promise) { await res; } } catch (_e) {} ')
|
|
if g.pref.is_stats {
|
|
g.writeln('main__BenchedTests_testing_step_end(bt);')
|
|
}
|
|
}
|
|
|
|
g.writeln('')
|
|
if g.pref.is_stats {
|
|
g.writeln('main__BenchedTests_end_testing(bt);')
|
|
}
|
|
g.dec_indent()
|
|
g.writeln('}')
|
|
g.escape_namespace()
|
|
}
|
|
|
|
fn (g &JsGen) get_all_test_function_names() []string {
|
|
mut tfuncs := []string{}
|
|
mut tsuite_begin := ''
|
|
mut tsuite_end := ''
|
|
for _, f in g.table.fns {
|
|
if !f.is_test {
|
|
continue
|
|
}
|
|
if f.name.ends_with('.testsuite_begin') {
|
|
tsuite_begin = f.name
|
|
continue
|
|
}
|
|
if f.name.contains('.test_') {
|
|
tfuncs << f.name
|
|
continue
|
|
}
|
|
if f.name.ends_with('.testsuite_end') {
|
|
tsuite_end = f.name
|
|
continue
|
|
}
|
|
}
|
|
mut all_tfuncs := []string{}
|
|
if tsuite_begin.len > 0 {
|
|
all_tfuncs << tsuite_begin
|
|
}
|
|
all_tfuncs << tfuncs
|
|
if tsuite_end.len > 0 {
|
|
all_tfuncs << tsuite_end
|
|
}
|
|
return all_tfuncs
|
|
}
|
|
|
|
pub fn (mut g JsGen) enter_namespace(name string) {
|
|
if g.namespaces[name] == 0 {
|
|
// create a new namespace
|
|
ns := &Namespace{
|
|
name: name
|
|
}
|
|
g.namespaces[name] = ns
|
|
g.ns = ns
|
|
} else {
|
|
g.ns = g.namespaces[name]
|
|
}
|
|
g.inside_builtin = name == 'builtin'
|
|
}
|
|
|
|
pub fn (mut g JsGen) escape_namespace() {
|
|
g.ns = &Namespace(0)
|
|
g.inside_builtin = false
|
|
}
|
|
|
|
pub fn (mut g JsGen) push_pub_var(s string) {
|
|
g.ns.pub_vars << g.js_name(s)
|
|
}
|
|
|
|
pub fn (mut g JsGen) find_class_methods(stmts []ast.Stmt) {
|
|
for stmt in stmts {
|
|
match stmt {
|
|
ast.FnDecl {
|
|
if stmt.is_method {
|
|
// Found struct method, store it to be generated along with the class.
|
|
mut class_name := g.table.get_type_name(stmt.receiver.typ)
|
|
// Workaround until `map[key] << val` works.
|
|
mut arr := g.method_fn_decls[class_name]
|
|
arr << stmt
|
|
g.method_fn_decls[class_name] = arr
|
|
}
|
|
}
|
|
else {}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn (mut g JsGen) init() {
|
|
g.definitions.writeln('// Generated by the V compiler\n')
|
|
// g.definitions.writeln('"use strict";')
|
|
g.definitions.writeln('')
|
|
g.definitions.writeln('var \$global = (new Function("return this"))();')
|
|
if g.pref.output_es5 {
|
|
g.definitions.writeln('globalThis = \$global;')
|
|
}
|
|
g.definitions.writeln('function \$ref(value) { if (value instanceof \$ref) { return value; } this.val = value; } ')
|
|
g.definitions.writeln('\$ref.prototype.valueOf = function() { return this.val; } ')
|
|
if g.pref.backend != .js_node {
|
|
g.definitions.writeln('const \$process = {')
|
|
g.definitions.writeln(' arch: "js",')
|
|
if g.pref.backend == .js_freestanding {
|
|
g.definitions.writeln(' platform: "freestanding",')
|
|
} else {
|
|
g.definitions.writeln(' platform: "browser",')
|
|
}
|
|
g.definitions.writeln(' cwd: function() { return "" }')
|
|
g.definitions.writeln('}')
|
|
|
|
g.definitions.writeln('const \$os = {')
|
|
g.definitions.writeln(' endianess: "LE",')
|
|
g.definitions.writeln('}')
|
|
} else {
|
|
g.definitions.writeln('const \$os = require("os");')
|
|
g.definitions.writeln('const \$process = process;')
|
|
}
|
|
g.definitions.writeln('function checkDefine(key) {')
|
|
g.definitions.writeln('\tif (globalThis.hasOwnProperty(key)) { return !!globalThis[key]; } return false;')
|
|
g.definitions.writeln('}')
|
|
|
|
g.definitions.writeln('function BreakException() {}')
|
|
g.definitions.writeln('function ContinueException() {}')
|
|
g.definitions.writeln('function ReturnException(val) { this.val = val; }')
|
|
}
|
|
|
|
pub fn (g JsGen) hashes() string {
|
|
mut res := '// V_COMMIT_HASH $version.vhash()\n'
|
|
res += '// V_CURRENT_COMMIT_HASH ${version.githash(g.pref.building_v)}\n'
|
|
return res
|
|
}
|
|
|
|
[noreturn]
|
|
fn verror(msg string) {
|
|
eprintln('jsgen error: $msg')
|
|
exit(1)
|
|
}
|
|
|
|
[inline]
|
|
pub fn (mut g JsGen) gen_indent() {
|
|
if g.ns.indent > 0 && g.empty_line {
|
|
g.out.write_string(util.tabs(g.ns.indent))
|
|
}
|
|
g.empty_line = false
|
|
}
|
|
|
|
[inline]
|
|
pub fn (mut g JsGen) inc_indent() {
|
|
g.ns.indent++
|
|
}
|
|
|
|
[inline]
|
|
pub fn (mut g JsGen) dec_indent() {
|
|
g.ns.indent--
|
|
}
|
|
|
|
[inline]
|
|
pub fn (mut g JsGen) write(s string) {
|
|
if g.ns == 0 {
|
|
verror('g.write: not in a namespace')
|
|
}
|
|
g.gen_indent()
|
|
g.out.write_string(s)
|
|
}
|
|
|
|
[inline]
|
|
pub fn (mut g JsGen) writeln(s string) {
|
|
if g.ns == 0 {
|
|
verror('g.writeln: not in a namespace')
|
|
}
|
|
g.gen_indent()
|
|
g.out.writeln(s)
|
|
g.empty_line = true
|
|
}
|
|
|
|
[inline]
|
|
pub fn (mut g JsGen) new_tmp_var() string {
|
|
g.tmp_count++
|
|
return '_tmp$g.tmp_count'
|
|
}
|
|
|
|
// 'mod1.mod2.fn' => 'mod1.mod2'
|
|
// 'fn' => ''
|
|
[inline]
|
|
fn get_ns(s string) string {
|
|
idx := s.last_index('.') or { return '' }
|
|
return s.substr(0, idx)
|
|
}
|
|
|
|
fn (mut g JsGen) get_alias(name string) string {
|
|
ns := get_ns(name)
|
|
if ns == '' {
|
|
return name
|
|
}
|
|
alias := g.ns.imports[ns]
|
|
if alias == '' {
|
|
return name
|
|
}
|
|
return alias + '.' + name.split('.').last()
|
|
}
|
|
|
|
fn (mut g JsGen) js_name(name_ string) string {
|
|
mut name := name_
|
|
if name.starts_with('JS.') {
|
|
name = name[3..]
|
|
return name
|
|
}
|
|
name = name_.replace('.', '__')
|
|
if name in js.js_reserved {
|
|
return '_v_$name'
|
|
}
|
|
return name
|
|
}
|
|
|
|
fn (mut g JsGen) stmts(stmts []ast.Stmt) {
|
|
for stmt in stmts {
|
|
g.stmt(stmt)
|
|
}
|
|
}
|
|
|
|
[inline]
|
|
fn (mut g JsGen) write_v_source_line_info(pos token.Pos) {
|
|
// g.inside_ternary == 0 &&
|
|
if g.pref.sourcemap {
|
|
g.ns.sourcemap_helper << SourcemapHelper{
|
|
src_path: util.vlines_escape_path(g.file.path, g.pref.ccompiler)
|
|
src_line: u32(pos.line_nr + 1)
|
|
ns_pos: u32(g.out.len)
|
|
}
|
|
}
|
|
if g.pref.is_vlines && g.is_vlines_enabled {
|
|
g.write(' /* ${pos.line_nr + 1} $g.out.len */ ')
|
|
}
|
|
}
|
|
|
|
fn (mut g JsGen) gen_global_decl(node ast.GlobalDecl) {
|
|
mod := if g.pref.build_mode == .build_module { 'enumerable: false' } else { 'enumerable: true' }
|
|
for field in node.fields {
|
|
if field.has_expr {
|
|
tmp_var := g.new_tmp_var()
|
|
g.write('const $tmp_var = ')
|
|
g.expr(field.expr)
|
|
g.writeln(';')
|
|
g.writeln('Object.defineProperty(\$global,"$field.name", {
|
|
configurable: false,
|
|
$mod ,
|
|
writable: true,
|
|
value: $tmp_var
|
|
}
|
|
); // global')
|
|
} else {
|
|
// TODO(playXE): Initialize with default value of type
|
|
|
|
if field.typ.is_ptr() {
|
|
g.writeln('Object.defineProperty(\$global,"$field.name", {
|
|
configurable: false,
|
|
$mod ,
|
|
writable: true,
|
|
value: new \$ref({})
|
|
}
|
|
); // global')
|
|
} else {
|
|
g.writeln('Object.defineProperty(\$global,"$field.name", {
|
|
configurable: false,
|
|
$mod ,
|
|
writable: true,
|
|
value: {}
|
|
}
|
|
); // global')
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn (mut g JsGen) gen_alias_type_decl(node ast.AliasTypeDecl) {
|
|
name := if g.ns.name == 'builtin' { node.name } else { '${g.js_name(g.ns.name)}__$node.name' }
|
|
g.writeln('function ${name}(val) { return val; }')
|
|
}
|
|
|
|
fn (mut g JsGen) stmt_no_semi(node_ ast.Stmt) {
|
|
g.stmt_start_pos = g.out.len
|
|
mut node := unsafe { node_ }
|
|
match mut node {
|
|
ast.EmptyStmt {}
|
|
ast.AsmStmt {
|
|
panic('inline asm is not supported by js')
|
|
}
|
|
ast.AssertStmt {
|
|
g.write_v_source_line_info(node.pos)
|
|
g.gen_assert_stmt(mut node)
|
|
}
|
|
ast.AssignStmt {
|
|
g.write_v_source_line_info(node.pos)
|
|
g.gen_assign_stmt(node, false)
|
|
}
|
|
ast.Block {
|
|
g.write_v_source_line_info(node.pos)
|
|
g.gen_block(node)
|
|
g.writeln('')
|
|
}
|
|
ast.BranchStmt {
|
|
g.write_v_source_line_info(node.pos)
|
|
g.gen_branch_stmt(node)
|
|
}
|
|
ast.ComptimeFor {}
|
|
ast.ConstDecl {
|
|
g.write_v_source_line_info(node.pos)
|
|
g.gen_const_decl(node)
|
|
}
|
|
ast.DeferStmt {
|
|
g.defer_stmts << node
|
|
}
|
|
ast.EnumDecl {
|
|
g.write_v_source_line_info(node.pos)
|
|
g.gen_enum_decl(node)
|
|
g.writeln('')
|
|
}
|
|
ast.ExprStmt {
|
|
g.write_v_source_line_info(node.pos)
|
|
g.gen_expr_stmt_no_semi(node)
|
|
}
|
|
ast.FnDecl {
|
|
g.write_v_source_line_info(node.pos)
|
|
|
|
g.gen_fn_decl(node)
|
|
}
|
|
ast.ForCStmt {
|
|
g.write_v_source_line_info(node.pos)
|
|
g.gen_for_c_stmt(node)
|
|
g.writeln('')
|
|
}
|
|
ast.ForInStmt {
|
|
g.write_v_source_line_info(node.pos)
|
|
g.gen_for_in_stmt(node)
|
|
g.writeln('')
|
|
}
|
|
ast.ForStmt {
|
|
g.write_v_source_line_info(node.pos)
|
|
g.gen_for_stmt(node)
|
|
g.writeln('')
|
|
}
|
|
ast.GlobalDecl {
|
|
g.write_v_source_line_info(node.pos)
|
|
g.gen_global_decl(node)
|
|
g.writeln('')
|
|
}
|
|
ast.GotoLabel {
|
|
g.write_v_source_line_info(node.pos)
|
|
g.writeln('${g.js_name(node.name)}:')
|
|
}
|
|
ast.GotoStmt {
|
|
// skip: JS has no goto
|
|
}
|
|
ast.HashStmt {
|
|
g.write_v_source_line_info(node.pos)
|
|
g.gen_hash_stmt(node)
|
|
}
|
|
ast.Import {
|
|
g.ns.imports[node.mod] = node.alias
|
|
}
|
|
ast.InterfaceDecl {
|
|
g.write_v_source_line_info(node.pos)
|
|
g.gen_interface_decl(node)
|
|
}
|
|
ast.Module {
|
|
// skip: namespacing implemented externally
|
|
}
|
|
ast.NodeError {}
|
|
ast.Return {
|
|
if g.defer_stmts.len > 0 {
|
|
g.gen_defer_stmts()
|
|
}
|
|
g.gen_return_stmt(node)
|
|
}
|
|
ast.SqlStmt {}
|
|
ast.StructDecl {
|
|
g.write_v_source_line_info(node.pos)
|
|
g.gen_struct_decl(node)
|
|
}
|
|
ast.TypeDecl {}
|
|
}
|
|
}
|
|
|
|
fn (mut g JsGen) stmt(node_ ast.Stmt) {
|
|
g.stmt_start_pos = g.out.len
|
|
mut node := unsafe { node_ }
|
|
match mut node {
|
|
ast.EmptyStmt {}
|
|
ast.AsmStmt {
|
|
panic('inline asm is not supported by js')
|
|
}
|
|
ast.AssertStmt {
|
|
g.write_v_source_line_info(node.pos)
|
|
g.gen_assert_stmt(mut node)
|
|
}
|
|
ast.AssignStmt {
|
|
g.write_v_source_line_info(node.pos)
|
|
g.gen_assign_stmt(node, true)
|
|
}
|
|
ast.Block {
|
|
g.write_v_source_line_info(node.pos)
|
|
g.gen_block(node)
|
|
g.writeln('')
|
|
}
|
|
ast.BranchStmt {
|
|
g.write_v_source_line_info(node.pos)
|
|
g.gen_branch_stmt(node)
|
|
}
|
|
ast.ComptimeFor {}
|
|
ast.ConstDecl {
|
|
g.write_v_source_line_info(node.pos)
|
|
g.gen_const_decl(node)
|
|
}
|
|
ast.DeferStmt {
|
|
g.defer_stmts << node
|
|
}
|
|
ast.EnumDecl {
|
|
g.write_v_source_line_info(node.pos)
|
|
g.gen_enum_decl(node)
|
|
g.writeln('')
|
|
}
|
|
ast.ExprStmt {
|
|
g.write_v_source_line_info(node.pos)
|
|
g.gen_expr_stmt(node)
|
|
}
|
|
ast.FnDecl {
|
|
g.write_v_source_line_info(node.pos)
|
|
g.gen_fn_decl(node)
|
|
}
|
|
ast.ForCStmt {
|
|
g.write_v_source_line_info(node.pos)
|
|
g.gen_for_c_stmt(node)
|
|
g.writeln('')
|
|
}
|
|
ast.ForInStmt {
|
|
g.write_v_source_line_info(node.pos)
|
|
g.gen_for_in_stmt(node)
|
|
g.writeln('')
|
|
}
|
|
ast.ForStmt {
|
|
g.write_v_source_line_info(node.pos)
|
|
g.gen_for_stmt(node)
|
|
g.writeln('')
|
|
}
|
|
ast.GlobalDecl {
|
|
g.write_v_source_line_info(node.pos)
|
|
g.gen_global_decl(node)
|
|
g.writeln('')
|
|
}
|
|
ast.GotoLabel {
|
|
g.write_v_source_line_info(node.pos)
|
|
g.writeln('${g.js_name(node.name)}:')
|
|
}
|
|
ast.GotoStmt {
|
|
// skip: JS has no goto
|
|
}
|
|
ast.HashStmt {
|
|
g.write_v_source_line_info(node.pos)
|
|
g.gen_hash_stmt(node)
|
|
}
|
|
ast.Import {
|
|
g.ns.imports[node.mod] = node.alias
|
|
}
|
|
ast.InterfaceDecl {
|
|
g.write_v_source_line_info(node.pos)
|
|
g.gen_interface_decl(node)
|
|
}
|
|
ast.Module {
|
|
// skip: namespacing implemented externally
|
|
}
|
|
ast.NodeError {}
|
|
ast.Return {
|
|
if g.defer_stmts.len > 0 {
|
|
g.gen_defer_stmts()
|
|
}
|
|
g.gen_return_stmt(node)
|
|
}
|
|
ast.SqlStmt {}
|
|
ast.StructDecl {
|
|
g.write_v_source_line_info(node.pos)
|
|
g.gen_struct_decl(node)
|
|
}
|
|
ast.TypeDecl {
|
|
match mut node {
|
|
ast.AliasTypeDecl {
|
|
g.gen_alias_type_decl(node)
|
|
}
|
|
else {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn (mut g JsGen) expr(node_ ast.Expr) {
|
|
// Note: please keep the type names in the match here in alphabetical order:
|
|
mut node := unsafe { node_ }
|
|
match mut node {
|
|
ast.ComptimeType {
|
|
verror('not yet implemented')
|
|
}
|
|
ast.EmptyExpr {}
|
|
ast.AnonFn {
|
|
g.gen_anon_fn(mut node)
|
|
}
|
|
ast.ArrayDecompose {}
|
|
ast.ArrayInit {
|
|
g.gen_array_init_expr(node)
|
|
}
|
|
ast.AsCast {
|
|
// skip: JS has no types, so no need to cast
|
|
// TODO: Is jsdoc needed here for TS support?
|
|
}
|
|
ast.Assoc {
|
|
// TODO
|
|
}
|
|
ast.AtExpr {
|
|
g.write('"$node.val"')
|
|
}
|
|
ast.BoolLiteral {
|
|
g.write('new bool(')
|
|
if node.val == true {
|
|
g.write('true')
|
|
} else {
|
|
g.write('false')
|
|
}
|
|
g.write(')')
|
|
}
|
|
ast.CallExpr {
|
|
g.gen_call_expr(node)
|
|
}
|
|
ast.CastExpr {
|
|
g.gen_type_cast_expr(node)
|
|
}
|
|
ast.ChanInit {
|
|
// TODO
|
|
}
|
|
ast.CharLiteral {
|
|
if utf8_str_len(node.val) < node.val.len {
|
|
g.write("new rune('$node.val'.charCodeAt())")
|
|
} else {
|
|
g.write("new u8('$node.val')")
|
|
}
|
|
}
|
|
ast.Comment {}
|
|
ast.ComptimeCall {
|
|
// TODO
|
|
}
|
|
ast.ComptimeSelector {
|
|
// TODO
|
|
}
|
|
ast.ConcatExpr {
|
|
// TODO
|
|
}
|
|
ast.CTempVar {
|
|
g.write('$node.name')
|
|
}
|
|
ast.DumpExpr {
|
|
g.write('/* ast.DumpExpr: $node.expr */')
|
|
}
|
|
ast.EnumVal {
|
|
sym := g.table.sym(node.typ)
|
|
styp := g.js_name(sym.name)
|
|
g.write('${styp}.$node.val')
|
|
}
|
|
ast.FloatLiteral {
|
|
g.gen_float_literal_expr(node)
|
|
}
|
|
ast.GoExpr {
|
|
g.gen_go_expr(node)
|
|
}
|
|
ast.Ident {
|
|
g.gen_ident(node)
|
|
}
|
|
ast.IfExpr {
|
|
g.gen_if_expr(node)
|
|
}
|
|
ast.IfGuardExpr {
|
|
// TODO no optionals yet
|
|
}
|
|
ast.IndexExpr {
|
|
g.gen_index_expr(node)
|
|
}
|
|
ast.InfixExpr {
|
|
g.infix_expr(node)
|
|
}
|
|
ast.IntegerLiteral {
|
|
g.gen_integer_literal_expr(node)
|
|
}
|
|
ast.Likely {
|
|
g.write('(')
|
|
g.expr(node.expr)
|
|
g.write(')')
|
|
}
|
|
ast.LockExpr {
|
|
g.gen_lock_expr(node)
|
|
}
|
|
ast.NodeError {}
|
|
ast.None {
|
|
g.write('none__')
|
|
}
|
|
ast.MapInit {
|
|
g.gen_map_init_expr(node)
|
|
}
|
|
ast.MatchExpr {
|
|
g.match_expr(node)
|
|
}
|
|
ast.OrExpr {
|
|
// TODO
|
|
}
|
|
ast.ParExpr {
|
|
g.write('(')
|
|
g.expr(node.expr)
|
|
g.write(')')
|
|
}
|
|
ast.PostfixExpr {
|
|
// match node.expr {
|
|
// ast.IndexExpr {
|
|
// g.gen_postfix_index_expr(node.expr,node.op)
|
|
// } else {
|
|
g.expr(node.expr)
|
|
if node.op in [.inc, .dec] {
|
|
g.write('.val $node.op')
|
|
} else {
|
|
g.write(node.op.str())
|
|
}
|
|
// }
|
|
// }
|
|
}
|
|
ast.PrefixExpr {
|
|
if node.op in [.amp, .mul] {
|
|
if node.op == .amp {
|
|
// if !node.right_type.is_pointer() {
|
|
// kind of weird way to handle references but it allows us to access type methods easily.
|
|
/*
|
|
g.write('(function(x) {')
|
|
g.write(' return { val: x, __proto__: Object.getPrototypeOf(x), valueOf: function() { return this.val; } }})( ')
|
|
g.expr(node.right)
|
|
g.write(')')*/
|
|
g.write('new \$ref(')
|
|
g.expr(node.right)
|
|
g.write(')')
|
|
//} else {
|
|
// g.expr(node.right)
|
|
// }
|
|
} else {
|
|
g.write('(')
|
|
g.expr(node.right)
|
|
g.write(').valueOf()')
|
|
}
|
|
} else {
|
|
g.write(node.op.str())
|
|
g.expr(node.right)
|
|
g.write('.val ')
|
|
}
|
|
}
|
|
ast.RangeExpr {
|
|
// Only used in IndexExpr, requires index type info
|
|
}
|
|
ast.SelectExpr {
|
|
// TODO: to be implemented
|
|
}
|
|
ast.SelectorExpr {
|
|
g.gen_selector_expr(node)
|
|
}
|
|
ast.SizeOf, ast.IsRefType {
|
|
// TODO
|
|
}
|
|
ast.OffsetOf {
|
|
// TODO
|
|
}
|
|
ast.SqlExpr {
|
|
// TODO
|
|
}
|
|
ast.StringInterLiteral {
|
|
g.gen_string_inter_literal(node)
|
|
}
|
|
ast.StringLiteral {
|
|
g.gen_string_literal(node)
|
|
}
|
|
ast.StructInit {
|
|
if node.unresolved {
|
|
resolved := ast.resolve_init(node, g.unwrap_generic(node.typ), g.table)
|
|
g.expr(resolved)
|
|
} else {
|
|
g.gen_struct_init(node)
|
|
}
|
|
}
|
|
ast.TypeNode {
|
|
typ := g.unwrap_generic(node.typ)
|
|
sym := g.table.sym(typ)
|
|
|
|
g.write('${g.js_name(sym.name)}')
|
|
}
|
|
ast.TypeOf {
|
|
g.gen_typeof_expr(node)
|
|
// TODO: Should this print the V type or the JS type?
|
|
}
|
|
ast.UnsafeExpr {
|
|
g.expr(node.expr)
|
|
}
|
|
}
|
|
}
|
|
|
|
struct UnsupportedAssertCtempTransform {
|
|
Error
|
|
}
|
|
|
|
const unsupported_ctemp_assert_transform = IError(UnsupportedAssertCtempTransform{})
|
|
|
|
fn (mut g JsGen) assert_subexpression_to_ctemp(expr ast.Expr, expr_type ast.Type) ?ast.Expr {
|
|
match expr {
|
|
ast.CallExpr {
|
|
return g.new_ctemp_var_then_gen(expr, expr_type)
|
|
}
|
|
ast.ParExpr {
|
|
if expr.expr is ast.CallExpr {
|
|
return g.new_ctemp_var_then_gen(expr.expr, expr_type)
|
|
}
|
|
}
|
|
ast.SelectorExpr {
|
|
if expr.expr is ast.CallExpr {
|
|
sym := g.table.final_sym(g.unwrap_generic(expr.expr.return_type))
|
|
if sym.kind == .struct_ {
|
|
if (sym.info as ast.Struct).is_union {
|
|
return js.unsupported_ctemp_assert_transform
|
|
}
|
|
}
|
|
return g.new_ctemp_var_then_gen(expr, expr_type)
|
|
}
|
|
}
|
|
else {}
|
|
}
|
|
return js.unsupported_ctemp_assert_transform
|
|
}
|
|
|
|
fn (mut g JsGen) new_ctemp_var(expr ast.Expr, expr_type ast.Type) ast.CTempVar {
|
|
return ast.CTempVar{
|
|
name: g.new_tmp_var()
|
|
typ: expr_type
|
|
is_ptr: expr_type.is_ptr()
|
|
orig: expr
|
|
}
|
|
}
|
|
|
|
fn (mut g JsGen) new_ctemp_var_then_gen(expr ast.Expr, expr_type ast.Type) ast.CTempVar {
|
|
x := g.new_ctemp_var(expr, expr_type)
|
|
g.gen_ctemp_var(x)
|
|
return x
|
|
}
|
|
|
|
fn (mut g JsGen) gen_ctemp_var(tvar ast.CTempVar) {
|
|
g.write('let $tvar.name = ')
|
|
g.expr(tvar.orig)
|
|
g.writeln(';')
|
|
}
|
|
|
|
fn (mut g JsGen) gen_assert_metainfo(node ast.AssertStmt) string {
|
|
mod_path := g.file.path
|
|
fn_name := if g.fn_decl == voidptr(0) || g.fn_decl.is_anon { 'anon' } else { g.fn_decl.name }
|
|
line_nr := node.pos.line_nr
|
|
src := node.expr.str()
|
|
metaname := 'v_assert_meta_info_$g.new_tmp_var()'
|
|
g.writeln('let $metaname = {}')
|
|
g.writeln('${metaname}.fpath = new string("$mod_path");')
|
|
g.writeln('${metaname}.line_nr = new int("$line_nr")')
|
|
g.writeln('${metaname}.fn_name = new string("$fn_name")')
|
|
metasrc := src
|
|
g.writeln('${metaname}.src = "$metasrc"')
|
|
|
|
match node.expr {
|
|
ast.InfixExpr {
|
|
expr_op_str := node.expr.op.str()
|
|
expr_left_str := node.expr.left.str()
|
|
expr_right_str := node.expr.right.str()
|
|
g.writeln('\t${metaname}.op = new string("$expr_op_str");')
|
|
g.writeln('\t${metaname}.llabel = new string("$expr_left_str");')
|
|
g.writeln('\t${metaname}.rlabel = new string("$expr_right_str");')
|
|
g.write('\t${metaname}.lvalue = ')
|
|
g.gen_assert_single_expr(node.expr.left, node.expr.left_type)
|
|
g.writeln(';')
|
|
g.write('\t${metaname}.rvalue = ')
|
|
g.gen_assert_single_expr(node.expr.right, node.expr.right_type)
|
|
g.writeln(';')
|
|
}
|
|
ast.CallExpr {
|
|
g.writeln('\t${metaname}.op = new string("call");')
|
|
}
|
|
else {}
|
|
}
|
|
return metaname
|
|
}
|
|
|
|
fn (mut g JsGen) gen_assert_single_expr(expr ast.Expr, typ ast.Type) {
|
|
// eprintln('> gen_assert_single_expr typ: $typ | expr: $expr | typeof(expr): ${typeof(expr)}')
|
|
unknown_value := '*unknown value*'
|
|
match expr {
|
|
ast.CastExpr, ast.IfExpr, ast.IndexExpr, ast.MatchExpr {
|
|
g.write('new string("$unknown_value")')
|
|
}
|
|
ast.PrefixExpr {
|
|
if expr.right is ast.CastExpr {
|
|
// TODO: remove this check;
|
|
// vlib/builtin/map_test.v (a map of &int, set to &int(0)) fails
|
|
// without special casing ast.CastExpr here
|
|
g.write('new string("$unknown_value")')
|
|
} else {
|
|
g.gen_expr_to_string(expr, typ)
|
|
}
|
|
}
|
|
ast.TypeNode {
|
|
sym := g.table.sym(g.unwrap_generic(typ))
|
|
g.write('new string("$sym.name"')
|
|
}
|
|
else {
|
|
mut should_clone := true
|
|
if typ == ast.string_type && expr is ast.StringLiteral {
|
|
should_clone = false
|
|
}
|
|
if expr is ast.CTempVar {
|
|
if expr.orig is ast.CallExpr {
|
|
should_clone = false
|
|
if expr.orig.or_block.kind == .propagate {
|
|
should_clone = true
|
|
}
|
|
if expr.orig.is_method && expr.orig.args.len == 0
|
|
&& expr.orig.name == 'type_name' {
|
|
should_clone = true
|
|
}
|
|
}
|
|
}
|
|
if should_clone {
|
|
g.write('string_clone(')
|
|
}
|
|
g.gen_expr_to_string(expr, typ)
|
|
if should_clone {
|
|
g.write(')')
|
|
}
|
|
}
|
|
}
|
|
// g.writeln(' /* typeof: ' + expr.type_name() + ' type: ' + typ.str() + ' */ ')
|
|
}
|
|
|
|
// TODO
|
|
fn (mut g JsGen) gen_assert_stmt(mut node ast.AssertStmt) {
|
|
if !node.is_used {
|
|
return
|
|
}
|
|
|
|
g.writeln('// assert')
|
|
|
|
if mut node.expr is ast.InfixExpr {
|
|
if subst_expr := g.assert_subexpression_to_ctemp(node.expr.left, node.expr.left_type) {
|
|
node.expr.left = subst_expr
|
|
}
|
|
if subst_expr := g.assert_subexpression_to_ctemp(node.expr.right, node.expr.right_type) {
|
|
node.expr.right = subst_expr
|
|
}
|
|
}
|
|
|
|
g.write('if( ')
|
|
g.expr(node.expr)
|
|
g.write('.valueOf() ) {')
|
|
s_assertion := node.expr.str().replace('"', "'")
|
|
mut mod_path := g.file.path.replace('\\', '\\\\')
|
|
if g.is_test {
|
|
metaname_ok := g.gen_assert_metainfo(node)
|
|
g.writeln(' g_test_oks++;')
|
|
g.writeln(' main__cb_assertion_ok($metaname_ok);')
|
|
g.writeln('} else {')
|
|
metaname_fail := g.gen_assert_metainfo(node)
|
|
g.writeln(' g_test_fails++;')
|
|
g.writeln(' main__cb_assertion_failed($metaname_fail);')
|
|
g.writeln(' builtin__exit(1);')
|
|
g.writeln('}')
|
|
return
|
|
}
|
|
g.writeln('} else {')
|
|
g.inc_indent()
|
|
fname := if g.fn_decl == voidptr(0) || g.fn_decl.is_anon { 'anon' } else { g.fn_decl.name }
|
|
g.writeln('builtin__eprintln(new string("$mod_path:${node.pos.line_nr + 1}: FAIL: fn ${fname}(): assert $s_assertion"));')
|
|
g.writeln('builtin__exit(1);')
|
|
g.dec_indent()
|
|
g.writeln('}')
|
|
}
|
|
|
|
fn (mut g JsGen) gen_assign_stmt(stmt ast.AssignStmt, semicolon bool) {
|
|
if stmt.left.len > stmt.right.len {
|
|
// multi return
|
|
g.write('let [')
|
|
for i, left in stmt.left {
|
|
if !left.is_blank_ident() {
|
|
g.expr(left)
|
|
}
|
|
if i < stmt.left.len - 1 {
|
|
g.write(', ')
|
|
}
|
|
}
|
|
g.write('] = ')
|
|
g.expr(stmt.right[0])
|
|
if semicolon {
|
|
g.writeln(';')
|
|
}
|
|
} else {
|
|
// `a := 1` | `a,b := 1,2`
|
|
for i, left in stmt.left {
|
|
mut op := stmt.op
|
|
if stmt.op == .decl_assign {
|
|
op = .assign
|
|
}
|
|
is_assign := stmt.op in [.plus_assign, .minus_assign, .mult_assign, .div_assign,
|
|
.xor_assign, .mod_assign, .or_assign, .and_assign, .right_shift_assign,
|
|
.left_shift_assign]
|
|
|
|
val := stmt.right[i]
|
|
mut is_mut := false
|
|
if left is ast.Ident {
|
|
is_mut = left.is_mut
|
|
if left.kind == .blank_ident || left.name in ['', '_'] {
|
|
tmp_var := g.new_tmp_var()
|
|
// TODO: Can the tmp_var declaration be omitted?
|
|
g.write('const $tmp_var = ')
|
|
g.expr(val)
|
|
g.writeln(';')
|
|
continue
|
|
}
|
|
}
|
|
|
|
mut styp := if stmt.left_types.len > i { g.typ(stmt.left_types[i]) } else { '' }
|
|
// l_sym := g.table.sym(stmt.left_types[i])
|
|
if !g.inside_loop && styp.len > 0 {
|
|
g.doc.gen_typ(styp)
|
|
}
|
|
if stmt.op == .decl_assign {
|
|
if g.inside_loop || is_mut {
|
|
g.write('let ')
|
|
} else {
|
|
g.write('const ')
|
|
}
|
|
}
|
|
mut array_set := false
|
|
mut map_set := false
|
|
match left {
|
|
ast.IndexExpr {
|
|
g.expr(left.left)
|
|
if left.left_type.is_ptr() {
|
|
g.write('.valueOf()')
|
|
}
|
|
array_set = true
|
|
|
|
if g.table.sym(left.left_type).kind == .map {
|
|
g.writeln('.length++;')
|
|
g.expr(left.left)
|
|
g.write('.map[')
|
|
map_set = true
|
|
} else {
|
|
g.write('.arr.set(')
|
|
}
|
|
if map_set {
|
|
g.expr(left.index)
|
|
g.write('.\$toJS()] = ')
|
|
} else {
|
|
g.write('new int(')
|
|
g.cast_stack << ast.int_type_idx
|
|
g.expr(left.index)
|
|
g.write('.valueOf()')
|
|
g.cast_stack.delete_last()
|
|
g.write('),')
|
|
}
|
|
}
|
|
else {
|
|
g.expr(left)
|
|
}
|
|
}
|
|
mut is_ptr := false
|
|
if stmt.op == .assign && stmt.left_types[i].is_ptr() && !array_set {
|
|
is_ptr = true
|
|
g.write('.val')
|
|
}
|
|
mut floor := false
|
|
if false && g.inside_map_set && op == .assign {
|
|
g.inside_map_set = false
|
|
g.write('] = ')
|
|
g.expr(val)
|
|
if is_ptr {
|
|
g.write('.val')
|
|
}
|
|
} else {
|
|
if is_assign && array_set {
|
|
g.write('new ${styp}(')
|
|
|
|
g.expr(left)
|
|
l_sym := g.table.sym(stmt.left_types[i])
|
|
if l_sym.kind == .string {
|
|
g.write('.str')
|
|
} else {
|
|
g.write('.val')
|
|
}
|
|
|
|
match op {
|
|
.plus_assign {
|
|
g.write(' + ')
|
|
}
|
|
.minus_assign {
|
|
g.write(' - ')
|
|
}
|
|
.mult_assign {
|
|
g.write(' * ')
|
|
}
|
|
.div_assign {
|
|
g.write(' / ')
|
|
}
|
|
.mod_assign {
|
|
g.write(' % ')
|
|
}
|
|
.xor_assign {
|
|
g.write(' ^ ')
|
|
}
|
|
.and_assign {
|
|
g.write(' & ')
|
|
}
|
|
.right_shift_assign {
|
|
g.write(' >> ')
|
|
}
|
|
.left_shift_assign {
|
|
g.write(' << ')
|
|
}
|
|
.or_assign {
|
|
g.write(' | ')
|
|
}
|
|
else {
|
|
panic('unexpected op $op')
|
|
}
|
|
}
|
|
} else if is_assign && !array_set {
|
|
l_sym := g.table.sym(stmt.left_types[i])
|
|
if l_sym.kind == .string {
|
|
g.write('.str')
|
|
} else {
|
|
g.write('.val')
|
|
}
|
|
|
|
if !array_set {
|
|
g.write(' = ')
|
|
}
|
|
if (l_sym.name != 'f64' || l_sym.name != 'f32')
|
|
&& (l_sym.name != 'i64' && l_sym.name != 'u64') && l_sym.name != 'string' {
|
|
g.write('Math.floor(')
|
|
floor = true
|
|
}
|
|
g.expr(left)
|
|
|
|
match op {
|
|
.plus_assign {
|
|
g.write(' + ')
|
|
}
|
|
.minus_assign {
|
|
g.write(' - ')
|
|
}
|
|
.mult_assign {
|
|
g.write(' * ')
|
|
}
|
|
.div_assign {
|
|
g.write(' / ')
|
|
}
|
|
.mod_assign {
|
|
g.write(' % ')
|
|
}
|
|
.xor_assign {
|
|
g.write(' ^ ')
|
|
}
|
|
.and_assign {
|
|
g.write(' & ')
|
|
}
|
|
.right_shift_assign {
|
|
g.write(' >> ')
|
|
}
|
|
.left_shift_assign {
|
|
g.write(' << ')
|
|
}
|
|
.or_assign {
|
|
g.write(' | ')
|
|
}
|
|
else {
|
|
panic('unexpected op $op')
|
|
}
|
|
}
|
|
} else {
|
|
if op == .assign && array_set {
|
|
} else {
|
|
g.write(' $op ')
|
|
}
|
|
}
|
|
// TODO: Multiple types??
|
|
|
|
should_cast := if stmt.left_types.len == 0 {
|
|
false
|
|
} else {
|
|
(g.table.type_kind(stmt.left_types.first()) in js.shallow_equatables)
|
|
&& (g.cast_stack.len <= 0 || stmt.left_types.first() != g.cast_stack.last())
|
|
}
|
|
|
|
if should_cast {
|
|
g.cast_stack << stmt.left_types.first()
|
|
g.write('new ')
|
|
g.write('${g.typ(stmt.left_types.first())}(')
|
|
}
|
|
g.expr(val)
|
|
if is_ptr {
|
|
g.write('.val')
|
|
}
|
|
if should_cast {
|
|
g.write(')')
|
|
g.cast_stack.delete_last()
|
|
}
|
|
if is_assign && array_set {
|
|
g.write(')')
|
|
}
|
|
if floor {
|
|
g.write(')')
|
|
}
|
|
}
|
|
if array_set && !map_set {
|
|
g.write(')')
|
|
}
|
|
if semicolon {
|
|
if g.inside_loop {
|
|
g.write('; ')
|
|
} else {
|
|
g.writeln(';')
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn (mut g JsGen) gen_attrs(attrs []ast.Attr) {
|
|
for attr in attrs {
|
|
g.writeln('/* [$attr.name] */')
|
|
}
|
|
}
|
|
|
|
fn (mut g JsGen) gen_block(it ast.Block) {
|
|
g.writeln('{')
|
|
g.inc_indent()
|
|
g.stmts(it.stmts)
|
|
g.dec_indent()
|
|
g.writeln('}')
|
|
}
|
|
|
|
fn (mut g JsGen) gen_branch_stmt(it ast.BranchStmt) {
|
|
// continue or break
|
|
if g.inside_or {
|
|
match it.kind {
|
|
.key_break {
|
|
g.writeln('throw new BreakException();')
|
|
}
|
|
.key_continue {
|
|
g.writeln('throw new ContinueException();')
|
|
}
|
|
else {
|
|
verror('unexpected branch stmt: $it.kind')
|
|
}
|
|
}
|
|
return
|
|
}
|
|
g.write(it.kind.str())
|
|
g.writeln(';')
|
|
}
|
|
|
|
fn (mut g JsGen) gen_const_decl(it ast.ConstDecl) {
|
|
for field in it.fields {
|
|
g.doc.gen_const(g.typ(field.typ))
|
|
if field.is_pub {
|
|
g.push_pub_var(field.name)
|
|
}
|
|
|
|
if field.expr is ast.StringInterLiteral || field.expr is ast.StringLiteral
|
|
|| field.expr is ast.IntegerLiteral || field.expr is ast.FloatLiteral
|
|
|| field.expr is ast.BoolLiteral {
|
|
g.write('const ${g.js_name(field.name)} = ')
|
|
g.expr(field.expr)
|
|
} else {
|
|
g.write('let ${g.js_name(field.name)} = ')
|
|
g.write('undefined')
|
|
g.init_global[g.ns.name][g.js_name(field.name)] = field.expr
|
|
}
|
|
g.writeln(';')
|
|
}
|
|
g.writeln('')
|
|
}
|
|
|
|
fn (mut g JsGen) gen_defer_stmts() {
|
|
g.writeln('(function defer() {')
|
|
for defer_stmt in g.defer_stmts {
|
|
g.stmts(defer_stmt.stmts)
|
|
}
|
|
g.defer_stmts = []
|
|
g.writeln('})();')
|
|
}
|
|
|
|
fn (mut g JsGen) gen_enum_decl(it ast.EnumDecl) {
|
|
g.doc.gen_enum()
|
|
g.writeln('const ${g.js_name(it.name)} = {')
|
|
g.inc_indent()
|
|
mut i := 0
|
|
for field in it.fields {
|
|
g.write('$field.name: ')
|
|
if field.has_expr && field.expr is ast.IntegerLiteral {
|
|
i = field.expr.val.int()
|
|
}
|
|
g.writeln('$i,')
|
|
i++
|
|
}
|
|
g.dec_indent()
|
|
g.writeln('};')
|
|
if it.is_pub {
|
|
g.push_pub_var(it.name)
|
|
}
|
|
}
|
|
|
|
fn (mut g JsGen) gen_expr_stmt(it ast.ExprStmt) {
|
|
g.expr(it.expr)
|
|
if !it.is_expr && it.expr !is ast.IfExpr && !g.inside_ternary && !g.inside_if_optional {
|
|
g.writeln(';')
|
|
}
|
|
}
|
|
|
|
fn (mut g JsGen) gen_expr_stmt_no_semi(it ast.ExprStmt) {
|
|
g.expr(it.expr)
|
|
}
|
|
|
|
// cc_type whether to prefix 'struct' or not (C__Foo -> struct Foo)
|
|
fn (mut g JsGen) cc_type(typ ast.Type, is_prefix_struct bool) string {
|
|
sym := g.table.sym(g.unwrap_generic(typ))
|
|
mut styp := sym.cname.replace('>', '').replace('<', '')
|
|
match sym.info {
|
|
ast.Struct, ast.Interface, ast.SumType {
|
|
if sym.info.is_generic {
|
|
mut sgtyps := '_T'
|
|
for gt in sym.info.generic_types {
|
|
gts := g.table.sym(g.unwrap_generic(gt))
|
|
sgtyps += '_$gts.cname'
|
|
}
|
|
styp += sgtyps
|
|
}
|
|
}
|
|
else {}
|
|
}
|
|
if styp.starts_with('JS__') {
|
|
styp = styp[4..]
|
|
}
|
|
return styp
|
|
}
|
|
|
|
fn (mut g JsGen) gen_for_c_stmt(it ast.ForCStmt) {
|
|
g.inside_loop = true
|
|
g.write('for (')
|
|
if it.has_init {
|
|
g.stmt(it.init)
|
|
} else {
|
|
g.write('; ')
|
|
}
|
|
if it.has_cond {
|
|
g.write('+') // convert to number or boolean
|
|
g.expr(it.cond)
|
|
}
|
|
g.write('; ')
|
|
if it.has_inc {
|
|
g.stmt_no_semi(it.inc)
|
|
}
|
|
g.writeln(') {')
|
|
g.inc_indent()
|
|
g.writeln('try { ')
|
|
g.inc_indent()
|
|
g.stmts(it.stmts)
|
|
g.dec_indent()
|
|
g.writeln('} catch (e) {')
|
|
g.writeln(' if (e instanceof BreakException) { break; }')
|
|
g.writeln(' else if (e instanceof ContinueException) { continue; }')
|
|
g.writeln(' else { throw e; } }')
|
|
g.dec_indent()
|
|
g.writeln('}')
|
|
|
|
g.inside_loop = false
|
|
}
|
|
|
|
fn (mut g JsGen) gen_for_in_stmt(it ast.ForInStmt) {
|
|
if it.is_range {
|
|
// `for x in 1..10 {`
|
|
mut i := it.val_var
|
|
if i in ['', '_'] {
|
|
i = g.new_tmp_var()
|
|
}
|
|
g.inside_loop = true
|
|
g.write('for (let $i = ')
|
|
g.expr(it.cond)
|
|
g.write('; $i < ')
|
|
g.expr(it.high)
|
|
g.writeln('; $i = new int($i + 1)) {')
|
|
g.inside_loop = false
|
|
g.inc_indent()
|
|
g.writeln('try { ')
|
|
g.inc_indent()
|
|
g.stmts(it.stmts)
|
|
g.dec_indent()
|
|
g.writeln('} catch (e) {')
|
|
g.writeln(' if (e instanceof BreakException) { break; }')
|
|
g.writeln(' else if (e instanceof ContinueException) { continue; }')
|
|
g.writeln(' else { throw e; } }')
|
|
g.dec_indent()
|
|
g.writeln('}')
|
|
} else if it.kind in [.array, .string] || it.cond_type.has_flag(.variadic) {
|
|
// `for num in nums {`
|
|
val := if it.val_var in ['', '_'] { '_' } else { it.val_var }
|
|
// styp := g.typ(it.val_type)
|
|
if it.key_var.len > 0 {
|
|
g.write('for (const [$it.key_var, $val] of ')
|
|
if it.kind == .string {
|
|
g.write('Array.from(')
|
|
g.expr(it.cond)
|
|
if it.cond_type.is_ptr() {
|
|
g.write('.valueOf()')
|
|
}
|
|
g.write('.str.split(\'\').entries(), ([$it.key_var, $val]) => [$it.key_var, ')
|
|
|
|
g.write('new ')
|
|
|
|
g.write('u8($val)])')
|
|
} else {
|
|
g.expr(it.cond)
|
|
if it.cond_type.is_ptr() {
|
|
g.write('.valueOf()')
|
|
}
|
|
g.write('.entries()')
|
|
}
|
|
} else {
|
|
g.write('for (const $val of ')
|
|
g.expr(it.cond)
|
|
if it.cond_type.is_ptr() {
|
|
g.write('.valueOf()')
|
|
}
|
|
if it.kind == .string {
|
|
g.write(".str.split('')")
|
|
}
|
|
// cast characters to bytes
|
|
if val !in ['', '_'] && it.kind == .string {
|
|
g.write('.map(c => ')
|
|
|
|
g.write('new ')
|
|
|
|
g.write('u8(c))')
|
|
}
|
|
}
|
|
g.writeln(') {')
|
|
g.inc_indent()
|
|
g.writeln('try { ')
|
|
g.inc_indent()
|
|
g.stmts(it.stmts)
|
|
g.dec_indent()
|
|
g.writeln('} catch (e) {')
|
|
g.writeln(' if (e instanceof BreakException) { break; }')
|
|
g.writeln(' else if (e instanceof ContinueException) { continue; }')
|
|
g.writeln(' else { throw e; } }')
|
|
g.dec_indent()
|
|
g.writeln('}')
|
|
} else if it.kind == .map {
|
|
// `for key, val in map[string]int {`
|
|
// key_styp := g.typ(it.key_type)
|
|
// val_styp := g.typ(it.val_type)
|
|
key := if it.key_var in ['', '_'] { '' } else { it.key_var }
|
|
val := if it.val_var in ['', '_'] { '' } else { it.val_var }
|
|
tmp := g.new_tmp_var()
|
|
tmp2 := g.new_tmp_var()
|
|
if g.pref.output_es5 {
|
|
tmp3 := g.new_tmp_var()
|
|
g.write('let $tmp2 = ')
|
|
g.expr(it.cond)
|
|
if it.cond_type.is_ptr() {
|
|
g.write('.valueOf()')
|
|
}
|
|
g.writeln(';')
|
|
|
|
g.write('for (var $tmp3 = 0; $tmp3 < Object.keys(${tmp2}.map).length; $tmp3++) ')
|
|
g.write('{')
|
|
g.writeln('\tlet $tmp = Object.keys(${tmp2}.map)')
|
|
g.writeln('\tlet $key = $tmp[$tmp3];')
|
|
g.writeln('\tlet $val = ${tmp2}.map[$tmp[$tmp3]];')
|
|
g.inc_indent()
|
|
g.writeln('try { ')
|
|
g.stmts(it.stmts)
|
|
g.writeln('} catch (e) {')
|
|
g.writeln(' if (e instanceof BreakException) { break; }')
|
|
g.writeln(' else if (e instanceof ContinueException) { continue; }')
|
|
g.writeln(' else { throw e; } }')
|
|
g.dec_indent()
|
|
g.writeln('}')
|
|
} else {
|
|
g.write('let $tmp = ')
|
|
g.expr(it.cond)
|
|
if it.cond_type.is_ptr() {
|
|
g.write('.valueOf()')
|
|
}
|
|
g.writeln(';')
|
|
g.writeln('for (var $tmp2 in ${tmp}.map) {')
|
|
|
|
g.inc_indent()
|
|
g.writeln('let $val = ${tmp}.map[$tmp2];')
|
|
g.writeln('let $key = $tmp2;')
|
|
|
|
g.writeln('try { ')
|
|
g.inc_indent()
|
|
g.stmts(it.stmts)
|
|
g.dec_indent()
|
|
g.writeln('} catch (e) {')
|
|
g.writeln(' if (e instanceof BreakException) { break; }')
|
|
g.writeln(' else if (e instanceof ContinueException) { continue; }')
|
|
g.writeln(' else { throw e; } } ')
|
|
g.dec_indent()
|
|
g.writeln('}')
|
|
}
|
|
}
|
|
}
|
|
|
|
fn (mut g JsGen) gen_for_stmt(it ast.ForStmt) {
|
|
g.write('while (')
|
|
if it.is_inf {
|
|
g.write('true')
|
|
} else {
|
|
g.write('+') // convert expr to number or boolean
|
|
g.expr(it.cond)
|
|
}
|
|
g.writeln(') {')
|
|
g.inc_indent()
|
|
g.writeln('try { ')
|
|
g.inc_indent()
|
|
g.stmts(it.stmts)
|
|
g.dec_indent()
|
|
g.writeln('} catch (e) {')
|
|
g.writeln(' if (e instanceof BreakException) { break; }')
|
|
g.writeln(' else if (e instanceof ContinueException) { continue; }')
|
|
g.writeln(' else { throw e; } }')
|
|
g.dec_indent()
|
|
g.writeln('}')
|
|
}
|
|
|
|
fn (mut g JsGen) gen_go_expr(node ast.GoExpr) {
|
|
if g.pref.output_es5 {
|
|
verror('No support for goroutines on ES5 output')
|
|
return
|
|
}
|
|
g.writeln('new _v_Promise({promise: new Promise(function(resolve){')
|
|
g.inc_indent()
|
|
g.write('resolve(')
|
|
g.expr(node.call_expr)
|
|
g.write(');')
|
|
g.dec_indent()
|
|
g.writeln('})});')
|
|
}
|
|
|
|
fn (mut g JsGen) gen_import_stmt(it ast.Import) {
|
|
g.ns.imports[it.mod] = it.alias
|
|
}
|
|
|
|
fn (mut g JsGen) gen_interface_decl(it ast.InterfaceDecl) {
|
|
if it.language != .v {
|
|
// JS interfaces do not need codegen
|
|
return
|
|
}
|
|
// JS is dynamically typed, so we don't need any codegen at all
|
|
// We just need the JSDoc so TypeScript type checking works
|
|
g.doc.gen_interface(it)
|
|
// This is a hack to make the interface's type accessible outside its namespace
|
|
// TODO: interfaces are always `pub`?
|
|
name := g.js_name(it.name)
|
|
g.push_pub_var('/** @type $name */\n\t\t$name')
|
|
g.writeln('function ${g.js_name(it.name)} (arg) { return new \$ref(arg); }')
|
|
}
|
|
|
|
fn (mut g JsGen) gen_optional_error(expr ast.Expr) {
|
|
g.write('new Option({ state: new u8(2),err: ')
|
|
g.expr(expr)
|
|
g.write('})')
|
|
}
|
|
|
|
fn (mut g JsGen) gen_return_stmt(it ast.Return) {
|
|
node := it
|
|
// sym := g.table.sym(g.fn_decl.return_type)
|
|
fn_return_is_optional := g.fn_decl.return_type.has_flag(.optional)
|
|
if node.exprs.len == 0 {
|
|
if fn_return_is_optional {
|
|
if g.inside_or {
|
|
g.writeln('throw new ReturnException({state: new int(0)});')
|
|
} else {
|
|
g.writeln('return {state: new int(0)}')
|
|
}
|
|
} else {
|
|
if g.inside_or {
|
|
g.writeln('throw new ReturnException(undefined);')
|
|
} else {
|
|
g.writeln('return;')
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
if fn_return_is_optional {
|
|
optional_none := node.exprs[0] is ast.None
|
|
ftyp := g.typ(node.types[0])
|
|
mut is_regular_option := ftyp == 'Option'
|
|
if optional_none || is_regular_option || node.types[0] == ast.error_type_idx {
|
|
if !isnil(g.fn_decl) && g.fn_decl.is_test {
|
|
test_error_var := g.new_tmp_var()
|
|
g.writeln('let $test_error_var = "TODO";')
|
|
g.writeln('return $test_error_var;')
|
|
return
|
|
}
|
|
if !g.inside_or {
|
|
g.write('return ')
|
|
} else {
|
|
g.write('throw new ReturnException(')
|
|
}
|
|
g.gen_optional_error(it.exprs[0])
|
|
if g.inside_or {
|
|
g.writeln(')')
|
|
}
|
|
g.writeln(';')
|
|
return
|
|
}
|
|
}
|
|
if fn_return_is_optional {
|
|
tmp := g.new_tmp_var()
|
|
g.write('const $tmp = new ')
|
|
|
|
g.writeln('Option({});')
|
|
g.write('${tmp}.state = new u8(0);')
|
|
g.write('${tmp}.data = ')
|
|
if it.exprs.len == 1 {
|
|
g.expr(it.exprs[0])
|
|
} else { // Multi return
|
|
g.gen_array_init_values(it.exprs)
|
|
}
|
|
g.writeln('')
|
|
if g.inside_or {
|
|
g.write('throw new ReturnException($tmp);')
|
|
} else {
|
|
g.write('return $tmp;')
|
|
}
|
|
return
|
|
}
|
|
if !g.inside_or {
|
|
g.write('return ')
|
|
} else {
|
|
g.write('throw new ReturnException(')
|
|
}
|
|
if it.exprs.len == 1 {
|
|
g.expr(it.exprs[0])
|
|
} else { // Multi return
|
|
g.gen_array_init_values(it.exprs)
|
|
}
|
|
if g.inside_or {
|
|
g.writeln(')')
|
|
}
|
|
g.writeln(';')
|
|
}
|
|
|
|
fn (mut g JsGen) gen_hash_stmt(it ast.HashStmt) {
|
|
g.writeln(it.val)
|
|
}
|
|
|
|
fn (mut g JsGen) gen_sumtype_decl(it ast.SumTypeDecl) {
|
|
name := g.js_name(it.name)
|
|
g.push_pub_var('/** @type $name */\n\t\t$name')
|
|
g.writeln('function ${g.js_name(it.name)} (arg) { return arg; }')
|
|
}
|
|
|
|
fn (mut g JsGen) gen_struct_decl(node ast.StructDecl) {
|
|
mut name := node.name
|
|
if name.starts_with('JS.') {
|
|
return
|
|
}
|
|
if name in js.v_types && g.ns.name == 'builtin' {
|
|
return
|
|
}
|
|
js_name := g.js_name(name)
|
|
g.gen_attrs(node.attrs)
|
|
g.doc.gen_fac_fn(node.fields)
|
|
if g.pref.output_es5 {
|
|
obj := g.new_tmp_var()
|
|
g.writeln('function ${js_name}($obj) {')
|
|
g.inc_indent()
|
|
g.writeln('if ($obj === undefined) { obj = {}; }')
|
|
for field in node.fields {
|
|
mut keep := true
|
|
for attr in field.attrs {
|
|
if attr.name == 'noinit' {
|
|
keep = false
|
|
}
|
|
}
|
|
if keep {
|
|
g.writeln('if (${obj}.$field.name === undefined) {')
|
|
g.write('${obj}.$field.name = ')
|
|
if field.has_default_expr {
|
|
g.expr(field.default_expr)
|
|
} else {
|
|
g.write('${g.to_js_typ_val(field.typ)}')
|
|
}
|
|
g.writeln('\n}')
|
|
}
|
|
g.writeln('var $field.name = ${obj}.$field.name;')
|
|
}
|
|
|
|
g.dec_indent()
|
|
} else {
|
|
g.write('function ${js_name}({ ')
|
|
for i, field in node.fields {
|
|
g.write('$field.name')
|
|
mut keep := true
|
|
for attr in field.attrs {
|
|
if attr.name == 'noinit' {
|
|
keep = false
|
|
}
|
|
}
|
|
if keep {
|
|
g.write(' = ')
|
|
|
|
if field.has_default_expr {
|
|
g.expr(field.default_expr)
|
|
} else {
|
|
g.write('${g.to_js_typ_val(field.typ)}')
|
|
}
|
|
}
|
|
if i < node.fields.len - 1 {
|
|
g.write(', ')
|
|
}
|
|
}
|
|
g.writeln(' }) {')
|
|
}
|
|
g.inc_indent()
|
|
for field in node.fields {
|
|
g.writeln('this.$field.name = $field.name')
|
|
}
|
|
g.dec_indent()
|
|
g.writeln('};')
|
|
g.writeln('${js_name}.prototype = {')
|
|
g.inc_indent()
|
|
for embed in node.embeds {
|
|
etyp := g.typ(embed.typ)
|
|
g.writeln('...${g.js_name(etyp)}.prototype,')
|
|
}
|
|
for iface, iface_types in g.table.iface_types {
|
|
if iface.starts_with('JS.') {
|
|
for ty in iface_types {
|
|
sym := g.table.sym(ty)
|
|
|
|
if sym.name == node.name {
|
|
g.writeln('...${g.js_name(iface)}.prototype,')
|
|
}
|
|
}
|
|
}
|
|
}
|
|
fns := g.method_fn_decls[name]
|
|
// gen toString method
|
|
fn_names := fns.map(it.name)
|
|
if 'toString' !in fn_names {
|
|
if g.pref.output_es5 {
|
|
g.writeln('toString: (function() {')
|
|
} else {
|
|
g.writeln('toString() {')
|
|
}
|
|
g.inc_indent()
|
|
g.write('return `$js_name {')
|
|
for i, field in node.fields {
|
|
if i == 0 {
|
|
g.write(' ')
|
|
} else {
|
|
g.write(', ')
|
|
}
|
|
match g.typ(field.typ).split('.').last() {
|
|
'string' { g.write('$field.name: "\${this["$field.name"].toString()}"') }
|
|
else { g.write('$field.name: \${this["$field.name"].toString()} ') }
|
|
}
|
|
}
|
|
g.writeln('}`')
|
|
g.dec_indent()
|
|
if g.pref.output_es5 {
|
|
g.writeln('}).bind(this),')
|
|
} else {
|
|
g.writeln('},')
|
|
}
|
|
}
|
|
for field in node.fields {
|
|
typ := g.typ(field.typ)
|
|
g.doc.gen_typ(typ)
|
|
mut keep := true
|
|
for attr in field.attrs {
|
|
if attr.name == 'noinit' {
|
|
keep = false
|
|
}
|
|
}
|
|
if keep {
|
|
g.write('$field.name: ${g.to_js_typ_val(field.typ)}')
|
|
g.writeln(',')
|
|
}
|
|
}
|
|
if g.pref.output_es5 {
|
|
g.writeln('\$toJS: (function() { return this; }).bind(this)')
|
|
} else {
|
|
g.writeln('\$toJS() { return this; }')
|
|
}
|
|
g.writeln('};\n')
|
|
g.dec_indent()
|
|
|
|
if node.is_pub {
|
|
g.push_pub_var(name)
|
|
}
|
|
}
|
|
|
|
fn (mut g JsGen) gen_array_init_expr(it ast.ArrayInit) {
|
|
// Note: Fixed arrays and regular arrays are handled the same, since fixed arrays:
|
|
// 1) Are only available for number types
|
|
// 2) Give the code unnecessary complexity
|
|
// 3) Have several limitations like missing most `Array.prototype` methods
|
|
// 4) Modern engines can optimize regular arrays into typed arrays anyways,
|
|
// offering similar performance
|
|
g.write('new array(new array_buffer({arr: ')
|
|
g.inc_indent()
|
|
|
|
if it.has_len {
|
|
t1 := g.new_tmp_var()
|
|
g.writeln('(function(length) {')
|
|
g.inc_indent()
|
|
g.writeln('const $t1 = [];')
|
|
g.write('for (let it = 0; it < length')
|
|
g.writeln('; it++) {')
|
|
g.inc_indent()
|
|
g.write('${t1}.push(')
|
|
if it.has_default {
|
|
g.expr(it.default_expr)
|
|
} else {
|
|
// Fill the array with the default values for its type
|
|
t := g.to_js_typ_val(it.elem_type)
|
|
g.write(t)
|
|
}
|
|
g.writeln(');')
|
|
g.dec_indent()
|
|
g.writeln('};')
|
|
g.writeln('return $t1;')
|
|
g.dec_indent()
|
|
g.write('})(')
|
|
g.expr(it.len_expr)
|
|
g.write('),len: new int(')
|
|
g.expr(it.len_expr)
|
|
g.write(')')
|
|
g.write(', cap: new int(')
|
|
g.expr(it.len_expr)
|
|
g.write(')')
|
|
} else if it.is_fixed && it.exprs.len == 1 {
|
|
// [100]u8 codegen
|
|
t1 := g.new_tmp_var()
|
|
t2 := g.new_tmp_var()
|
|
g.writeln('(function() {')
|
|
g.inc_indent()
|
|
g.writeln('const $t1 = [];')
|
|
g.write('for (let $t2 = 0; $t2 < ')
|
|
g.expr(it.exprs[0])
|
|
g.writeln('; $t2++) {')
|
|
g.inc_indent()
|
|
g.write('${t1}.push(')
|
|
if it.has_default {
|
|
g.expr(it.default_expr)
|
|
} else {
|
|
// Fill the array with the default values for its type
|
|
t := g.to_js_typ_val(it.elem_type)
|
|
g.write(t)
|
|
}
|
|
g.writeln(');')
|
|
g.dec_indent()
|
|
g.writeln('};')
|
|
g.writeln('return $t1;')
|
|
g.dec_indent()
|
|
g.write('})(), len: new int(')
|
|
g.expr(it.exprs[0])
|
|
g.write('), cap: new int(')
|
|
g.expr(it.exprs[0])
|
|
g.write(')')
|
|
} else {
|
|
styp := g.typ(it.elem_type)
|
|
|
|
c := if styp in js.v_types {
|
|
g.gen_array_init_values_prim(it.exprs, styp)
|
|
} else {
|
|
g.gen_array_init_values(it.exprs)
|
|
}
|
|
g.write(', len: new int($c), cap: new int($c)')
|
|
}
|
|
g.dec_indent()
|
|
g.write('}))')
|
|
}
|
|
|
|
fn (mut g JsGen) gen_array_init_values(exprs []ast.Expr) int {
|
|
g.write('[')
|
|
mut c := 0
|
|
for i, expr in exprs {
|
|
g.expr(expr)
|
|
if i < exprs.len - 1 {
|
|
g.write(', ')
|
|
}
|
|
c++
|
|
}
|
|
g.write(']')
|
|
return c
|
|
}
|
|
|
|
fn (mut g JsGen) gen_array_init_values_prim(exprs []ast.Expr, typ string) int {
|
|
g.write('[')
|
|
mut c := 0
|
|
for i, expr in exprs {
|
|
g.write('new ${typ}(')
|
|
g.expr(expr)
|
|
g.write(')')
|
|
if i < exprs.len - 1 {
|
|
g.write(', ')
|
|
}
|
|
c++
|
|
}
|
|
g.write(']')
|
|
return c
|
|
}
|
|
|
|
fn (mut g JsGen) gen_ident(node ast.Ident) {
|
|
mut name := g.js_name(node.name)
|
|
if node.kind == .blank_ident || name in ['', '_'] {
|
|
name = g.new_tmp_var()
|
|
}
|
|
// TODO `is`
|
|
// TODO handle optionals
|
|
g.write(name)
|
|
|
|
// TODO: Generate .val for basic types
|
|
}
|
|
|
|
fn (mut g JsGen) gen_lock_expr(node ast.LockExpr) {
|
|
// TODO: implement this
|
|
}
|
|
|
|
fn (mut g JsGen) need_tmp_var_in_match(node ast.MatchExpr) bool {
|
|
if node.is_expr && node.return_type != ast.void_type && node.return_type != 0 {
|
|
cond_sym := g.table.final_sym(node.cond_type)
|
|
sym := g.table.sym(node.return_type)
|
|
if sym.kind == .multi_return {
|
|
return false
|
|
}
|
|
if cond_sym.kind == .enum_ && node.branches.len > 5 {
|
|
return true
|
|
}
|
|
for branch in node.branches {
|
|
if branch.stmts.len > 1 {
|
|
return true
|
|
}
|
|
if branch.stmts.len == 1 {
|
|
if branch.stmts[0] is ast.ExprStmt {
|
|
stmt := branch.stmts[0] as ast.ExprStmt
|
|
if stmt.expr in [ast.CallExpr, ast.IfExpr, ast.MatchExpr]
|
|
|| (stmt.expr is ast.IndexExpr
|
|
&& (stmt.expr as ast.IndexExpr).or_expr.kind != .absent) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
fn (mut g JsGen) match_expr_classic(node ast.MatchExpr, is_expr bool, cond_var MatchCond, tmp_var string) {
|
|
type_sym := g.table.sym(node.cond_type)
|
|
for j, branch in node.branches {
|
|
is_last := j == node.branches.len - 1
|
|
if branch.is_else || (node.is_expr && is_last && tmp_var.len == 0) {
|
|
if node.branches.len > 1 {
|
|
if is_expr && tmp_var.len == 0 {
|
|
// TODO too many branches. maybe separate ?: matches
|
|
g.write(' : ')
|
|
} else {
|
|
g.writeln('')
|
|
g.write_v_source_line_info(branch.pos)
|
|
g.writeln('else {')
|
|
}
|
|
}
|
|
} else {
|
|
if j > 0 {
|
|
if is_expr && tmp_var.len == 0 {
|
|
g.write(' : ')
|
|
} else {
|
|
g.writeln('')
|
|
g.write_v_source_line_info(branch.pos)
|
|
g.write('else ')
|
|
}
|
|
}
|
|
if is_expr && tmp_var.len == 0 {
|
|
g.write('(')
|
|
} else {
|
|
if j == 0 {
|
|
g.writeln('')
|
|
}
|
|
g.write_v_source_line_info(branch.pos)
|
|
g.write('if (')
|
|
}
|
|
for i, expr in branch.exprs {
|
|
if i > 0 {
|
|
g.write(' || ')
|
|
}
|
|
match type_sym.kind {
|
|
.array {
|
|
ptr_typ := g.gen_array_equality_fn(node.cond_type)
|
|
|
|
g.write('${ptr_typ}_arr_eq(')
|
|
g.match_cond(cond_var)
|
|
g.write(',')
|
|
g.expr(expr)
|
|
g.write(').val')
|
|
}
|
|
.array_fixed {
|
|
ptr_typ := g.gen_fixed_array_equality_fn(node.cond_type)
|
|
|
|
g.write('${ptr_typ}_arr_eq(')
|
|
g.match_cond(cond_var)
|
|
g.write(',')
|
|
g.expr(expr)
|
|
g.write(').val')
|
|
}
|
|
.map {
|
|
ptr_typ := g.gen_map_equality_fn(node.cond_type)
|
|
|
|
g.write('${ptr_typ}_map_eq(')
|
|
g.match_cond(cond_var)
|
|
g.write(',')
|
|
g.expr(expr)
|
|
g.write(').val')
|
|
}
|
|
.string {
|
|
g.match_cond(cond_var)
|
|
g.write('.str === ')
|
|
g.expr(expr)
|
|
g.write('.str')
|
|
}
|
|
.struct_ {
|
|
ptr_typ := g.gen_struct_equality_fn(node.cond_type)
|
|
|
|
g.write('${ptr_typ}_struct_eq(')
|
|
g.match_cond(cond_var)
|
|
g.write(',')
|
|
g.expr(expr)
|
|
g.write(').val')
|
|
}
|
|
.sum_type {
|
|
ptr_typ := g.gen_sumtype_equality_fn(node.cond_type)
|
|
|
|
g.write('${ptr_typ}_sumtype_eq(')
|
|
g.match_cond(cond_var)
|
|
g.write(',')
|
|
g.expr(expr)
|
|
g.write(').val')
|
|
}
|
|
.alias {
|
|
ptr_typ := g.gen_alias_equality_fn(node.cond_type)
|
|
|
|
g.write('${ptr_typ}_alias_eq(')
|
|
g.match_cond(cond_var)
|
|
g.write(',')
|
|
g.expr(expr)
|
|
g.write(').val')
|
|
}
|
|
else {
|
|
has_operator_overloading := g.table.has_method(type_sym, '==')
|
|
if has_operator_overloading {
|
|
left := g.unwrap(node.cond_type)
|
|
g.write(g.typ(left.unaliased.set_nr_muls(0)))
|
|
g.write('__eq(')
|
|
g.match_cond(cond_var)
|
|
g.gen_deref_ptr(node.cond_type)
|
|
g.write(',')
|
|
g.expr(expr)
|
|
g.write(')')
|
|
g.write('.valueOf()')
|
|
} else if expr is ast.RangeExpr {
|
|
// if type is unsigned and low is 0, check is unneeded
|
|
mut skip_low := false
|
|
if expr.low is ast.IntegerLiteral {
|
|
if node.cond_type in [ast.u16_type, ast.u32_type, ast.u64_type]
|
|
&& expr.low.val == '0' {
|
|
skip_low = true
|
|
}
|
|
}
|
|
g.write('(')
|
|
if !skip_low {
|
|
g.match_cond(cond_var)
|
|
g.write(' >= ')
|
|
g.expr(expr.low)
|
|
g.write(' && ')
|
|
}
|
|
g.match_cond(cond_var)
|
|
g.write(' <= ')
|
|
g.expr(expr.high)
|
|
g.write(')')
|
|
} else {
|
|
g.write('vEq(')
|
|
g.match_cond(cond_var)
|
|
g.write(',')
|
|
g.expr(expr)
|
|
g.write(')')
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if is_expr && tmp_var.len == 0 {
|
|
g.write(') ? ')
|
|
} else {
|
|
g.writeln(') {')
|
|
}
|
|
}
|
|
g.stmts_with_tmp_var(branch.stmts, tmp_var)
|
|
if !g.inside_ternary && node.branches.len >= 1 {
|
|
g.write('}')
|
|
}
|
|
}
|
|
}
|
|
|
|
type MatchCond = CondExpr | CondString
|
|
|
|
struct CondString {
|
|
s string
|
|
}
|
|
|
|
struct CondExpr {
|
|
expr ast.Expr
|
|
}
|
|
|
|
fn (mut g JsGen) match_cond(cond MatchCond) {
|
|
match cond {
|
|
CondString {
|
|
g.write(cond.s)
|
|
}
|
|
CondExpr {
|
|
g.expr(cond.expr)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn (mut g JsGen) match_expr(node ast.MatchExpr) {
|
|
if node.cond_type == 0 {
|
|
g.writeln('// match 0')
|
|
return
|
|
}
|
|
prev := g.inside_ternary
|
|
need_tmp_var := g.need_tmp_var_in_match(node)
|
|
is_expr := (node.is_expr && node.return_type != ast.void_type) || g.inside_ternary
|
|
mut cond_var := MatchCond(CondString{''})
|
|
mut tmp_var := ''
|
|
mut cur_line := ''
|
|
if is_expr && !need_tmp_var {
|
|
g.inside_ternary = true
|
|
}
|
|
|
|
if node.cond in [ast.Ident, ast.SelectorExpr, ast.IntegerLiteral, ast.StringLiteral,
|
|
ast.FloatLiteral, ast.CallExpr, ast.EnumVal] {
|
|
cond_var = CondExpr{node.cond}
|
|
} else {
|
|
s := g.new_tmp_var()
|
|
cond_var = CondString{s}
|
|
g.write('let $s = ')
|
|
g.expr(node.cond)
|
|
g.writeln(';')
|
|
}
|
|
if need_tmp_var {
|
|
g.empty_line = true
|
|
cur_line = g.out.cut_to(g.stmt_start_pos).trim_left(' \t')
|
|
tmp_var = g.new_tmp_var()
|
|
g.writeln('let $tmp_var = undefined;')
|
|
}
|
|
if is_expr && !need_tmp_var {
|
|
g.write('(')
|
|
}
|
|
typ := g.table.final_sym(node.cond_type)
|
|
if node.is_sum_type {
|
|
g.match_expr_sumtype(node, is_expr, cond_var, tmp_var)
|
|
} else if typ.kind == .enum_ && !g.inside_loop && node.branches.len > 5 && g.fn_decl != 0 { // do not optimize while in top-level
|
|
g.match_expr_switch(node, is_expr, cond_var, tmp_var, typ)
|
|
} else {
|
|
g.match_expr_classic(node, is_expr, cond_var, tmp_var)
|
|
}
|
|
g.write(cur_line)
|
|
if need_tmp_var {
|
|
g.write('$tmp_var')
|
|
}
|
|
if is_expr && !need_tmp_var {
|
|
g.write(')')
|
|
g.inside_ternary = prev
|
|
}
|
|
}
|
|
|
|
fn (mut g JsGen) stmts_with_tmp_var(stmts []ast.Stmt, tmp_var string) {
|
|
g.inc_indent()
|
|
if g.inside_ternary {
|
|
g.write('(')
|
|
}
|
|
prev := g.inside_ternary
|
|
for i, stmt in stmts {
|
|
if i == stmts.len - 1 && tmp_var != '' {
|
|
if g.inside_if_optional {
|
|
if stmt is ast.ExprStmt {
|
|
if stmt.typ == ast.error_type_idx || stmt.expr is ast.None {
|
|
g.writeln('${tmp_var}.state = 2;')
|
|
g.write('${tmp_var}.err = ')
|
|
g.expr(stmt.expr)
|
|
g.writeln(';')
|
|
} else {
|
|
g.write('opt_ok(')
|
|
g.stmt(stmt)
|
|
g.writeln(', $tmp_var);')
|
|
}
|
|
}
|
|
} else {
|
|
g.write('$tmp_var = ')
|
|
g.stmt(stmt)
|
|
g.writeln('')
|
|
}
|
|
} else {
|
|
g.stmt(stmt)
|
|
if g.inside_if_optional && stmt is ast.ExprStmt {
|
|
g.writeln(';')
|
|
}
|
|
}
|
|
if g.inside_ternary && i < stmts.len - 1 {
|
|
g.write(',')
|
|
}
|
|
}
|
|
g.dec_indent()
|
|
g.inside_ternary = prev
|
|
if g.inside_ternary {
|
|
g.write(')')
|
|
}
|
|
}
|
|
|
|
fn (mut g JsGen) match_expr_sumtype(node ast.MatchExpr, is_expr bool, cond_var MatchCond, tmp_var string) {
|
|
for j, branch in node.branches {
|
|
mut sumtype_index := 0
|
|
for {
|
|
is_last := j == node.branches.len - 1
|
|
sym := g.table.sym(node.cond_type)
|
|
if branch.is_else || (node.is_expr && is_last && tmp_var.len == 0) {
|
|
if is_expr && tmp_var.len == 0 {
|
|
g.write(' : ')
|
|
} else {
|
|
g.writeln('')
|
|
g.writeln('else {')
|
|
}
|
|
} else {
|
|
if j > 0 || sumtype_index > 0 {
|
|
if is_expr && tmp_var.len == 0 {
|
|
g.write(' : ')
|
|
} else {
|
|
g.write('else ')
|
|
}
|
|
}
|
|
|
|
if is_expr && tmp_var.len == 0 {
|
|
g.write('(')
|
|
} else {
|
|
g.write('if (')
|
|
}
|
|
if sym.kind == .sum_type || sym.kind == .interface_ {
|
|
x := branch.exprs[sumtype_index]
|
|
|
|
if x is ast.TypeNode {
|
|
typ := g.unwrap_generic(x.typ)
|
|
|
|
tsym := g.table.sym(typ)
|
|
if tsym.language == .js && (tsym.name == 'JS.Number'
|
|
|| tsym.name == 'JS.Boolean' || tsym.name == 'JS.String') {
|
|
g.write('typeof ')
|
|
}
|
|
}
|
|
}
|
|
g.match_cond(cond_var)
|
|
if sym.kind == .sum_type {
|
|
x := branch.exprs[sumtype_index]
|
|
if x is ast.TypeNode {
|
|
typ := g.unwrap_generic(x.typ)
|
|
tsym := g.table.sym(typ)
|
|
if tsym.language == .js && (tsym.name == 'JS.Number'
|
|
|| tsym.name == 'JS.Boolean' || tsym.name == 'JS.String') {
|
|
g.write(' === "${tsym.name[3..].to_lower()}"')
|
|
} else {
|
|
g.write(' instanceof ')
|
|
g.expr(branch.exprs[sumtype_index])
|
|
}
|
|
} else {
|
|
g.write(' instanceof ')
|
|
g.expr(branch.exprs[sumtype_index])
|
|
}
|
|
} else if sym.kind == .interface_ {
|
|
if !sym.name.starts_with('JS.') {
|
|
g.write('.val')
|
|
}
|
|
x := branch.exprs[sumtype_index]
|
|
if x is ast.TypeNode {
|
|
typ := g.unwrap_generic(x.typ)
|
|
tsym := g.table.sym(typ)
|
|
if tsym.language == .js && (tsym.name == 'Number'
|
|
|| tsym.name == 'Boolean' || tsym.name == 'String') {
|
|
g.write(' === $tsym.name.to_lower()')
|
|
} else {
|
|
g.write(' instanceof ')
|
|
g.expr(branch.exprs[sumtype_index])
|
|
}
|
|
} else {
|
|
g.write(' instanceof ')
|
|
g.write('None__')
|
|
}
|
|
}
|
|
if is_expr && tmp_var.len == 0 {
|
|
g.write(') ? ')
|
|
} else {
|
|
g.writeln(') {')
|
|
}
|
|
}
|
|
g.stmts_with_tmp_var(branch.stmts, tmp_var)
|
|
if !g.inside_ternary {
|
|
g.writeln('}')
|
|
}
|
|
sumtype_index++
|
|
if branch.exprs.len == 0 || sumtype_index == branch.exprs.len {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn (mut g JsGen) match_expr_switch(node ast.MatchExpr, is_expr bool, cond_var MatchCond, tmp_var string, enum_typ ast.TypeSymbol) {
|
|
mut range_branches := []ast.MatchBranch{cap: node.branches.len} // branches have RangeExpr cannot emit as switch case branch, we handle it in default branch
|
|
mut default_generated := false
|
|
g.empty_line = true
|
|
g.write('switch (')
|
|
g.match_cond(cond_var)
|
|
g.writeln(') {')
|
|
g.inc_indent()
|
|
for branch in node.branches {
|
|
if branch.is_else {
|
|
g.writeln('default:')
|
|
default_generated = true
|
|
if range_branches.len > 0 {
|
|
g.inc_indent()
|
|
for range_branch in range_branches {
|
|
g.write('if (')
|
|
for i, expr in range_branch.exprs {
|
|
if i > 0 {
|
|
g.write(' || ')
|
|
}
|
|
if expr is ast.RangeExpr {
|
|
// if type is unsigned and low is 0, check is unneeded
|
|
mut skip_low := false
|
|
if expr.low is ast.IntegerLiteral {
|
|
if node.cond_type in [ast.u16_type, ast.u32_type, ast.u64_type]
|
|
&& expr.low.val == '0' {
|
|
skip_low = true
|
|
}
|
|
}
|
|
g.write('(')
|
|
if !skip_low {
|
|
g.match_cond(cond_var)
|
|
g.write(' >= ')
|
|
g.expr(expr.low)
|
|
g.write(' && ')
|
|
}
|
|
g.match_cond(cond_var)
|
|
g.write(' <= ')
|
|
g.expr(expr.high)
|
|
g.write(')')
|
|
} else {
|
|
g.match_cond(cond_var)
|
|
g.write(' == (')
|
|
g.expr(expr)
|
|
g.write(')')
|
|
}
|
|
}
|
|
g.writeln(') {')
|
|
g.stmts_with_tmp_var(range_branch.stmts, tmp_var)
|
|
g.writeln('break;')
|
|
g.writeln('}')
|
|
}
|
|
g.dec_indent()
|
|
}
|
|
} else {
|
|
if branch.exprs.any(it is ast.RangeExpr) {
|
|
range_branches << branch
|
|
continue
|
|
}
|
|
for expr in branch.exprs {
|
|
if expr is ast.EnumVal {
|
|
g.write('case ')
|
|
g.expr(expr)
|
|
g.writeln(': ')
|
|
}
|
|
}
|
|
}
|
|
g.inc_indent()
|
|
g.writeln('{')
|
|
g.stmts_with_tmp_var(branch.stmts, tmp_var)
|
|
g.writeln('} break;')
|
|
g.dec_indent()
|
|
}
|
|
if range_branches.len > 0 && !default_generated {
|
|
g.writeln('default:')
|
|
g.inc_indent()
|
|
for range_branch in range_branches {
|
|
g.write('if (')
|
|
for i, expr in range_branch.exprs {
|
|
if i > 0 {
|
|
g.write(' || ')
|
|
}
|
|
if expr is ast.RangeExpr {
|
|
// if type is unsigned and low is 0, check is unneeded
|
|
mut skip_low := false
|
|
if expr.low is ast.IntegerLiteral {
|
|
if node.cond_type in [ast.u16_type, ast.u32_type, ast.u64_type]
|
|
&& expr.low.val == '0' {
|
|
skip_low = true
|
|
}
|
|
}
|
|
g.write('(')
|
|
if !skip_low {
|
|
g.match_cond(cond_var)
|
|
g.write(' >= ')
|
|
g.expr(expr.low)
|
|
g.write(' && ')
|
|
}
|
|
g.match_cond(cond_var)
|
|
g.write(' <= ')
|
|
g.expr(expr.high)
|
|
g.write(')')
|
|
} else {
|
|
g.match_cond(cond_var)
|
|
g.write(' == (')
|
|
g.expr(expr)
|
|
g.write(')')
|
|
}
|
|
}
|
|
g.writeln(') {')
|
|
g.stmts_with_tmp_var(range_branch.stmts, tmp_var)
|
|
g.writeln('break;')
|
|
g.writeln('}')
|
|
}
|
|
g.dec_indent()
|
|
}
|
|
g.dec_indent()
|
|
g.writeln('}')
|
|
}
|
|
|
|
fn (mut g JsGen) need_tmp_var_in_if(node ast.IfExpr) bool {
|
|
if node.is_expr && g.inside_ternary {
|
|
if node.typ.has_flag(.optional) {
|
|
return true
|
|
}
|
|
|
|
for branch in node.branches {
|
|
if branch.cond is ast.IfGuardExpr || branch.stmts.len > 1 {
|
|
return true
|
|
}
|
|
|
|
if branch.stmts.len == 1 {
|
|
if branch.stmts[0] is ast.ExprStmt {
|
|
stmt := branch.stmts[0] as ast.ExprStmt
|
|
if stmt.expr is ast.CallExpr {
|
|
if stmt.expr.is_method {
|
|
left_sym := g.table.sym(stmt.expr.receiver_type)
|
|
if left_sym.kind in [.array, .array_fixed, .map] {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
fn (mut g JsGen) gen_if_expr(node ast.IfExpr) {
|
|
if node.is_comptime {
|
|
g.comptime_if(node)
|
|
return
|
|
}
|
|
// For simpe if expressions we can use C's `?:`
|
|
// `if x > 0 { 1 } else { 2 }` => `(x > 0) ? (1) : (2)`
|
|
// For if expressions with multiple statements or another if expression inside, it's much
|
|
// easier to use a temp var, than do C tricks with commas, introduce special vars etc
|
|
// (as it used to be done).
|
|
// Always use this in -autofree, since ?: can have tmp expressions that have to be freed.
|
|
needs_tmp_var := g.need_tmp_var_in_if(node)
|
|
tmp := if needs_tmp_var { g.new_tmp_var() } else { '' }
|
|
|
|
if needs_tmp_var {
|
|
if node.typ.has_flag(.optional) {
|
|
g.inside_if_optional = true
|
|
}
|
|
|
|
g.writeln('let $tmp; /* if prepend */')
|
|
} else if node.is_expr || g.inside_ternary {
|
|
g.write('(')
|
|
prev := g.inside_ternary
|
|
g.inside_ternary = true
|
|
for i, branch in node.branches {
|
|
if i > 0 {
|
|
g.write(' : ')
|
|
}
|
|
if i < node.branches.len - 1 || !node.has_else {
|
|
g.write('(')
|
|
g.expr(branch.cond)
|
|
g.write(').valueOf()')
|
|
g.write(' ? ')
|
|
}
|
|
g.stmts(branch.stmts)
|
|
}
|
|
g.inside_ternary = prev
|
|
g.write(')')
|
|
return
|
|
}
|
|
|
|
mut is_guard := false
|
|
mut guard_idx := 0
|
|
mut guard_vars := []string{}
|
|
|
|
for i, branch in node.branches {
|
|
cond := branch.cond
|
|
if cond is ast.IfGuardExpr {
|
|
if !is_guard {
|
|
is_guard = true
|
|
guard_idx = i
|
|
guard_vars = []string{len: node.branches.len}
|
|
}
|
|
if cond.expr !is ast.IndexExpr && cond.expr !is ast.PrefixExpr {
|
|
var_name := g.new_tmp_var()
|
|
guard_vars[i] = var_name
|
|
g.writeln('let $var_name;')
|
|
} else {
|
|
guard_vars[i] = ''
|
|
}
|
|
}
|
|
}
|
|
|
|
for i, branch in node.branches {
|
|
if i > 0 {
|
|
g.write('} else ')
|
|
}
|
|
// if last branch is `else {`
|
|
if i == node.branches.len - 1 && node.has_else {
|
|
g.writeln('{')
|
|
// define `err` only for simple `if val := opt {...} else {`
|
|
if is_guard && guard_idx == i - 1 {
|
|
cvar_name := guard_vars[guard_idx]
|
|
g.writeln('\tlet err = ${cvar_name}.err;')
|
|
}
|
|
} else {
|
|
match branch.cond {
|
|
ast.IfGuardExpr {
|
|
mut var_name := guard_vars[i]
|
|
mut short_opt := false
|
|
if var_name == '' {
|
|
short_opt = true // we don't need a further tmp, so use the one we'll get later
|
|
var_name = g.new_tmp_var()
|
|
guard_vars[i] = var_name // for `else`
|
|
g.tmp_count--
|
|
g.writeln('if (${var_name}.state == 0) {')
|
|
} else {
|
|
g.write('if ($var_name = ')
|
|
g.expr(branch.cond.expr)
|
|
g.writeln(', ${var_name}.state == 0) {')
|
|
}
|
|
if short_opt || branch.cond.vars[0].name != '_' {
|
|
if short_opt {
|
|
cond_var_name := if branch.cond.vars[0].name == '_' {
|
|
'_dummy_${g.tmp_count + 1}'
|
|
} else {
|
|
branch.cond.vars[0].name
|
|
}
|
|
g.write('\tlet $cond_var_name = ')
|
|
g.expr(branch.cond.expr)
|
|
g.writeln(';')
|
|
} else {
|
|
g.writeln('\tlet $branch.cond.vars[0].name = ${var_name}.data;')
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
g.write('if ((')
|
|
g.expr(branch.cond)
|
|
g.writeln(').valueOf()) {')
|
|
}
|
|
}
|
|
}
|
|
g.inc_indent()
|
|
if needs_tmp_var {
|
|
g.stmts_with_tmp_var(branch.stmts, tmp)
|
|
} else {
|
|
g.stmts(branch.stmts)
|
|
}
|
|
g.dec_indent()
|
|
}
|
|
if node.branches.len > 0 {
|
|
g.writeln('}')
|
|
}
|
|
if needs_tmp_var {
|
|
g.write('$tmp')
|
|
}
|
|
if node.typ.has_flag(.optional) {
|
|
g.inside_if_optional = false
|
|
}
|
|
}
|
|
|
|
fn (mut g JsGen) gen_index_expr(expr ast.IndexExpr) {
|
|
left_typ := g.table.sym(expr.left_type)
|
|
// TODO: Handle splice setting if it's implemented
|
|
if expr.index is ast.RangeExpr {
|
|
if left_typ.kind == .string {
|
|
g.write('string_slice(')
|
|
} else {
|
|
g.write('array_slice(')
|
|
}
|
|
g.expr(expr.left)
|
|
if expr.left_type.is_ptr() {
|
|
g.write('.valueOf()')
|
|
}
|
|
g.write(',')
|
|
|
|
if expr.index.has_low {
|
|
g.expr(expr.index.low)
|
|
} else {
|
|
g.write('new int(0)')
|
|
}
|
|
g.write(', ')
|
|
if expr.index.has_high {
|
|
g.expr(expr.index.high)
|
|
} else {
|
|
g.expr(expr.left)
|
|
if expr.left_type.is_ptr() {
|
|
g.write('.valueOf()')
|
|
}
|
|
g.write('.len')
|
|
}
|
|
g.write(')')
|
|
} else if left_typ.kind == .map {
|
|
g.expr(expr.left)
|
|
|
|
if expr.is_setter {
|
|
g.inside_map_set = true
|
|
g.write('.getOrSet(')
|
|
} else {
|
|
g.write('.get(')
|
|
}
|
|
g.expr(expr.index)
|
|
g.write('.\$toJS()')
|
|
if expr.is_setter {
|
|
// g.write(', ${g.to_js_typ_val(left_typ.)')
|
|
match left_typ.info {
|
|
ast.Map {
|
|
g.write(', ${g.to_js_typ_val(left_typ.info.value_type)}')
|
|
}
|
|
else {
|
|
verror('unreachable')
|
|
}
|
|
}
|
|
}
|
|
g.write(')')
|
|
} else if left_typ.kind == .string {
|
|
if expr.is_setter {
|
|
// TODO: What's the best way to do this?
|
|
// 'string'[3] = `o`
|
|
} else {
|
|
// TODO: Maybe use u16 there? JS String returns values up to 2^16-1
|
|
g.write('new u8(')
|
|
g.expr(expr.left)
|
|
if expr.left_type.is_ptr() {
|
|
g.write('.valueOf()')
|
|
}
|
|
g.write('.str.charCodeAt(')
|
|
g.expr(expr.index)
|
|
g.write('))')
|
|
}
|
|
} else {
|
|
// TODO Does this cover all cases?
|
|
g.expr(expr.left)
|
|
if expr.left_type.is_ptr() {
|
|
g.write('.valueOf()')
|
|
}
|
|
g.write('.arr.get(')
|
|
g.write('new int(')
|
|
g.cast_stack << ast.int_type_idx
|
|
g.expr(expr.index)
|
|
g.write('.valueOf()')
|
|
g.cast_stack.delete_last()
|
|
g.write('))')
|
|
}
|
|
}
|
|
|
|
fn (mut g JsGen) gen_deref_ptr(ty ast.Type) {
|
|
mut t := ty
|
|
for t.is_ptr() {
|
|
g.write('.valueOf()')
|
|
t = t.deref()
|
|
}
|
|
}
|
|
|
|
fn (mut g JsGen) expr_string(expr ast.Expr) string {
|
|
pos := g.out.len
|
|
g.expr(expr)
|
|
return g.out.cut_to(pos).trim_space()
|
|
}
|
|
|
|
fn (mut g JsGen) gen_infix_expr(it ast.InfixExpr) {
|
|
l_sym := g.table.final_sym(it.left_type)
|
|
r_sym := g.table.final_sym(it.right_type)
|
|
|
|
is_not := it.op in [.not_in, .not_is, .ne]
|
|
if is_not {
|
|
g.write('!(')
|
|
}
|
|
is_arithmetic := it.op in [token.Kind.plus, .minus, .mul, .div, .mod, .right_shift, .left_shift,
|
|
.amp, .pipe, .xor]
|
|
|
|
if !g.pref.output_es5 && is_arithmetic && ((l_sym.kind == .i64 || l_sym.kind == .u64)
|
|
|| (r_sym.kind == .i64 || r_sym.kind == .u64)) {
|
|
// if left or right is i64 or u64 we convert them to bigint to perform operation.
|
|
greater_typ := if l_sym.kind == .i64 || l_sym.kind == .u64 {
|
|
it.left_type
|
|
} else {
|
|
it.right_type
|
|
} // g.greater_typ(it.left_type, it.right_type)
|
|
g.write('new ')
|
|
|
|
g.write('${g.typ(greater_typ)}(')
|
|
g.cast_stack << greater_typ
|
|
g.write('BigInt((')
|
|
g.expr(it.left)
|
|
g.gen_deref_ptr(it.left_type)
|
|
g.write(').\$toJS())')
|
|
g.write(' $it.op ')
|
|
g.write('BigInt((')
|
|
g.expr(it.right)
|
|
g.gen_deref_ptr(it.right_type)
|
|
g.write(').\$toJS())')
|
|
g.cast_stack.delete_last()
|
|
g.write(')')
|
|
if is_not {
|
|
g.write(')')
|
|
}
|
|
return
|
|
}
|
|
if it.op == .logical_or || it.op == .and {
|
|
g.write('new bool(')
|
|
g.expr(it.left)
|
|
g.write('.valueOf()')
|
|
g.write(it.op.str())
|
|
g.expr(it.right)
|
|
g.write('.valueOf()')
|
|
g.write(')')
|
|
} else if it.op == .eq || it.op == .ne {
|
|
node := it
|
|
left := g.unwrap(node.left_type)
|
|
right := g.unwrap(node.right_type)
|
|
has_operator_overloading := g.table.has_method(left.sym, '==')
|
|
if has_operator_overloading
|
|
|| (l_sym.kind in js.shallow_equatables && r_sym.kind in js.shallow_equatables) {
|
|
if node.op == .ne {
|
|
g.write('!')
|
|
}
|
|
g.write(g.typ(left.unaliased.set_nr_muls(0)))
|
|
g.write('__eq(')
|
|
g.expr(node.left)
|
|
g.gen_deref_ptr(left.typ)
|
|
g.write(',')
|
|
g.expr(node.right)
|
|
g.gen_deref_ptr(right.typ)
|
|
g.write(')')
|
|
} else {
|
|
g.write('vEq(')
|
|
g.expr(it.left)
|
|
g.gen_deref_ptr(it.left_type)
|
|
g.write(', ')
|
|
g.expr(it.right)
|
|
g.gen_deref_ptr(it.right_type)
|
|
g.write(')')
|
|
}
|
|
} else if l_sym.kind == .array && it.op == .left_shift { // arr << 1
|
|
g.write('array_push(')
|
|
g.expr(it.left)
|
|
mut ltyp := it.left_type
|
|
for ltyp.is_ptr() {
|
|
g.write('.val')
|
|
ltyp = ltyp.deref()
|
|
}
|
|
g.write('.arr.arr,')
|
|
array_info := l_sym.info as ast.Array
|
|
// arr << [1, 2]
|
|
if r_sym.kind == .array && array_info.elem_type != it.right_type {
|
|
g.write('...')
|
|
}
|
|
g.expr(it.right)
|
|
g.write(')')
|
|
} else if r_sym.kind in [.array, .map, .string] && it.op in [.key_in, .not_in] {
|
|
g.expr(it.right)
|
|
|
|
mut ltyp := it.right_type
|
|
for ltyp.is_ptr() {
|
|
g.write('.val')
|
|
ltyp = ltyp.deref()
|
|
}
|
|
if r_sym.kind == .map {
|
|
g.write('.map.has(')
|
|
} else if r_sym.kind == .string {
|
|
g.write('.str.includes(')
|
|
} else {
|
|
g.write('.\$includes(')
|
|
}
|
|
g.expr(it.left)
|
|
if l_sym.kind == .string {
|
|
g.write('.str')
|
|
}
|
|
g.write(')')
|
|
} else if it.op in [.key_is, .not_is] { // foo is Foo
|
|
g.expr(it.left)
|
|
g.gen_deref_ptr(it.left_type)
|
|
g.write(' instanceof ')
|
|
g.write(g.typ(it.right_type))
|
|
} else if it.op in [.lt, .gt, .ge, .le] && g.table.has_method(l_sym, '<')
|
|
&& l_sym.kind == r_sym.kind {
|
|
if it.op in [.le, .ge] {
|
|
g.write('!')
|
|
}
|
|
if it.op in [.lt, .ge] {
|
|
g.expr(it.left)
|
|
g.gen_deref_ptr(it.left_type)
|
|
g.write('.\$lt (')
|
|
g.expr(it.right)
|
|
g.gen_deref_ptr(it.right_type)
|
|
g.write(')')
|
|
} else {
|
|
g.expr(it.right)
|
|
g.gen_deref_ptr(it.right_type)
|
|
g.write('.\$lt (')
|
|
g.expr(it.left)
|
|
g.gen_deref_ptr(it.left_type)
|
|
g.write(')')
|
|
}
|
|
} else {
|
|
has_operator_overloading := g.table.has_method(l_sym, it.op.str())
|
|
if has_operator_overloading {
|
|
g.expr(it.left)
|
|
g.gen_deref_ptr(it.left_type)
|
|
name := match it.op.str() {
|
|
'+' {
|
|
'\$add'
|
|
}
|
|
'-' {
|
|
'\$sub'
|
|
}
|
|
'/' {
|
|
'\$div'
|
|
}
|
|
'*' {
|
|
'\$mul'
|
|
}
|
|
'%' {
|
|
'\$mod'
|
|
}
|
|
else {
|
|
panic('unreachable')
|
|
''
|
|
}
|
|
}
|
|
g.write('.$name (')
|
|
g.expr(it.right)
|
|
g.gen_deref_ptr(it.right_type)
|
|
g.write(')')
|
|
} else {
|
|
mut greater_typ := 0
|
|
// todo(playX): looks like this cast is always required to perform .eq operation on types.
|
|
if is_arithmetic {
|
|
greater_typ = g.greater_typ(it.left_type, it.right_type)
|
|
if g.cast_stack.len > 0 {
|
|
// needs_cast = g.cast_stack.last() != greater_typ
|
|
}
|
|
}
|
|
|
|
if is_arithmetic {
|
|
g.write('new ')
|
|
|
|
g.write('${g.typ(greater_typ)}(')
|
|
g.cast_stack << greater_typ
|
|
}
|
|
|
|
g.expr(it.left)
|
|
|
|
g.gen_deref_ptr(it.left_type)
|
|
// g.write('.val')
|
|
g.write(' $it.op ')
|
|
|
|
g.expr(it.right)
|
|
g.gen_deref_ptr(it.right_type)
|
|
// g.write('.val')
|
|
|
|
if is_arithmetic {
|
|
g.cast_stack.delete_last()
|
|
g.write(')')
|
|
}
|
|
}
|
|
}
|
|
|
|
if is_not {
|
|
g.write(')')
|
|
}
|
|
}
|
|
|
|
fn (mut g JsGen) greater_typ(left ast.Type, right ast.Type) ast.Type {
|
|
l := int(left)
|
|
r := int(right)
|
|
lr := [l, r]
|
|
if ast.string_type_idx in lr {
|
|
return ast.Type(ast.string_type_idx)
|
|
}
|
|
should_float := (l in ast.integer_type_idxs && r in ast.float_type_idxs)
|
|
|| (r in ast.integer_type_idxs && l in ast.float_type_idxs)
|
|
if should_float {
|
|
if ast.f64_type_idx in lr {
|
|
return ast.Type(ast.f64_type_idx)
|
|
}
|
|
if ast.f32_type_idx in lr {
|
|
return ast.Type(ast.f32_type_idx)
|
|
}
|
|
return ast.Type(ast.float_literal_type)
|
|
}
|
|
should_int := (l in ast.integer_type_idxs && r in ast.integer_type_idxs)
|
|
if should_int {
|
|
if ast.u64_type_idx in lr {
|
|
return ast.Type(ast.u64_type_idx)
|
|
}
|
|
// just guessing this order
|
|
if ast.i64_type_idx in lr {
|
|
return ast.Type(ast.i64_type_idx)
|
|
}
|
|
if ast.u32_type_idx in lr {
|
|
return ast.Type(ast.u32_type_idx)
|
|
}
|
|
if ast.int_type_idx in lr {
|
|
return ast.Type(ast.int_type_idx)
|
|
}
|
|
if ast.u16_type_idx in lr {
|
|
return ast.Type(ast.u16_type_idx)
|
|
}
|
|
if ast.i16_type_idx in lr {
|
|
return ast.Type(ast.i16_type_idx)
|
|
}
|
|
if ast.byte_type_idx in lr {
|
|
return ast.Type(ast.byte_type_idx)
|
|
}
|
|
if ast.i8_type_idx in lr {
|
|
return ast.Type(ast.i8_type_idx)
|
|
}
|
|
return ast.Type(ast.int_literal_type_idx)
|
|
}
|
|
return ast.Type(l)
|
|
}
|
|
|
|
fn (mut g JsGen) gen_map_init_expr(it ast.MapInit) {
|
|
// key_typ_sym := g.table.sym(it.key_type)
|
|
// value_typ_sym := g.table.sym(it.value_type)
|
|
// key_typ_str := util.no_dots(key_typ_sym.name)
|
|
// value_typ_str := util.no_dots(value_typ_sym.name)
|
|
g.writeln('new map(')
|
|
g.inc_indent()
|
|
if it.vals.len > 0 {
|
|
g.writeln('{')
|
|
g.inc_indent()
|
|
for i, key in it.keys {
|
|
val := it.vals[i]
|
|
g.write('[')
|
|
g.expr(key)
|
|
g.write('.\$toJS()]')
|
|
g.write(': ')
|
|
g.expr(val)
|
|
if i < it.keys.len - 1 {
|
|
g.write(',')
|
|
}
|
|
g.writeln('')
|
|
}
|
|
g.dec_indent()
|
|
g.write('}')
|
|
} else {
|
|
g.write('{}')
|
|
}
|
|
g.dec_indent()
|
|
g.write(')')
|
|
}
|
|
|
|
fn (mut g JsGen) type_name(raw_type ast.Type) {
|
|
typ := raw_type
|
|
sym := g.table.sym(typ)
|
|
mut s := ''
|
|
if sym.kind == .function {
|
|
// todo: properly print function signatures
|
|
if typ.is_ptr() {
|
|
s = '&function'
|
|
} else {
|
|
s = 'function'
|
|
}
|
|
} else {
|
|
s = g.table.type_to_str(g.unwrap_generic(typ))
|
|
}
|
|
g.write('new string("$s")')
|
|
}
|
|
|
|
fn (mut g JsGen) gen_selector_expr(it ast.SelectorExpr) {
|
|
if it.name_type > 0 {
|
|
node := it
|
|
match node.gkind_field {
|
|
.name {
|
|
g.type_name(it.name_type)
|
|
return
|
|
}
|
|
.typ {
|
|
g.write('new int(')
|
|
|
|
g.write('${int(g.unwrap_generic(it.name_type))}')
|
|
g.write(')')
|
|
g.write(')')
|
|
return
|
|
}
|
|
.unknown {
|
|
if node.field_name == 'name' {
|
|
g.type_name(it.name_type)
|
|
return
|
|
} else if node.field_name == 'idx' {
|
|
g.write('new int(')
|
|
g.write('${int(g.unwrap_generic(it.name_type))}')
|
|
g.write(')')
|
|
return
|
|
}
|
|
panic('unknown generic field $it.pos')
|
|
}
|
|
}
|
|
}
|
|
g.expr(it.expr)
|
|
mut ltyp := it.expr_type
|
|
lsym := g.table.sym(ltyp)
|
|
if lsym.kind != .interface_ && lsym.language != .js {
|
|
for ltyp.is_ptr() {
|
|
g.write('.val')
|
|
ltyp = ltyp.deref()
|
|
}
|
|
}
|
|
g.write('.$it.field_name')
|
|
}
|
|
|
|
fn (mut g JsGen) gen_string_inter_literal(it ast.StringInterLiteral) {
|
|
should_cast := !(g.cast_stack.len > 0 && g.cast_stack.last() == ast.string_type_idx)
|
|
if should_cast {
|
|
g.write('new ')
|
|
|
|
g.write('string(')
|
|
}
|
|
g.write('`')
|
|
for i, val in it.vals {
|
|
escaped_val := val.replace('`', '\\`')
|
|
g.write(escaped_val)
|
|
if i >= it.exprs.len {
|
|
continue
|
|
}
|
|
expr := it.exprs[i]
|
|
// fmt := it.fmts[i]
|
|
// fwidth := it.fwidths[i]
|
|
// precision := it.precisions[i]
|
|
g.write('\${')
|
|
typ := g.unwrap_generic(it.expr_types[i])
|
|
/*
|
|
g.expr(expr)
|
|
if sym.kind == .struct_ && sym.has_method('str') {
|
|
g.write('.str()')
|
|
}*/
|
|
g.gen_expr_to_string(expr, typ)
|
|
g.write('}')
|
|
}
|
|
g.write('`')
|
|
if should_cast {
|
|
g.write(')')
|
|
}
|
|
}
|
|
|
|
fn (mut g JsGen) gen_string_literal(it ast.StringLiteral) {
|
|
mut text := it.val.replace("'", "'")
|
|
text = text.replace('"', '\\"')
|
|
should_cast := !(g.cast_stack.len > 0 && g.cast_stack.last() == ast.string_type_idx)
|
|
if true || should_cast {
|
|
g.write('new ')
|
|
|
|
g.write('string(')
|
|
}
|
|
if it.is_raw {
|
|
g.writeln('(function() { let s = String(); ')
|
|
for x in text {
|
|
g.writeln('s += String.fromCharCode($x);')
|
|
}
|
|
g.writeln('return s; })()')
|
|
} else {
|
|
g.write('"')
|
|
for char in text {
|
|
if char == `\n` {
|
|
g.write('\\n')
|
|
} else {
|
|
g.write('$char.ascii_str()')
|
|
}
|
|
}
|
|
g.write('"')
|
|
}
|
|
if true || should_cast {
|
|
g.write(')')
|
|
}
|
|
}
|
|
|
|
fn (mut g JsGen) gen_struct_init(it ast.StructInit) {
|
|
type_sym := g.table.sym(it.typ)
|
|
mut name := type_sym.name
|
|
if name.contains('<') {
|
|
name = name[0..name.index('<') or { name.len }]
|
|
}
|
|
if it.fields.len == 0 && type_sym.kind != .interface_ {
|
|
if type_sym.kind == .struct_ && type_sym.language == .js {
|
|
g.write('{}')
|
|
} else {
|
|
g.write('new ${g.js_name(name)}({})')
|
|
}
|
|
} else if it.fields.len == 0 && type_sym.kind == .interface_ {
|
|
g.write('new ${g.js_name(name)}()') // JS interfaces can be instantiated with default ctor
|
|
} else if type_sym.kind == .interface_ && it.fields.len != 0 {
|
|
g.writeln('(function () {')
|
|
g.inc_indent()
|
|
g.writeln('let tmp = new ${g.js_name(name)}()')
|
|
|
|
for field in it.fields {
|
|
g.write('tmp.$field.name = ')
|
|
g.expr(field.expr)
|
|
g.writeln(';')
|
|
}
|
|
g.writeln('return tmp')
|
|
g.dec_indent()
|
|
g.writeln('})()')
|
|
} else if type_sym.kind == .struct_ && type_sym.language == .js {
|
|
g.writeln('{')
|
|
g.inc_indent()
|
|
for i, field in it.fields {
|
|
if field.name.len != 0 {
|
|
g.write('$field.name: ')
|
|
}
|
|
g.expr(field.expr)
|
|
if i < it.fields.len - 1 {
|
|
g.write(',')
|
|
}
|
|
g.writeln('')
|
|
}
|
|
g.dec_indent()
|
|
|
|
g.writeln('}')
|
|
} else {
|
|
g.writeln('(function() {')
|
|
g.inc_indent()
|
|
tmp := g.new_tmp_var()
|
|
g.writeln('let $tmp = new ${g.js_name(name)}({});')
|
|
|
|
for field in it.fields {
|
|
if field.name.len != 0 {
|
|
g.write('${tmp}.$field.name = ')
|
|
g.expr(field.expr)
|
|
}
|
|
g.write(';')
|
|
|
|
g.writeln('')
|
|
}
|
|
g.writeln('return $tmp;')
|
|
g.dec_indent()
|
|
g.writeln('})()')
|
|
}
|
|
}
|
|
|
|
fn (mut g JsGen) gen_typeof_expr(it ast.TypeOf) {
|
|
sym := g.table.sym(it.expr_type)
|
|
if sym.kind == .sum_type {
|
|
// TODO: JS sumtypes not implemented yet
|
|
} else if sym.kind == .array_fixed {
|
|
fixed_info := sym.info as ast.ArrayFixed
|
|
typ_name := g.table.get_type_name(fixed_info.elem_type)
|
|
g.write('"[$fixed_info.size]$typ_name"')
|
|
} else if sym.kind == .function {
|
|
info := sym.info as ast.FnType
|
|
fn_info := info.func
|
|
mut repr := 'fn ('
|
|
for i, arg in fn_info.params {
|
|
if i > 0 {
|
|
repr += ', '
|
|
}
|
|
repr += g.table.get_type_name(arg.typ)
|
|
}
|
|
repr += ')'
|
|
if fn_info.return_type != ast.void_type {
|
|
repr += ' ${g.table.get_type_name(fn_info.return_type)}'
|
|
}
|
|
g.write('"$repr"')
|
|
} else {
|
|
g.write('"$sym.name"')
|
|
}
|
|
}
|
|
|
|
fn (mut g JsGen) gen_cast_tmp(tmp string, typ_ ast.Type) {
|
|
// Skip cast if type is the same as the parrent caster
|
|
tsym := g.table.final_sym(typ_)
|
|
if !g.pref.output_es5 && (tsym.kind == .i64 || tsym.kind == .u64) {
|
|
g.write('new ')
|
|
|
|
g.write('$tsym.kind.str()')
|
|
g.write('(BigInt(')
|
|
g.write(tmp)
|
|
g.write('n))')
|
|
return
|
|
}
|
|
g.cast_stack << typ_
|
|
typ := g.typ(typ_)
|
|
|
|
if typ_.is_ptr() {
|
|
g.write('new \$ref(')
|
|
}
|
|
|
|
g.write('new ')
|
|
g.write('${typ}(')
|
|
g.write(tmp)
|
|
if typ == 'string' {
|
|
g.write('.toString()')
|
|
}
|
|
|
|
g.write(')')
|
|
if typ_.is_ptr() {
|
|
g.write(')')
|
|
}
|
|
|
|
g.cast_stack.delete_last()
|
|
}
|
|
|
|
fn (mut g JsGen) gen_type_cast_expr(it ast.CastExpr) {
|
|
is_literal := ((it.expr is ast.IntegerLiteral && it.typ in ast.integer_type_idxs)
|
|
|| (it.expr is ast.FloatLiteral && it.typ in ast.float_type_idxs))
|
|
from_type_sym := g.table.sym(it.expr_type)
|
|
to_type_sym := g.table.sym(it.typ) // type to be used as cast
|
|
if it.typ.is_bool() && from_type_sym.name == 'JS.Boolean' {
|
|
g.write('new bool(')
|
|
g.expr(it.expr)
|
|
g.write(')')
|
|
return
|
|
}
|
|
if it.expr_type.is_bool() && to_type_sym.name == 'JS.Boolean' {
|
|
g.expr(it.expr)
|
|
g.write('.\$toJS()')
|
|
return
|
|
}
|
|
if (to_type_sym.is_number() && from_type_sym.name == 'JS.Number')
|
|
|| (to_type_sym.is_number() && from_type_sym.name == 'JS.BigInt')
|
|
|| (to_type_sym.is_string() && from_type_sym.name == 'JS.String') {
|
|
g.write('new ${to_type_sym.kind.str()}(')
|
|
g.expr(it.expr)
|
|
g.write(')')
|
|
return
|
|
}
|
|
|
|
if (from_type_sym.is_number() && to_type_sym.name == 'JS.Number')
|
|
|| (from_type_sym.is_number() && to_type_sym.name == 'JS.BigInt')
|
|
|| (from_type_sym.is_string() && to_type_sym.name == 'JS.String') {
|
|
g.write('${g.typ(it.typ)}(')
|
|
g.expr(it.expr)
|
|
g.write('.\$toJS())')
|
|
return
|
|
}
|
|
|
|
if (from_type_sym.name == 'Any' && from_type_sym.language == .js)
|
|
|| from_type_sym.name == 'JS.Any' || from_type_sym.name == 'voidptr' {
|
|
if it.typ.is_ptr() {
|
|
g.write('new \$ref(')
|
|
}
|
|
g.expr(it.expr)
|
|
if it.typ.is_ptr() {
|
|
g.write(')')
|
|
}
|
|
return
|
|
}
|
|
|
|
// Skip cast if type is the same as the parrent caster
|
|
tsym := to_type_sym
|
|
if tsym.kind == .sum_type {
|
|
g.expr(it.expr)
|
|
return
|
|
}
|
|
if !g.pref.output_es5 && it.expr is ast.IntegerLiteral
|
|
&& (tsym.kind == .i64 || tsym.kind == .u64) {
|
|
g.write('new ')
|
|
|
|
g.write('$tsym.kind.str()')
|
|
g.write('(BigInt(')
|
|
g.write(it.expr.val)
|
|
g.write('n))')
|
|
return
|
|
}
|
|
if g.cast_stack.len > 0 && is_literal {
|
|
if it.typ == g.cast_stack[g.cast_stack.len - 1] {
|
|
g.expr(it.expr)
|
|
return
|
|
}
|
|
}
|
|
g.cast_stack << it.typ
|
|
typ := g.typ(it.typ)
|
|
if !is_literal {
|
|
if it.typ.is_ptr() {
|
|
g.write('new \$ref(')
|
|
}
|
|
|
|
g.write('new ')
|
|
|
|
g.write('${typ}(')
|
|
}
|
|
g.expr(it.expr)
|
|
if typ == 'string' && it.expr !is ast.StringLiteral {
|
|
g.write('.toString()')
|
|
}
|
|
if !is_literal {
|
|
g.write(')')
|
|
if it.typ.is_ptr() {
|
|
g.write(')')
|
|
}
|
|
}
|
|
g.cast_stack.delete_last()
|
|
}
|
|
|
|
fn (mut g JsGen) gen_integer_literal_expr(it ast.IntegerLiteral) {
|
|
typ := ast.Type(ast.int_type)
|
|
|
|
// Don't wrap integers for use in JS.foo functions.
|
|
// TODO: call.language always seems to be "v", parser bug?
|
|
if g.call_stack.len > 0 {
|
|
call := g.call_stack[g.call_stack.len - 1]
|
|
if call.language == .js {
|
|
for t in call.args {
|
|
if t.expr is ast.IntegerLiteral {
|
|
if t.expr == it {
|
|
g.write(it.val)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Skip cast if type is the same as the parrent caster
|
|
if g.cast_stack.len > 0 {
|
|
if g.cast_stack[g.cast_stack.len - 1] in ast.integer_type_idxs {
|
|
g.write('new ')
|
|
|
|
g.write('int($it.val)')
|
|
return
|
|
}
|
|
}
|
|
g.write('new ')
|
|
|
|
g.write('${g.typ(typ)}($it.val)')
|
|
}
|
|
|
|
fn (mut g JsGen) gen_float_literal_expr(it ast.FloatLiteral) {
|
|
typ := ast.Type(ast.f32_type)
|
|
|
|
// Don't wrap integers for use in JS.foo functions.
|
|
// TODO: call.language always seems to be "v", parser bug?
|
|
if g.call_stack.len > 0 {
|
|
call := g.call_stack[g.call_stack.len - 1]
|
|
if call.language == .js {
|
|
for i, t in call.args {
|
|
if t.expr is ast.FloatLiteral {
|
|
if t.expr == it {
|
|
if call.expected_arg_types[i] in ast.integer_type_idxs {
|
|
g.write(int(it.val.f64()).str())
|
|
} else {
|
|
g.write(it.val)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Skip cast if type is the same as the parrent caster
|
|
if g.cast_stack.len > 0 {
|
|
if g.cast_stack[g.cast_stack.len - 1] in ast.float_type_idxs {
|
|
g.write('new f32($it.val)')
|
|
return
|
|
} else if g.cast_stack[g.cast_stack.len - 1] in ast.integer_type_idxs {
|
|
g.write(int(it.val.f64()).str())
|
|
return
|
|
}
|
|
}
|
|
g.write('new ')
|
|
|
|
g.write('${g.typ(typ)}($it.val)')
|
|
}
|
|
|
|
fn (mut g JsGen) unwrap_generic(typ ast.Type) ast.Type {
|
|
if typ.has_flag(.generic) {
|
|
/*
|
|
resolve_generic_to_concrete should not mutate the table.
|
|
It mutates if the generic type is for example []T and the
|
|
concrete type is an array type that has not been registered
|
|
yet. This should have already happened in the checker, since
|
|
it also calls resolve_generic_to_concrete. g.table is made
|
|
non-mut to make sure no one else can accidentally mutates the table.
|
|
*/
|
|
mut muttable := unsafe { &ast.Table(g.table) }
|
|
if t_typ := muttable.resolve_generic_to_concrete(typ, if g.fn_decl != 0 {
|
|
g.fn_decl.generic_names
|
|
} else {
|
|
[]string{}
|
|
}, g.cur_concrete_types)
|
|
{
|
|
return t_typ
|
|
}
|
|
}
|
|
return typ
|
|
}
|
|
|
|
fn replace_op(s string) string {
|
|
return match s {
|
|
'+' { '_plus' }
|
|
'-' { '_minus' }
|
|
'*' { '_mult' }
|
|
'/' { '_div' }
|
|
'%' { '_mod' }
|
|
'<' { '_lt' }
|
|
'>' { '_gt' }
|
|
'==' { '_eq' }
|
|
else { '' }
|
|
}
|
|
}
|
|
|
|
fn (mut g JsGen) gen_postfix_index_expr(expr ast.IndexExpr, op token.Kind) {
|
|
left_typ := g.table.sym(expr.left_type)
|
|
// TODO: Handle splice setting if it's implemented
|
|
if expr.index is ast.RangeExpr {
|
|
if left_typ.kind == .array {
|
|
g.write('array_slice(')
|
|
} else {
|
|
g.write('string_slice(')
|
|
}
|
|
g.expr(expr.left)
|
|
if expr.left_type.is_ptr() {
|
|
g.write('.valueOf()')
|
|
}
|
|
g.write(',')
|
|
|
|
if expr.index.has_low {
|
|
g.expr(expr.index.low)
|
|
} else {
|
|
g.write('new int(0)')
|
|
}
|
|
g.write(', ')
|
|
if expr.index.has_high {
|
|
g.expr(expr.index.high)
|
|
} else {
|
|
g.expr(expr.left)
|
|
if expr.left_type.is_ptr() {
|
|
g.write('.valueOf()')
|
|
}
|
|
g.write('.len')
|
|
}
|
|
g.write(')')
|
|
} else if left_typ.kind == .map {
|
|
g.expr(expr.left)
|
|
|
|
if expr.is_setter {
|
|
g.inside_map_set = true
|
|
g.write('.map.set(')
|
|
} else {
|
|
g.write('.map.get(')
|
|
}
|
|
g.expr(expr.index)
|
|
g.write('.\$toJS()')
|
|
if !expr.is_setter {
|
|
g.write(')')
|
|
} else {
|
|
g.write(',')
|
|
lsym := g.table.sym(expr.left_type)
|
|
key_typ := match lsym.info {
|
|
ast.Map {
|
|
lsym.info.value_type
|
|
}
|
|
else {
|
|
verror('unreachable')
|
|
}
|
|
}
|
|
g.write('new ${g.typ(key_typ)}(')
|
|
|
|
g.expr(expr.left)
|
|
g.write('.map.get(')
|
|
g.expr(expr.index)
|
|
g.write('.\$toJS())')
|
|
match op {
|
|
.inc {
|
|
g.write('.val + 1)')
|
|
}
|
|
.dec {
|
|
g.write('.val - 1)')
|
|
}
|
|
else {
|
|
verror('not yet implemented')
|
|
}
|
|
}
|
|
g.write(')')
|
|
}
|
|
} else if left_typ.kind == .string {
|
|
if expr.is_setter {
|
|
// TODO: What's the best way to do this?
|
|
// 'string'[3] = `o`
|
|
} else {
|
|
// TODO: Maybe use u16 there? JS String returns values up to 2^16-1
|
|
g.write('new u8(')
|
|
g.expr(expr.left)
|
|
if expr.left_type.is_ptr() {
|
|
g.write('.valueOf()')
|
|
}
|
|
g.write('.str.charCodeAt(')
|
|
g.expr(expr.index)
|
|
g.write('))')
|
|
}
|
|
}
|
|
}
|