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