jsgen: many fixes and updates

pull/4962/head
spaceface777 2020-05-20 16:57:42 +02:00 committed by GitHub
parent a0ed1e2878
commit dd534fde57
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 474 additions and 197 deletions

View File

@ -19,29 +19,30 @@ const (
) )
struct JsGen { struct JsGen {
table &table.Table table &table.Table
definitions strings.Builder definitions strings.Builder
pref &pref.Preferences pref &pref.Preferences
mut: mut:
out strings.Builder out strings.Builder
namespaces map[string]strings.Builder namespaces map[string]strings.Builder
namespaces_pub map[string][]string namespaces_pub map[string][]string
namespace_imports map[string]map[string]string namespace_imports map[string]map[string]string
namespace string namespace string
doc &JsDoc doc &JsDoc
constants strings.Builder // all global V constants enable_doc bool
file ast.File constants strings.Builder // all global V constants
tmp_count int file ast.File
inside_ternary bool tmp_count int
inside_loop bool inside_ternary bool
is_test bool inside_loop bool
indents map[string]int // indentations mapped to namespaces is_test bool
stmt_start_pos int indents map[string]int // indentations mapped to namespaces
defer_stmts []ast.DeferStmt stmt_start_pos int
fn_decl &ast.FnDecl // pointer to the FnDecl we are currently inside otherwise 0 defer_stmts []ast.DeferStmt
str_types []string // types that need automatic str() generation fn_decl &ast.FnDecl // pointer to the FnDecl we are currently inside otherwise 0
method_fn_decls map[string][]ast.Stmt str_types []string // types that need automatic str() generation
empty_line bool method_fn_decls map[string][]ast.Stmt
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 {
@ -54,8 +55,13 @@ pub fn gen(files []ast.File, table &table.Table, pref &pref.Preferences) string
fn_decl: 0 fn_decl: 0
empty_line: true empty_line: true
doc: 0 doc: 0
enable_doc: true
} }
g.doc = new_jsdoc(g) g.doc = new_jsdoc(g)
// TODO: Add '[-no]-jsdoc' flag
if pref.is_prod {
g.enable_doc = false
}
g.init() g.init()
mut graph := depgraph.new_dep_graph() mut graph := depgraph.new_dep_graph()
@ -92,18 +98,18 @@ pub fn gen(files []ast.File, table &table.Table, pref &pref.Preferences) string
g.finish() g.finish()
mut out := g.hashes() + g.definitions.str() + g.constants.str() mut out := g.hashes() + g.definitions.str() + g.constants.str()
for node in deps_resolved.nodes { for node in deps_resolved.nodes {
out += '\n/* namespace: $node.name */\n' out += '/* namespace: $node.name */\n'
out += 'const $node.name = (function (' out += 'const $node.name = (function ('
imports := g.namespace_imports[node.name] imports := g.namespace_imports[node.name]
for i, key in imports.keys() { for i, key in imports.keys() {
if i > 0 { out += ', ' } if i > 0 { out += ', ' }
out += imports[key] out += imports[key]
} }
out += ') {' out += ') {\n\t'
// private scope // private scope
out += g.namespaces[node.name].str() out += g.namespaces[node.name].str().trim_space()
// public scope // public scope
out += '\n\t/* module exports */' out += '\n\n\t/* module exports */'
out += '\n\treturn {' out += '\n\treturn {'
for pub_var in g.namespaces_pub[node.name] { for pub_var in g.namespaces_pub[node.name] {
out += '\n\t\t$pub_var,' out += '\n\t\t$pub_var,'
@ -114,7 +120,7 @@ pub fn gen(files []ast.File, table &table.Table, pref &pref.Preferences) string
if i > 0 { out += ', ' } if i > 0 { out += ', ' }
out += key out += key
} }
out += ');' out += ');\n\n'
} }
return out return out
} }
@ -125,20 +131,19 @@ pub fn (mut g JsGen) enter_namespace(n string) {
// create a new namespace // create a new namespace
g.out = strings.new_builder(100) g.out = strings.new_builder(100)
g.indents[g.namespace] = 0 g.indents[g.namespace] = 0
} } else {
else {
g.out = g.namespaces[g.namespace] g.out = g.namespaces[g.namespace]
} }
} }
pub fn (mut g JsGen) escape_namespace() { pub fn (mut g JsGen) escape_namespace() {
g.namespaces[g.namespace] = g.out g.namespaces[g.namespace] = g.out
g.namespace = "" g.namespace = ''
} }
pub fn (mut g JsGen) push_pub_var(s string) { pub fn (mut g JsGen) push_pub_var(s string) {
mut arr := g.namespaces_pub[g.namespace] mut arr := g.namespaces_pub[g.namespace]
arr << s arr << g.js_name(s)
g.namespaces_pub[g.namespace] = arr g.namespaces_pub[g.namespace] = arr
} }
@ -148,7 +153,7 @@ pub fn (mut g JsGen) find_class_methods(stmts []ast.Stmt) {
ast.FnDecl { ast.FnDecl {
if it.is_method { if it.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_symbol(it.receiver.typ).name class_name := g.table.get_type_name(it.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
@ -161,7 +166,7 @@ pub fn (mut g JsGen) find_class_methods(stmts []ast.Stmt) {
} }
pub fn (mut g JsGen) init() { pub fn (mut g JsGen) init() {
g.definitions.writeln('// Generated by the V compiler') g.definitions.writeln('// Generated by the V compiler\n')
g.definitions.writeln('"use strict";') g.definitions.writeln('"use strict";')
g.definitions.writeln('') g.definitions.writeln('')
} }
@ -170,7 +175,7 @@ pub fn (mut g JsGen) finish() {
if g.constants.len > 0 { if g.constants.len > 0 {
constants := g.constants.str() constants := g.constants.str()
g.constants = strings.new_builder(100) g.constants = strings.new_builder(100)
g.constants.writeln('const CONSTANTS = Object.freeze({') g.constants.writeln('const _CONSTS = Object.freeze({')
g.constants.write(constants) g.constants.write(constants)
g.constants.writeln('});') g.constants.writeln('});')
g.constants.writeln('') g.constants.writeln('')
@ -179,7 +184,7 @@ pub fn (mut g JsGen) finish() {
pub fn (g JsGen) hashes() string { pub fn (g JsGen) hashes() string {
mut res := '// V_COMMIT_HASH ${util.vhash()}\n' mut res := '// V_COMMIT_HASH ${util.vhash()}\n'
res += '// V_CURRENT_COMMIT_HASH ${util.githash(g.pref.building_v)}\n\n' res += '// V_CURRENT_COMMIT_HASH ${util.githash(g.pref.building_v)}\n'
return res return res
} }
@ -187,9 +192,28 @@ pub fn (g JsGen) hashes() string {
// V type to JS type // V type to JS type
pub fn (mut g JsGen) typ(t table.Type) string { pub fn (mut g JsGen) typ(t table.Type) string {
sym := g.table.get_type_symbol(t) sym := g.table.get_type_symbol(t)
mut styp := sym.name.replace('.', '__') mut styp := sym.name
if styp.starts_with('JS__') { if styp.starts_with('JS.') {
styp = styp[4..] styp = styp[3..]
}
// 'multi_return_int_int' => '[number, number]'
if styp.starts_with('multi_return_') {
tokens := styp.replace('multi_return_', '').split('_')
return '[' + tokens.map(g.to_js_typ(it)).join(', ') + ']'
}
// 'anon_fn_7_7_1' => '(a number, b number) => void'
if styp.starts_with('anon_') {
info := sym.info as table.FnType
mut res := '('
for i, arg in info.func.args {
res += '$arg.name: ${g.typ(arg.typ)}'
if i < info.func.args.len - 1 { res += ', ' }
}
return res + ') => ' + g.typ(info.func.return_type)
}
// Struct instance => ns["class"]["prototype"]
if sym.kind == .struct_ {
return g.to_js_typ(styp) + '["prototype"]'
} }
return g.to_js_typ(styp) return g.to_js_typ(styp)
} }
@ -223,6 +247,14 @@ fn (mut g JsGen) to_js_typ(typ string) string {
} }
} }
} }
// ns.export => ns["export"]
for i, v in styp.split('.') {
if i == 0 {
styp = v
continue
}
styp += '["$v"]'
}
return styp return styp
} }
@ -259,13 +291,42 @@ pub fn (mut g JsGen) new_tmp_var() string {
return '_tmp$g.tmp_count' return '_tmp$g.tmp_count'
} }
// 'mod1.mod2.fn' => 'mod1.mod2'
// 'fn' => ''
[inline] [inline]
fn js_name(name string) string { fn get_ns(s string) string {
// name := name_.replace('.', '__') parts := s.split('.')
if name in js_reserved { mut res := ''
return 'v_$name' for i, p in parts {
if i == parts.len - 1 { break } // Last part (fn/struct/var name): skip
res += p
if i < parts.len - 2 { res += '.' } // Avoid trailing dot
} }
return name return res
}
fn (mut g JsGen) get_alias(name string) string {
// TODO: This is a hack; find a better way to do this
split := name.split('.')
if split.len > 1 {
imports := g.namespace_imports[g.namespace]
alias := imports[split[0]]
if alias != '' {
return alias + '.' + split[1..].join('.')
}
}
return name // No dot == no alias
}
fn (mut g JsGen) js_name(name_ string) string {
ns := get_ns(name_)
mut name := if ns == g.namespace { name_.split('.').last() } else { g.get_alias(name_) }
mut parts := name.split('.')
for i, p in parts {
if p in js_reserved { parts[i] = 'v_$p' }
}
return parts.join('.')
} }
fn (mut g JsGen) stmts(stmts []ast.Stmt) { fn (mut g JsGen) stmts(stmts []ast.Stmt) {
@ -337,7 +398,7 @@ fn (mut g JsGen) stmt(node ast.Stmt) {
g.writeln('') g.writeln('')
} }
ast.GotoLabel { ast.GotoLabel {
g.writeln('${js_name(it.name)}:') g.writeln('${g.js_name(it.name)}:')
} }
ast.GotoStmt { ast.GotoStmt {
// skip: JS has no goto // skip: JS has no goto
@ -377,11 +438,13 @@ fn (mut g JsGen) expr(node ast.Expr) {
ast.ArrayInit { ast.ArrayInit {
g.gen_array_init_expr(it) g.gen_array_init_expr(it)
} }
ast.AssignExpr {
g.gen_assign_expr(it)
}
ast.BoolLiteral { ast.BoolLiteral {
if it.val == true { if it.val == true {
g.write('true') g.write('true')
} } else {
else {
g.write('false') g.write('false')
} }
} }
@ -389,30 +452,18 @@ fn (mut g JsGen) expr(node ast.Expr) {
g.write("'$it.val'") g.write("'$it.val'")
} }
ast.CallExpr { ast.CallExpr {
mut name := "" mut name := ''
if it.name.starts_with('JS.') { if it.name.starts_with('JS.') {
name = it.name[3..] name = it.name[3..]
} else { } else {
name = it.name name = g.js_name(it.name)
// TODO: Ugly fix until `it.is_method` and `it.left` gets fixed.
// `it.left` should be the name of the module in this case.
// TODO: This should be in `if it.is_method` instead but is_method seems to be broken.
dot_idx := name.index('.') or {-1} // is there a way to do `if optional()`?
if dot_idx > -1 {
split := name.split('.')
imports := g.namespace_imports[g.namespace]
alias := imports[split.first()]
if alias != "" {
name = alias + "." + split[1..].join(".")
}
}
} }
g.expr(it.left) g.expr(it.left)
if it.is_method { if it.is_method {
// example: foo.bar.baz() // example: foo.bar.baz()
g.write('.') g.write('.')
} }
g.write('${js_name(name)}(') g.write('${g.js_name(name)}(')
for i, arg in it.args { for i, arg in it.args {
g.expr(arg.expr) g.expr(arg.expr)
if i != it.args.len - 1 { if i != it.args.len - 1 {
@ -479,8 +530,8 @@ fn (mut g JsGen) expr(node ast.Expr) {
g.gen_selector_expr(it) g.gen_selector_expr(it)
} }
ast.AnonFn { ast.AnonFn {
g.gen_anon_fn_decl(it) g.gen_anon_fn_decl(it)
} }
else { else {
println(term.red('jsgen.expr(): bad node "${typeof(node)}"')) println(term.red('jsgen.expr(): bad node "${typeof(node)}"'))
} }
@ -514,7 +565,7 @@ fn (mut g JsGen) gen_string_inter_literal(it ast.StringInterLiteral) {
// `expr ? "true" : "false"` // `expr ? "true" : "false"`
g.expr(expr) g.expr(expr)
g.write(' ? "true" : "false"') g.write(' ? "true" : "false"')
} else { } else {
sym := g.table.get_type_symbol(it.expr_types[i]) sym := g.table.get_type_symbol(it.expr_types[i])
match sym.kind { match sym.kind {
@ -538,7 +589,7 @@ fn (mut g JsGen) gen_import_stmt(it ast.Import) {
mut imports := g.namespace_imports[g.namespace] mut imports := g.namespace_imports[g.namespace]
imports[it.mod] = it.alias imports[it.mod] = it.alias
g.namespace_imports[g.namespace] = imports g.namespace_imports[g.namespace] = imports
} }
fn (mut g JsGen) gen_array_init_expr(it ast.ArrayInit) { fn (mut g JsGen) gen_array_init_expr(it ast.ArrayInit) {
type_sym := g.table.get_type_symbol(it.typ) type_sym := g.table.get_type_symbol(it.typ)
@ -589,7 +640,7 @@ fn (mut g JsGen) gen_assign_stmt(it ast.AssignStmt) {
styp := g.typ(ident_var_info.typ) styp := g.typ(ident_var_info.typ)
jsdoc.write(styp) jsdoc.write(styp)
stmt.write(js_name(ident.name)) stmt.write(g.js_name(ident.name))
if i < it.left.len - 1 { if i < it.left.len - 1 {
jsdoc.write(', ') jsdoc.write(', ')
@ -602,23 +653,19 @@ fn (mut g JsGen) gen_assign_stmt(it ast.AssignStmt) {
g.write(stmt.str()) g.write(stmt.str())
g.expr(it.right[0]) g.expr(it.right[0])
g.writeln(';') g.writeln(';')
} } else {
else {
// `a := 1` | `a,b := 1,2` // `a := 1` | `a,b := 1,2`
for i, ident in it.left { for i, ident in it.left {
val := it.right[i] val := it.right[i]
ident_var_info := ident.var_info() ident_var_info := ident.var_info()
mut styp := g.typ(ident_var_info.typ) mut styp := g.typ(ident_var_info.typ)
match val { if val is ast.EnumVal {
ast.EnumVal { // we want the type of the enum value not the enum
// we want the type of the enum value not the enum styp = 'number'
styp = 'number' } else if val is ast.StructInit {
} // no need to print jsdoc for structs
ast.StructInit { styp = ''
// no need to print jsdoc for structs
styp = ''
} else {}
} }
if !g.inside_loop && styp.len > 0 { if !g.inside_loop && styp.len > 0 {
@ -631,11 +678,11 @@ fn (mut g JsGen) gen_assign_stmt(it ast.AssignStmt) {
g.write('const ') g.write('const ')
} }
g.write('${js_name(ident.name)} = ') g.write('${g.js_name(ident.name)} = ')
g.expr(val) g.expr(val)
if g.inside_loop { if g.inside_loop {
g.write("; ") g.write('; ')
} else { } else {
g.writeln(';') g.writeln(';')
} }
@ -643,6 +690,12 @@ fn (mut g JsGen) gen_assign_stmt(it ast.AssignStmt) {
} }
} }
fn (mut g JsGen) gen_assign_expr(it ast.AssignExpr) {
g.expr(it.left)
g.write(' $it.op ')
g.expr(it.val)
}
fn (mut g JsGen) gen_attr(it ast.Attr) { fn (mut g JsGen) gen_attr(it ast.Attr) {
g.writeln('/* [$it.name] */') g.writeln('/* [$it.name] */')
} }
@ -667,11 +720,13 @@ fn (mut g JsGen) gen_const_decl(it ast.ConstDecl) {
g.expr(field.expr) g.expr(field.expr)
val := g.out.after(pos) val := g.out.after(pos)
g.out.go_back(val.len) g.out.go_back(val.len)
typ := g.typ(field.typ) if g.enable_doc {
typ := g.typ(field.typ)
g.constants.write('\t')
g.constants.writeln(g.doc.gen_typ(typ, field.name))
}
g.constants.write('\t') g.constants.write('\t')
g.constants.writeln(g.doc.gen_typ(typ, field.name)) g.constants.write('${g.js_name(field.name)}: $val')
g.constants.write('\t')
g.constants.write('${js_name(field.name)}: $val')
if i < it.fields.len - 1 { if i < it.fields.len - 1 {
g.constants.writeln(',') g.constants.writeln(',')
} }
@ -689,7 +744,7 @@ fn (mut g JsGen) gen_defer_stmts() {
} }
fn (mut g JsGen) gen_enum_decl(it ast.EnumDecl) { fn (mut g JsGen) gen_enum_decl(it ast.EnumDecl) {
g.writeln('const ${js_name(it.name)} = Object.freeze({') g.writeln('const ${g.js_name(it.name)} = Object.freeze({')
g.inc_indent() g.inc_indent()
for i, field in it.fields { for i, field in it.fields {
g.write('$field.name: ') g.write('$field.name: ')
@ -714,16 +769,8 @@ fn (mut g JsGen) gen_enum_decl(it ast.EnumDecl) {
fn (mut g JsGen) gen_expr_stmt(it ast.ExprStmt) { fn (mut g JsGen) gen_expr_stmt(it ast.ExprStmt) {
g.expr(it.expr) g.expr(it.expr)
expr := it.expr expr := it.expr
match expr { if expr is ast.IfExpr { } // no ; after an if expression
ast.IfExpr { else if !g.inside_ternary { g.writeln(';') }
// no ; after an if expression
}
else {
if !g.inside_ternary {
g.writeln(';')
}
}
}
} }
fn (mut g JsGen) gen_fn_decl(it ast.FnDecl) { fn (mut g JsGen) gen_fn_decl(it ast.FnDecl) {
@ -738,8 +785,8 @@ fn (mut g JsGen) gen_fn_decl(it ast.FnDecl) {
} }
fn (mut g JsGen) gen_anon_fn_decl(it ast.AnonFn) { fn (mut g JsGen) gen_anon_fn_decl(it ast.AnonFn) {
g.gen_method_decl(it.decl) g.gen_method_decl(it.decl)
} }
fn (mut g JsGen) gen_method_decl(it ast.FnDecl) { fn (mut g JsGen) gen_method_decl(it ast.FnDecl) {
g.fn_decl = &it g.fn_decl = &it
@ -756,7 +803,7 @@ fn (mut g JsGen) gen_method_decl(it ast.FnDecl) {
} else if it.is_anon { } else if it.is_anon {
g.write('function (') g.write('function (')
} else { } else {
mut name := js_name(it.name.split('.').last()) mut name := g.js_name(it.name)
c := name[0] c := name[0]
if c in [`+`, `-`, `*`, `/`] { if c in [`+`, `-`, `*`, `/`] {
name = util.replace_op(name) name = util.replace_op(name)
@ -775,7 +822,7 @@ fn (mut g JsGen) gen_method_decl(it ast.FnDecl) {
} }
g.write('${name}(') g.write('${name}(')
if it.is_pub { if it.is_pub && !it.is_method {
g.push_pub_var(name) g.push_pub_var(name)
} }
} }
@ -894,7 +941,7 @@ fn (mut g JsGen) gen_for_stmt(it ast.ForStmt) {
fn (mut g JsGen) fn_args(args []table.Arg, is_variadic bool) { fn (mut g JsGen) fn_args(args []table.Arg, is_variadic bool) {
// no_names := args.len > 0 && args[0].name == 'arg_1' // no_names := args.len > 0 && args[0].name == 'arg_1'
for i, arg in args { for i, arg in args {
name := js_name(arg.name) name := g.js_name(arg.name)
is_varg := i == args.len - 1 && is_variadic is_varg := i == args.len - 1 && is_variadic
if is_varg { if is_varg {
g.write('...$name') g.write('...$name')
@ -931,7 +978,7 @@ fn (mut g JsGen) gen_go_stmt(node ast.GoStmt) {
g.dec_indent() g.dec_indent()
g.writeln('});') g.writeln('});')
} }
else { } else {}
} }
} }
@ -965,14 +1012,12 @@ fn (mut g JsGen) gen_map_init_expr(it ast.MapInit) {
fn (mut g JsGen) gen_return_stmt(it ast.Return) { fn (mut g JsGen) gen_return_stmt(it ast.Return) {
g.write('return ') g.write('return ')
if g.fn_decl.name == 'main' { if it.exprs.len == 0 {
// we can't return anything in main // Returns nothing
g.writeln('void;') } else if it.exprs.len == 1 {
return g.expr(it.exprs[0])
} } else {
// Multi return
// multiple returns
if it.exprs.len > 1 {
g.write('[') g.write('[')
for i, expr in it.exprs { for i, expr in it.exprs {
g.expr(expr) g.expr(expr)
@ -982,53 +1027,38 @@ fn (mut g JsGen) gen_return_stmt(it ast.Return) {
} }
g.write(']') g.write(']')
} }
else {
g.expr(it.exprs[0])
}
g.writeln(';') g.writeln(';')
} }
fn (mut g JsGen) enum_expr(node ast.Expr) { fn (mut g JsGen) enum_expr(node ast.Expr) {
match node { match node {
ast.EnumVal { ast.EnumVal { g.write(it.val) }
g.write(it.val) else { g.expr(node) }
}
else {
g.expr(node)
}
} }
} }
fn (mut g JsGen) gen_struct_decl(node ast.StructDecl) { fn (mut g JsGen) gen_struct_decl(node ast.StructDecl) {
g.writeln('class ${js_name(node.name)} {') g.writeln('class ${g.js_name(node.name)} {')
g.inc_indent() g.inc_indent()
g.writeln(g.doc.gen_ctor(node.fields)) g.writeln(g.doc.gen_ctor(node.fields))
g.writeln('constructor(values) {') g.writeln('constructor(values) {')
g.inc_indent() g.inc_indent()
for field in node.fields { for field in node.fields {
g.writeln('this.$field.name = values.$field.name') // TODO: Generate default struct init values
g.writeln('this.$field.name = values.$field.name')
} }
g.dec_indent() g.dec_indent()
g.writeln('}') g.writeln('}')
g.writeln('')
fns := g.method_fn_decls[node.name] fns := g.method_fn_decls[node.name]
for cfn in fns { for cfn in fns {
// TODO: Fix this hack for type conversion // TODO: Move cast to the entire array whenever it's possible
// Directly converting to FnDecl gives it := cfn as ast.FnDecl
// error: conversion to non-scalar type requested g.writeln('')
match cfn { g.gen_method_decl(it)
ast.FnDecl {
g.gen_method_decl(it)
}
else {}
}
} }
g.dec_indent() g.dec_indent()
g.writeln('}') g.writeln('}\n')
if node.is_pub { if node.is_pub {
g.push_pub_var(node.name) g.push_pub_var(node.name)
} }
@ -1036,7 +1066,8 @@ fn (mut g JsGen) gen_struct_decl(node ast.StructDecl) {
fn (mut g JsGen) gen_struct_init(it ast.StructInit) { fn (mut g JsGen) gen_struct_init(it ast.StructInit) {
type_sym := g.table.get_type_symbol(it.typ) type_sym := g.table.get_type_symbol(it.typ)
g.writeln('new ${type_sym.name}({') name := type_sym.name
g.writeln('new ${g.js_name(name)}({')
g.inc_indent() g.inc_indent()
for i, field in it.fields { for i, field in it.fields {
g.write('$field.name: ') g.write('$field.name: ')
@ -1052,10 +1083,11 @@ fn (mut g JsGen) gen_struct_init(it ast.StructInit) {
fn (mut g JsGen) gen_ident(node ast.Ident) { fn (mut g JsGen) gen_ident(node ast.Ident) {
if node.kind == .constant { if node.kind == .constant {
g.write('CONSTANTS.') // TODO: Handle const namespacing: only consts in the main module are handled rn
g.write('_CONSTS.')
} }
name := js_name(node.name) name := g.js_name(node.name)
// TODO `is` // TODO `is`
// TODO handle optionals // TODO handle optionals
g.write(name) g.write(name)
@ -1087,7 +1119,7 @@ fn (mut g JsGen) gen_if_expr(node ast.IfExpr) {
g.inside_ternary = false g.inside_ternary = false
g.write(')') g.write(')')
} else { } else {
//mut is_guard = false // mut is_guard = false
for i, branch in node.branches { for i, branch in node.branches {
if i == 0 { if i == 0 {
match branch.cond { match branch.cond {
@ -1109,7 +1141,7 @@ fn (mut g JsGen) gen_if_expr(node ast.IfExpr) {
//g.writeln('} if (!$guard_ok) { /* else */') //g.writeln('} if (!$guard_ok) { /* else */')
} else { */ } else { */
g.writeln('} else {') g.writeln('} else {')
//} // }
} }
g.stmts(branch.stmts) g.stmts(branch.stmts)
} }
@ -1128,11 +1160,7 @@ fn verror(s string) {
fn fn_has_go(it ast.FnDecl) bool { fn fn_has_go(it ast.FnDecl) bool {
mut has_go := false mut has_go := false
for stmt in it.stmts { for stmt in it.stmts {
match stmt { if stmt is ast.GoStmt { has_go = true }
ast.GoStmt {
has_go = true
} else {}
}
} }
return has_go return has_go
} }

View File

@ -25,11 +25,13 @@ fn (mut d JsDoc) gen_indent() {
} }
fn (mut d JsDoc) write(s string) { fn (mut d JsDoc) write(s string) {
if !d.gen.enable_doc { return }
d.gen_indent() d.gen_indent()
d.out.write(s) d.out.write(s)
} }
fn (mut d JsDoc) writeln(s string) { fn (mut d JsDoc) writeln(s string) {
if !d.gen.enable_doc { return }
d.gen_indent() d.gen_indent()
d.out.writeln(s) d.out.writeln(s)
d.empty_line = true d.empty_line = true
@ -45,7 +47,7 @@ fn (mut d JsDoc) gen_typ(typ, name string) string {
d.write('/**') d.write('/**')
d.write(' @type {$typ}') d.write(' @type {$typ}')
if name.len > 0 { if name.len > 0 {
d.write(' - ${js_name(name)}') d.write(' - ${d.gen.js_name(name)}')
} }
d.write(' */') d.write(' */')
return d.out.str() return d.out.str()
@ -54,15 +56,18 @@ fn (mut d JsDoc) gen_typ(typ, name string) string {
fn (mut d JsDoc) gen_ctor(fields []ast.StructField) string { fn (mut d JsDoc) gen_ctor(fields []ast.StructField) string {
d.reset() d.reset()
d.writeln('/**') d.writeln('/**')
d.write('* @param {{') d.write(' * @param {{')
for i, field in fields { for i, field in fields {
d.write('$field.name: ${d.gen.typ(field.typ)}') // Marked as optional: structs have default default values,
// so all struct members don't have to be initialized.
// TODO: Actually generate default struct init values :P
d.write('$field.name?: ${d.gen.typ(field.typ)}')
if i < fields.len - 1 { if i < fields.len - 1 {
d.write(', ') d.write(', ')
} }
} }
d.writeln('}} values - values for this class fields') d.writeln('}} values - values for this class fields')
d.writeln('* @constructor') d.writeln(' * @constructor')
d.write('*/') d.write('*/')
return d.out.str() return d.out.str()
} }
@ -71,20 +76,23 @@ fn (mut d JsDoc) gen_fn(it ast.FnDecl) string {
d.reset() d.reset()
type_name := d.gen.typ(it.return_type) type_name := d.gen.typ(it.return_type)
d.writeln('/**') d.writeln('/**')
if it.is_deprecated {
d.writeln(' * @deprecated')
}
for i, arg in it.args { for i, arg in it.args {
if it.is_method && i == 0 { if it.is_method && i == 0 {
continue continue
} }
arg_type_name := d.gen.typ(arg.typ) arg_type_name := d.gen.typ(arg.typ)
is_varg := i == it.args.len - 1 && it.is_variadic is_varg := i == it.args.len - 1 && it.is_variadic
name := js_name(arg.name) name := d.gen.js_name(arg.name)
if is_varg { if is_varg {
d.writeln('* @param {...$arg_type_name} $name') d.writeln(' * @param {...$arg_type_name} $name')
} else { } else {
d.writeln('* @param {$arg_type_name} $name') d.writeln(' * @param {$arg_type_name} $name')
} }
} }
d.writeln('* @return {$type_name}') d.writeln(' * @returns {$type_name}')
d.write('*/') d.write('*/')
return d.out.str() return d.out.str()
} }

View File

@ -0,0 +1,7 @@
module hello1
// Unused for now: nested modules do not work yet
pub fn nested() string {
return 'Nested'
}

View File

@ -1,9 +1,30 @@
module hello module hello
pub fn standard() string { // TODO: Fix const namespacing, uncomment once it works
return "Hello" /*
pub const (
hello = 'Hello'
)
*/
pub struct A {
pub mut:
foo string
}
pub fn (mut a A) update(s string) {
a.foo = s
}
struct B {}
pub enum C {}
pub fn debugger() string {
v := B{}
return 'Hello'
} }
pub fn excited() string { pub fn excited() string {
return standard() + "!" return debugger() + "!"
} }

View File

@ -1,46 +1,91 @@
// V_COMMIT_HASH d697b28 // V_COMMIT_HASH 74686d0
// V_CURRENT_COMMIT_HASH 11c06ec // V_CURRENT_COMMIT_HASH 577d252
// Generated by the V compiler // Generated by the V compiler
"use strict"; "use strict";
const CONSTANTS = Object.freeze({ const _CONSTS = Object.freeze({
/** @type {number} - i_am_a_const */ /** @type {number} - i_am_a_const */
i_am_a_const: 21214, i_am_a_const: 21214,
/** @type {string} - v_super */ /** @type {string} - v_super */
v_super: "amazing keyword" v_super: "amazing keyword"
}); });
/* namespace: hello */ /* namespace: hello */
const hello = (function () { /** const hello = (function () {
* @return {string} class A {
/**
* @param {{foo?: string}} values - values for this class fields
* @constructor
*/
constructor(values) {
this.foo = values.foo
}
/**
* @param {string} s
* @returns {void}
*/
update(s) {
const a = this;
a.foo = s;
}
}
class B {
/**
* @param {{}} values - values for this class fields
* @constructor
*/
constructor(values) {
}
}
const C = Object.freeze({
});
/**
* @returns {string}
*/ */
function standard() { function v_debugger() {
const v = new B({
});
return "Hello"; return "Hello";
} }
/** /**
* @return {string} * @returns {string}
*/ */
function excited() { function excited() {
return hello.standard() + "!"; return v_debugger() + "!";
} }
/* module exports */ /* module exports */
return { return {
standard, A,
C,
v_debugger,
excited, excited,
}; };
})(); })();
/* namespace: main */ /* namespace: main */
const main = (function (greeting) { const main = (function (hl) {
class Foo {
class Companies {
/** /**
* @param {{google: number, amazon: boolean, yahoo: string}} values - values for this class fields * @param {{a?: hello["A"]["prototype"]}} values - values for this class fields
* @constructor * @constructor
*/
constructor(values) {
this.a = values.a
}
}
class Companies {
/**
* @param {{google?: number, amazon?: boolean, yahoo?: string}} values - values for this class fields
* @constructor
*/ */
constructor(values) { constructor(values) {
this.google = values.google this.google = values.google
@ -49,7 +94,7 @@ class Companies {
} }
/** /**
* @return {number} * @returns {number}
*/ */
method() { method() {
const it = this; const it = this;
@ -68,15 +113,16 @@ class Companies {
return 0; return 0;
} }
} }
const POSITION = Object.freeze({ const POSITION = Object.freeze({
go_back: 0, go_back: 0,
dont_go_back: 1, dont_go_back: 1,
}); });
/** /**
* @param {string} v_extends * @param {string} v_extends
* @param {number} v_instanceof * @param {number} v_instanceof
* @return {void} * @returns {void}
*/ */
function v_class(v_extends, v_instanceof) { function v_class(v_extends, v_instanceof) {
/** @type {number} - v_delete */ /** @type {number} - v_delete */
@ -86,6 +132,21 @@ class Companies {
/* program entry point */ /* program entry point */
(async function() { (async function() {
console.log("Hello from V.js!"); console.log("Hello from V.js!");
/** @type {number} - a */
let a = 1;
a *= 2;
a += 3;
console.log(a, " == 5");
const b = new hl.A({
});
b.update("an update");
console.log(b);
const c = new Foo({
a: new hl.A({
})
});
c.a.update("another update");
console.log(c);
/** @type {string} - v */ /** @type {string} - v */
const v = "done"; const v = "done";
{ {
@ -96,14 +157,14 @@ class Companies {
/** @type {number} - pos */ /** @type {number} - pos */
const pos = POSITION.go_back; const pos = POSITION.go_back;
/** @type {string} - v_debugger */ /** @type {string} - v_debugger */
const v_debugger = "JS keyword"; const v_debugger = "JS keywords";
/** @type {string} - v_await */ /** @type {string} - v_await */
const v_await = CONSTANTS.v_super + v_debugger; const v_await = _CONSTS.v_super + ": " + v_debugger;
/** @type {string} - v_finally */ /** @type {string} - v_finally */
let v_finally = "implemented"; let v_finally = "implemented";
console.log(v_await, v_finally); console.log(v_await, v_finally);
/** @type {number} - dun */ /** @type {number} - dun */
const dun = CONSTANTS.i_am_a_const * 20; const dun = _CONSTS.i_am_a_const * 20;
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
} }
@ -117,7 +178,7 @@ class Companies {
/** @type {number[]} - arr */ /** @type {number[]} - arr */
const arr = [1, 2, 3, 4, 5]; const arr = [1, 2, 3, 4, 5];
for (let _tmp1 = 0; _tmp1 < arr.length; ++_tmp1) { for (let _tmp1 = 0; _tmp1 < arr.length; ++_tmp1) {
let a = arr[_tmp1]; let i = arr[_tmp1];
} }
/** @type {Map<string, string>} - ma */ /** @type {Map<string, string>} - ma */
@ -135,37 +196,40 @@ class Companies {
resolve(); resolve();
}); });
/** @type {anon_1011_7_1} - fn_in_var */ /** @type {(number: number) => void} - fn_in_var */
const fn_in_var = function (number) { const fn_in_var = function (number) {
console.log(tos3(`number: ${number}`)); console.log(tos3(`number: ${number}`));
}; };
anon_consumer(greeting.excited(), function (message) { hl.v_debugger();
anon_consumer(hl.excited(), function (message) {
console.log(message); console.log(message);
}); });
})(); })();
/** /**
* @param {string} greeting * @param {string} greeting
* @param {anon_fn_18_1} anon * @param {(message: string) => void} anon
* @return {void} * @returns {void}
*/ */
function anon_consumer(greeting, anon) { function anon_consumer(greeting, anon) {
anon(greeting); anon(greeting);
} }
/** /**
* @param {number} num * @param {number} num
* @param {string} def * @param {string} def
* @return {void} * @returns {void}
*/ */
function async(num, def) { function async(num, def) {
} }
/* [inline] */ /* [inline] */
/* [deprecated] */
/** /**
* @param {number} game_on * @deprecated
* @param {...string} dummy * @param {number} game_on
* @return {multi_return_int_int} * @param {...string} dummy
* @returns {[number, number]}
*/ */
function hello(game_on, ...dummy) { function hello(game_on, ...dummy) {
for (let _tmp2 = 0; _tmp2 < dummy.length; ++_tmp2) { for (let _tmp2 = 0; _tmp2 < dummy.length; ++_tmp2) {
@ -180,10 +244,10 @@ class Companies {
})(); })();
return [game_on + 2, 221]; return [game_on + 2, 221];
} }
/* module exports */ /* module exports */
return { return {
}; };
})(hello); })(hello);

View File

@ -1,4 +1,4 @@
import hello as greeting import hello as hl
fn JS.alert(arg string) fn JS.alert(arg string)
fn JS.console.log(arg string) fn JS.console.log(arg string)
@ -8,6 +8,10 @@ const (
super = 'amazing keyword' super = 'amazing keyword'
) )
struct Foo {
a hl.A
}
struct Companies { struct Companies {
google int google int
amazon bool amazon bool
@ -27,6 +31,19 @@ fn class(extends string, instanceof int) {
fn main() { fn main() {
JS.console.log('Hello from V.js!') JS.console.log('Hello from V.js!')
mut a := 1
a *= 2
a += 3
JS.console.log(a, ' == 5') // TODO: Handle string interpolation
b := hl.A{}
b.update('an update')
JS.console.log(b)
c := Foo{ hl.A{} }
c.a.update('another update')
JS.console.log(c)
v := "done" v := "done"
{ {
_ := "block" _ := "block"
@ -34,9 +51,9 @@ fn main() {
pos := POSITION.go_back pos := POSITION.go_back
debugger := 'JS keyword' debugger := 'JS keywords'
// TODO: Implement interpolation // TODO: Implement interpolation
await := super + debugger await := super + ': ' + debugger
mut finally := 'implemented' mut finally := 'implemented'
JS.console.log(await, finally) JS.console.log(await, finally)
@ -50,7 +67,7 @@ fn main() {
for x in 1..10 {} for x in 1..10 {}
arr := [1,2,3,4,5] arr := [1,2,3,4,5]
for a in arr {} for i in arr {}
ma := { ma := {
'str': "done" 'str': "done"
@ -67,7 +84,8 @@ fn main() {
JS.console.log("number: $number") JS.console.log("number: $number")
} }
anon_consumer(greeting.excited(), fn (message string) { hl.debugger()
anon_consumer(hl.excited(), fn (message string) {
JS.console.log(message) JS.console.log(message)
}) })
} }
@ -79,6 +97,7 @@ fn anon_consumer (greeting string, anon fn(message string)) {
fn async(num int, def string) {} fn async(num int, def string) {}
[inline] [inline]
[deprecated]
fn hello(game_on int, dummy ...string) (int, int) { fn hello(game_on int, dummy ...string) (int, int) {
defer { defer {
do := "not" do := "not"

View File

@ -0,0 +1,105 @@
// V_COMMIT_HASH 74686d0
// V_CURRENT_COMMIT_HASH 577d252
// Generated by the V compiler
"use strict";
/* namespace: hello */
const hello = (function () {
class A {
/**
* @param {{foo?: string}} values - values for this class fields
* @constructor
*/
constructor(values) {
this.foo = values.foo
}
/**
* @param {string} s
* @returns {void}
*/
update(s) {
const a = this;
a.foo = s;
}
}
class B {
/**
* @param {{}} values - values for this class fields
* @constructor
*/
constructor(values) {
}
}
const C = Object.freeze({
});
/**
* @returns {string}
*/
function v_debugger() {
const v = new B({
});
return "Hello";
}
/**
* @returns {string}
*/
function excited() {
return v_debugger() + "!";
}
/* module exports */
return {
A,
C,
v_debugger,
excited,
};
})();
/* namespace: main */
const main = (function (hello) {
class D {
/**
* @param {{a?: hello["A"]["prototype"]}} values - values for this class fields
* @constructor
*/
constructor(values) {
this.a = values.a
}
}
/**
* @param {hello["A"]["prototype"]} arg
* @returns {void}
*/
function struct_arg(arg) {
console.log(arg);
}
/* program entry point */
(function() {
struct_arg(new hello.A({
foo: "hello"
}));
/** @type {number} - a */
let a = 1;
a += 2;
console.log(a);
const b = new hello.A({
});
console.log(b);
})();
/* module exports */
return {
};
})(hello);

View File

@ -0,0 +1,25 @@
import hello
// import hello.hello1
// TODO: Uncomment once nested modules work
fn JS.console.log()
struct D {
a hello.A
}
fn struct_arg (arg hello.A) {
JS.console.log(arg)
}
fn main() {
struct_arg(hello.A{ 'hello' })
mut a := 1
a += 2
JS.console.log(a)
b := hello.A{}
JS.console.log(b)
// hello1.nested()
}