pull/7199/head
Leah Lundqvist 2020-12-08 17:49:20 +01:00 committed by GitHub
parent a2ec52b8c4
commit 90c1c639fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 476 additions and 223 deletions

View File

@ -4,14 +4,18 @@
module builtin module builtin
fn (a any) toString()
pub fn println(s any) { 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) { pub fn print(s any) {
// TODO // TODO
// $if js.node { // $if js.node {
JS.process.stdout.write(s) JS.process.stdout.write(s.toString())
// } $else { // } $else {
// panic('Cannot `print` in a browser, use `println` instead') // panic('Cannot `print` in a browser, use `println` instead')
// } // }

View File

@ -7,6 +7,23 @@
module builtin 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 // Top level functions
fn JS.eval(string) any fn JS.eval(string) any
fn JS.parseInt(string, f64) f64 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.sin(f64) f64
fn JS.Math.sqrt(f64) f64 fn JS.Math.sqrt(f64) f64
fn JS.Math.tan(f64) f64 fn JS.Math.tan(f64) f64
// JSON
fn JS.JSON.stringify(any) string
fn JS.JSON.parse(string) any

View File

@ -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))
}

View File

@ -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()
}

View File

@ -3,6 +3,7 @@ module js
import strings import strings
import v.ast import v.ast
import v.table import v.table
import v.token
import v.pref import v.pref
import v.util import v.util
import v.depgraph import v.depgraph
@ -13,7 +14,9 @@ const (
'default', 'delete', 'do', 'else', 'enum', 'export', 'extends', 'finally', 'for', 'function', 'if', 'default', 'delete', 'do', 'else', 'enum', 'export', 'extends', 'finally', 'for', 'function', 'if',
'implements', 'import', 'in', 'instanceof', 'interface', 'let', 'new', 'package', 'private', 'protected', 'implements', 'import', 'in', 'instanceof', 'interface', 'let', 'new', 'package', 'private', 'protected',
'public', 'return', 'static', 'super', 'switch', 'this', 'throw', 'try', 'typeof', 'var', 'void', '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', 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'] '\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 table &table.Table
pref &pref.Preferences pref &pref.Preferences
mut: mut:
definitions strings.Builder definitions strings.Builder
ns &Namespace ns &Namespace
namespaces map[string]&Namespace namespaces map[string]&Namespace
doc &JsDoc doc &JsDoc
enable_doc bool enable_doc bool
file ast.File file ast.File
tmp_count int tmp_count int
inside_ternary bool inside_ternary bool
inside_loop bool inside_loop bool
inside_map_set bool // map.set(key, value) inside_map_set bool // map.set(key, value)
inside_builtin bool inside_builtin bool
is_test bool generated_builtin bool
stmt_start_pos int inside_def_typ_decl bool
defer_stmts []ast.DeferStmt is_test bool
fn_decl &ast.FnDecl // pointer to the FnDecl we are currently inside otherwise 0 stmt_start_pos int
str_types []string // types that need automatic str() generation defer_stmts []ast.DeferStmt
method_fn_decls map[string][]ast.FnDecl fn_decl &ast.FnDecl // pointer to the FnDecl we are currently inside otherwise 0
builtin_fns []string // Functions defined in `builtin` str_types []string // types that need automatic str() generation
empty_line bool 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 { 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 imports << imp.mod
} }
graph.add(g.file.mod.name, imports) 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) g.stmts(file.stmts)
// store the current namespace // store the current namespace
g.escape_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\t/* module exports */'
} }
out += '\n\treturn {' 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 { for i, pub_var in g.namespaces[node.name].pub_vars {
out += '\n\t\t$pub_var' out += '\n\t\t$pub_var'
if i < g.namespaces[node.name].pub_vars.len - 1 { 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 += 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 { if pref.is_shared {
// Export, through CommonJS, the module of the entry file if `-shared` was passed // Export, through CommonJS, the module of the entry file if `-shared` was passed
export := nodes[nodes.len - 1].name 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 return out
} }
@ -175,7 +207,7 @@ pub fn (mut g JsGen) find_class_methods(stmts []ast.Stmt) {
ast.FnDecl { ast.FnDecl {
if stmt.is_method { if stmt.is_method {
// Found struct method, store it to be generated along with the class. // 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. // Workaround until `map[key] << val` works.
mut arr := g.method_fn_decls[class_name] mut arr := g.method_fn_decls[class_name]
arr << stmt arr << stmt
@ -199,171 +231,6 @@ pub fn (g JsGen) hashes() string {
return res 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] [inline]
fn verror(msg string) { fn verror(msg string) {
eprintln('jsgen error: $msg') eprintln('jsgen error: $msg')
@ -576,9 +443,7 @@ fn (mut g JsGen) expr(node ast.Expr) {
// TODO // TODO
} }
ast.CastExpr { ast.CastExpr {
// JS has no types, so no need to cast g.gen_type_cast_expr(node)
// Just write the expression inside
g.expr(node.expr)
} }
ast.CharLiteral { ast.CharLiteral {
g.write("'$node.val'") g.write("'$node.val'")
@ -593,7 +458,7 @@ fn (mut g JsGen) expr(node ast.Expr) {
g.write('${styp}.$node.val') g.write('${styp}.$node.val')
} }
ast.FloatLiteral { ast.FloatLiteral {
g.write(node.val) g.write('${g.typ(table.Type(table.f32_type))}($node.val)')
} }
ast.Ident { ast.Ident {
g.gen_ident(node) g.gen_ident(node)
@ -611,7 +476,7 @@ fn (mut g JsGen) expr(node ast.Expr) {
g.gen_infix_expr(node) g.gen_infix_expr(node)
} }
ast.IntegerLiteral { ast.IntegerLiteral {
g.write(node.val) g.write('${g.typ(table.Type(table.int_type))}($node.val)')
} }
ast.LockExpr { ast.LockExpr {
g.gen_lock_expr(node) g.gen_lock_expr(node)
@ -664,8 +529,9 @@ fn (mut g JsGen) expr(node ast.Expr) {
g.gen_string_inter_literal(node) g.gen_string_inter_literal(node)
} }
ast.StringLiteral { ast.StringLiteral {
text := node.val.replace('`', '\\`') text := node.val.replace('\'', "\\'")
g.write('`$text`') if g.file.mod.name == 'builtin' { g.write('new ') }
g.write("string('$text')")
} }
ast.StructInit { ast.StructInit {
// `user := User{name: 'Bob'}` // `user := User{name: 'Bob'}`
@ -904,6 +770,12 @@ fn (mut g JsGen) gen_method_decl(it ast.FnDecl) {
} }
if !it.is_method { if !it.is_method {
g.write('function ') 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}(') g.write('${name}(')
if it.is_pub && !it.is_method { 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) { 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 return
} }
if name in v_types && g.ns.name == 'builtin' {
return
}
js_name := g.js_name(name)
g.gen_attrs(node.attrs) g.gen_attrs(node.attrs)
g.doc.gen_fac_fn(node.fields) 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 { for i, field in node.fields {
g.write('$field.name = ') g.write('$field.name = ')
if field.has_default_expr { if field.has_default_expr {
@ -1117,31 +994,41 @@ fn (mut g JsGen) gen_struct_decl(node ast.StructDecl) {
} }
g.dec_indent() g.dec_indent()
g.writeln('};') g.writeln('};')
g.writeln('${g.js_name(node.name)}.prototype = {') g.writeln('${js_name}.prototype = {')
g.inc_indent() g.inc_indent()
fns := g.method_fn_decls[node.name] fns := g.method_fn_decls[name]
for i, field in node.fields { for field in node.fields {
typ := g.typ(field.typ) typ := g.typ(field.typ)
g.doc.gen_typ(typ) g.doc.gen_typ(typ)
g.write('$field.name: ${g.to_js_typ_val(field.typ)}') g.write('$field.name: ${g.to_js_typ_val(field.typ)}')
if i < node.fields.len - 1 || fns.len > 0 { g.writeln(',')
g.writeln(',')
} else {
g.writeln('')
}
} }
for i, cfn in fns { for cfn in fns {
g.gen_method_decl(cfn) g.gen_method_decl(cfn)
if i < fns.len - 1 { g.writeln(',')
g.writeln(',')
} else {
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.dec_indent()
g.writeln('};\n') g.writeln('};\n')
if node.is_pub { 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 `is`
// TODO handle optionals // TODO handle optionals
g.write(name) g.write(name)
// TODO: Generate .val for basic types
} }
fn (mut g JsGen) gen_lock_expr(node ast.LockExpr) { 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.write('.get(')
} }
g.expr(expr.index) g.expr(expr.index)
g.write('.toString()')
if !expr.is_setter { if !expr.is_setter {
g.write(')') g.write(')')
} }
@ -1401,7 +1290,7 @@ fn (mut g JsGen) gen_index_expr(expr ast.IndexExpr) {
// 'string'[3] = `o` // 'string'[3] = `o`
} else { } else {
g.expr(expr.left) g.expr(expr.left)
g.write('.charCodeAt(') g.write('.str.charCodeAt(')
g.expr(expr.index) g.expr(expr.index)
g.write(')') g.write(')')
} }
@ -1433,10 +1322,15 @@ fn (mut g JsGen) gen_infix_expr(it ast.InfixExpr) {
g.expr(it.right) g.expr(it.right)
g.write(if r_sym.kind == .map { g.write(if r_sym.kind == .map {
'.has(' '.has('
} else if r_sym.kind == .string {
'.str.includes('
} else { } else {
'.includes(' '.includes('
}) })
g.expr(it.left) g.expr(it.left)
if l_sym.kind == .string {
g.write('.str')
}
g.write(')') g.write(')')
if it.op == .not_in { if it.op == .not_in {
g.write(')') g.write(')')
@ -1454,8 +1348,12 @@ fn (mut g JsGen) gen_infix_expr(it ast.InfixExpr) {
} else { } else {
both_are_int := int(it.left_type) in table.integer_type_idxs && both_are_int := int(it.left_type) in table.integer_type_idxs &&
int(it.right_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 { if it.op == .div && both_are_int {
g.write('parseInt(') g.write('((')
} }
g.expr(it.left) g.expr(it.left)
// in js == is non-strict & === is strict, always do strict // 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.write(' $it.op ')
} }
g.expr(it.right) 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 { 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) { fn (mut g JsGen) gen_map_init_expr(it ast.MapInit) {
// key_typ_sym := g.table.get_type_symbol(it.key_type) // key_typ_sym := g.table.get_type_symbol(it.key_type)
// value_typ_sym := g.table.get_type_symbol(it.value_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) { 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 { for i, val in it.vals {
escaped_val := val.replace('`', '\\`') escaped_val := val.replace('`', '\\`')
g.write(escaped_val) 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('`') g.write('`)')
} }
fn (mut g JsGen) gen_struct_init(it ast.StructInit) { 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"') 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(')')
}
}

View File

@ -78,6 +78,8 @@ for i, _ in arr2 { println(i) }
println('\n\n4 to 5\t=> ') println('\n\n4 to 5\t=> ')
for _, v in slice3 { println(v) } for _, v in slice3 { println(v) }
println(int(1.5))
println('\n\n') println('\n\n')
// map // map

View File

@ -34,13 +34,16 @@ fn main() {
mut a := 1 mut a := 1
a *= 2 a *= 2
a += 3 a += 3
println(a) // TODO: Handle string interpolation println(a)
mut b := hl.Aaa{} mut b := hl.Aaa{}
b.update('an update') b.update('an update')
println(b) println(b)
mut c := Foo{hl.Aaa{}} mut c := Foo{hl.Aaa{}}
c.a.update('another update') c.a.update('another update')
println(c) println(c)
println('int(1.5) == "${int(1.5)}"')
d := int(10) + f32(127)
println('typeof (int + f32) == "${typeof(d)}"')
_ = 'done' _ = 'done'
{ {
_ = 'block' _ = 'block'
@ -52,7 +55,7 @@ fn main() {
await := '$super: $debugger' await := '$super: $debugger'
mut finally := 'implemented' mut finally := 'implemented'
println('$await $finally') println('$await $finally')
dun := i_am_a_const * 20 dun := i_am_a_const * 20 + 2
dunn := hl.hello // External constant dunn := hl.hello // External constant
_ = hl1.nested() _ = hl1.nested()
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {

View File

@ -213,7 +213,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
if p.tok.kind == .name { if p.tok.kind == .name {
// TODO high order fn // TODO high order fn
name = if language == .js { p.check_js_name() } else { p.check_name() } 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') p.error('function names cannot contain uppercase letters, use snake_case instead')
return ast.FnDecl{} return ast.FnDecl{}
} }