v/vlib/v/gen/js/builtin_types.v

420 lines
9.3 KiB
V

module js
import v.ast
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 ast.Type) string {
sym := g.table.get_type_symbol(t)
mut styp := ''
mut prefix := if g.file.mod.name == 'builtin' { 'new ' } else { 'new builtin.' }
match sym.kind {
.i8, .i16, .int, .i64, .byte, .u8, .u16, .u32, .u64, .f32, .f64, .int_literal,
.float_literal, .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(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)})'
}
.voidptr {
styp = 'null'
}
else {
// TODO
styp = 'undefined'
}
}
return styp
}
fn (mut g JsGen) sym_to_js_typ(sym ast.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'
}
.int_literal {
styp = 'int_literal'
}
.float_literal {
styp = 'float_literal'
}
.size_t {
styp = 'size_t'
}
.bool {
styp = 'bool'
}
.string {
styp = 'string'
}
.map {
styp = 'map'
}
.array {
styp = 'array'
}
.voidptr {
styp = 'any'
}
else {
// TODO
styp = 'undefined'
}
}
return styp
}
// V type to JS type
pub fn (mut g JsGen) typ(t ast.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, .isize, .byte, .u8, .u16, .u32, .u64, .usize, .f32, .f64,
.int_literal, .float_literal, .size_t {
styp = '${g.sym_to_js_typ(sym)}'
}
.bool {
styp = '${g.sym_to_js_typ(sym)}'
}
.none_ {
styp = 'undefined'
}
.string, .char {
styp = '${g.sym_to_js_typ(sym)}'
}
// 'array_array_int' => 'number[][]'
.array {
info := sym.info as ast.Array
styp = '${g.sym_to_js_typ(sym)}(${g.typ(info.elem_type)})'
}
.array_fixed {
info := sym.info as ast.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 ast.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_inst {}
// 'multi_return_int_int' => '[number, number]'
.multi_return {
info := sym.info as ast.MultiReturn
types := info.types.map(g.typ(it))
joined := types.join(', ')
styp = '[$joined]'
}
.sum_type {
// TODO: Implement sumtypes
styp = 'union_sym_type'
}
.alias {
fsym := g.table.get_final_type_symbol(t)
name := g.js_name(fsym.name)
styp += '$name'
}
.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 ast.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')
}
.thread {
panic('TODO: unhandled thread 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 []ast.Param, return_type ast.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"]'
}
struct BuiltinPrototypeConfig {
typ_name string
val_name string = 'val'
default_value string
constructor string = 'this.val = val'
value_of string = 'this.val'
to_string string = 'this.val.toString()'
eq string = 'this.val === other.val'
to_jsval string = 'this'
extras string
has_strfn bool
}
fn (mut g JsGen) gen_builtin_prototype(c BuiltinPrototypeConfig) {
g.writeln('function ${c.typ_name}($c.val_name = $c.default_value) { $c.constructor }')
g.writeln('${c.typ_name}.prototype = {')
g.inc_indent()
g.writeln('$c.val_name: $c.default_value,')
if c.extras.len > 0 {
g.writeln('$c.extras,')
}
for method in g.method_fn_decls[c.typ_name] {
g.inside_def_typ_decl = true
g.gen_method_decl(method, .struct_method)
g.inside_def_typ_decl = false
g.writeln(',')
}
g.writeln('valueOf() { return $c.value_of },')
g.writeln('toString() { return $c.to_string },')
g.writeln('eq(other) { return $c.eq },')
g.writeln('\$toJS() { return $c.to_jsval }, ')
if c.has_strfn {
g.writeln('str() { return new string(this.toString()) }')
}
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', 'u16', 'u32', 'int_literal', 'size_t' {
// TODO: Bounds checking
g.gen_builtin_prototype(
typ_name: typ_name
default_value: 'new Number(0)'
constructor: 'this.val = Number(val)'
value_of: 'Number(this.val)'
to_string: 'this.valueOf().toString()'
eq: 'this.valueOf() === other.valueOf()'
to_jsval: '+this'
)
}
// u64 and i64 are so big that their values do not fit into JS number so we use BigInt.
'u64' {
g.gen_builtin_prototype(
typ_name: typ_name
default_value: 'BigInt(0)'
constructor: 'this.val = BigInt.asUintN(64,BigInt(val))'
value_of: 'this.val'
to_string: 'this.val.toString()'
eq: 'this.valueOf() === other.valueOf()'
to_jsval: 'this.val'
)
}
'i64' {
g.gen_builtin_prototype(
typ_name: typ_name
default_value: 'BigInt(0)'
constructor: 'this.val = BigInt.asIntN(64,BigInt(val))'
value_of: 'this.val'
to_string: 'this.val.toString()'
eq: 'this.valueOf() === other.valueOf()'
to_jsval: 'this.val'
)
}
'byte' {
g.gen_builtin_prototype(
typ_name: typ_name
default_value: 'new Number(0)'
constructor: 'if (typeof(val) == "string") { this.val = val.charCodeAt() } else if (val instanceof string) { this.val = val.str.charCodeAt(); } else { this.val = val | 0 }'
value_of: 'this.val | 0'
to_string: 'new string(this.val + "")'
eq: 'this.valueOf() === other.valueOf()'
to_jsval: '+this'
)
}
'f32', 'f64', 'float_literal' {
g.gen_builtin_prototype(
typ_name: typ_name
constructor: 'this.val = Number(val)'
default_value: 'new Number(0)'
to_jsval: '+this'
)
}
'bool' {
g.gen_builtin_prototype(
constructor: 'this.val = +val !== 0'
typ_name: typ_name
default_value: 'new Boolean(false)'
to_jsval: '+this != 0'
eq: 'this.val === other.valueOf()'
)
}
'string' {
g.gen_builtin_prototype(
typ_name: typ_name
val_name: 'str'
default_value: 'new String("")'
constructor: 'this.str = str.toString(); this.len = this.str.length'
value_of: 'this.str'
to_string: 'this.str'
eq: 'this.str === other.str'
has_strfn: false
to_jsval: 'this.str'
)
}
'map' {
g.gen_builtin_prototype(
typ_name: typ_name
val_name: 'map'
default_value: 'new map(new Map())'
constructor: 'this.map = map'
value_of: 'this'
to_string: 'this.map.toString()'
eq: 'vEq(this, other)'
to_jsval: 'this.map'
)
}
'array' {
g.gen_builtin_prototype(
typ_name: typ_name
val_name: 'arr'
default_value: 'new Array()'
constructor: 'this.arr = arr'
value_of: 'this'
to_string: 'JSON.stringify(this.arr.map(it => it.valueOf()))'
eq: 'vEq(this, other)'
to_jsval: 'this.arr'
)
}
'any' {
g.gen_builtin_prototype(
typ_name: typ_name
val_name: 'any'
default_value: 'null'
constructor: 'this.val = any'
value_of: 'this.val'
to_string: '"&" + this.val'
eq: 'this == other' // compare by ptr
to_jsval: 'this.val.\$toJS()'
)
}
else {}
}
}
g.dec_indent()
}