diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index 9bef52f249..2cd648f1f0 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -6,6 +6,7 @@ import v.table import v.pref import term import v.util +import v.depgraph const ( // https://ecma-international.org/ecma-262/#sec-reserved-words @@ -22,24 +23,25 @@ struct JsGen { definitions strings.Builder pref &pref.Preferences mut: - out strings.Builder - namespaces map[string]strings.Builder - namespaces_pub map[string][]string - namespace string - doc &JsDoc - constants strings.Builder // all global V constants - file ast.File - tmp_count int - inside_ternary bool - inside_loop bool - is_test bool - indents map[string]int // indentations mapped to namespaces - 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.Stmt - empty_line bool + out strings.Builder + namespaces map[string]strings.Builder + namespaces_pub map[string][]string + namespace_imports map[string]map[string]string + namespace string + doc &JsDoc + constants strings.Builder // all global V constants + file ast.File + tmp_count int + inside_ternary bool + inside_loop bool + is_test bool + indents map[string]int // indentations mapped to namespaces + 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.Stmt + empty_line bool } pub fn gen(files []ast.File, table &table.Table, pref &pref.Preferences) string { @@ -56,6 +58,8 @@ pub fn gen(files []ast.File, table &table.Table, pref &pref.Preferences) string g.doc = new_jsdoc(g) g.init() + mut graph := depgraph.new_dep_graph() + // Get class methods for file in files { g.file = file @@ -69,24 +73,48 @@ pub fn gen(files []ast.File, table &table.Table, pref &pref.Preferences) string g.file = file g.enter_namespace(g.file.mod.name) g.is_test = g.file.path.ends_with('_test.v') + + // store imports + mut imports := []string{} + for imp in g.file.imports { + imports << imp.mod + } + graph.add(g.file.mod.name, imports) + g.stmts(file.stmts) // store the current namespace g.escape_namespace() } + + // resolve imports + deps_resolved := graph.resolve() + g.finish() mut out := g.hashes() + g.definitions.str() + g.constants.str() - for key in g.namespaces.keys() { - out += '/* namespace: $key */\n' + for node in deps_resolved.nodes { + out += '\n/* namespace: $node.name */\n' + out += 'const $node.name = (function (' + imports := g.namespace_imports[node.name] + for i, key in imports.keys() { + if i > 0 { out += ', ' } + out += imports[key] + } + out += ') {' // private scope - out += g.namespaces[key].str() + out += g.namespaces[node.name].str() // public scope out += '\n\t/* module exports */' out += '\n\treturn {' - for pub_var in g.namespaces_pub[key] { + for pub_var in g.namespaces_pub[node.name] { out += '\n\t\t$pub_var,' } out += '\n\t};' - out += '\n})();' + out += '\n})(' + for i, key in imports.keys() { + if i > 0 { out += ', ' } + out += key + } + out += ');' } return out } @@ -97,7 +125,6 @@ pub fn (mut g JsGen) enter_namespace(n string) { // create a new namespace g.out = strings.new_builder(100) g.indents[g.namespace] = 0 - g.out.writeln('const $n = (function () {') } else { g.out = g.namespaces[g.namespace] @@ -110,7 +137,9 @@ pub fn (mut g JsGen) escape_namespace() { } pub fn (mut g JsGen) push_pub_var(s string) { - g.namespaces_pub[g.namespace] << s + mut arr := g.namespaces_pub[g.namespace] + arr << s + g.namespaces_pub[g.namespace] = arr } pub fn (mut g JsGen) find_class_methods(stmts []ast.Stmt) { @@ -316,7 +345,9 @@ fn (mut g JsGen) stmt(node ast.Stmt) { ast.HashStmt { // skip: nothing with # in JS } - ast.Import {} + ast.Import { + g.gen_import_stmt(it) + } ast.InterfaceDecl { // TODO skip: interfaces not implemented yet } @@ -358,7 +389,24 @@ fn (mut g JsGen) expr(node ast.Expr) { g.write("'$it.val'") } ast.CallExpr { - name := if it.name.starts_with('JS.') { it.name[3..] } else { it.name } + mut name := "" + if it.name.starts_with('JS.') { + name = it.name[3..] + } else { + 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) if it.is_method { // example: foo.bar.baz() @@ -430,6 +478,9 @@ fn (mut g JsGen) expr(node ast.Expr) { ast.SelectorExpr { g.gen_selector_expr(it) } + ast.AnonFn { + g.gen_anon_fn_decl(it) + } else { println(term.red('jsgen.expr(): bad node "${typeof(node)}"')) } @@ -483,6 +534,12 @@ fn (mut g JsGen) gen_string_inter_literal(it ast.StringInterLiteral) { g.write('`)') } +fn (mut g JsGen) gen_import_stmt(it ast.Import) { + mut imports := g.namespace_imports[g.namespace] + imports[it.mod] = it.alias + g.namespace_imports[g.namespace] = imports + } + fn (mut g JsGen) gen_array_init_expr(it ast.ArrayInit) { type_sym := g.table.get_type_symbol(it.typ) if type_sym.kind != .array_fixed { @@ -627,6 +684,7 @@ fn (mut g JsGen) gen_defer_stmts() { for defer_stmt in g.defer_stmts { g.stmts(defer_stmt.stmts) } + g.defer_stmts = [] g.writeln('})();') } @@ -679,6 +737,10 @@ fn (mut g JsGen) gen_fn_decl(it ast.FnDecl) { g.gen_method_decl(it) } +fn (mut g JsGen) gen_anon_fn_decl(it ast.AnonFn) { + g.gen_method_decl(it.decl) + } + fn (mut g JsGen) gen_method_decl(it ast.FnDecl) { g.fn_decl = &it has_go := fn_has_go(it) @@ -691,8 +753,10 @@ fn (mut g JsGen) gen_method_decl(it ast.FnDecl) { g.write('async ') } g.write('function(') + } else if it.is_anon { + g.write('function (') } else { - mut name := js_name(it.name) + mut name := js_name(it.name.split('.').last()) c := name[0] if c in [`+`, `-`, `*`, `/`] { name = util.replace_op(name) @@ -734,7 +798,9 @@ fn (mut g JsGen) gen_method_decl(it ast.FnDecl) { if is_main { g.write(')();') } - g.writeln('') + if !it.is_anon { + g.writeln('') + } g.fn_decl = 0 } @@ -989,7 +1055,6 @@ fn (mut g JsGen) gen_ident(node ast.Ident) { g.write('CONSTANTS.') } - // TODO js_name name := js_name(node.name) // TODO `is` // TODO handle optionals diff --git a/vlib/v/gen/js/tests/hello/hello.v b/vlib/v/gen/js/tests/hello/hello.v new file mode 100644 index 0000000000..2339441a4e --- /dev/null +++ b/vlib/v/gen/js/tests/hello/hello.v @@ -0,0 +1,9 @@ +module hello + +pub fn standard() string { + return "Hello" +} + +pub fn excited() string { + return standard() + "!" +} \ No newline at end of file diff --git a/vlib/v/gen/js/tests/js.js b/vlib/v/gen/js/tests/js.js index 1a858bf15f..935cdf389a 100644 --- a/vlib/v/gen/js/tests/js.js +++ b/vlib/v/gen/js/tests/js.js @@ -1,5 +1,5 @@ -// V_COMMIT_HASH 04744a5 -// V_CURRENT_COMMIT_HASH 04744a5 +// V_COMMIT_HASH d697b28 +// V_CURRENT_COMMIT_HASH 11c06ec // Generated by the V compiler "use strict"; @@ -11,9 +11,31 @@ const CONSTANTS = Object.freeze({ v_super: "amazing keyword" }); -/* namespace: main */ -const main = (function () { + +/* namespace: hello */ +const hello = (function () { /** + * @return {string} + */ + function standard() { + return "Hello"; + } + /** + * @return {string} + */ + function excited() { + return hello.standard() + "!"; + } + + + /* module exports */ + return { + standard, + excited, + }; +})(); +/* namespace: main */ +const main = (function (greeting) { class Companies { /** @@ -113,8 +135,24 @@ class Companies { resolve(); }); + /** @type {anon_1011_7_1} - fn_in_var */ + const fn_in_var = function (number) { + console.log(tos3(`number: ${number}`)); + }; + anon_consumer(greeting.excited(), function (message) { + console.log(message); + }); })(); + /** + * @param {string} greeting + * @param {anon_fn_18_1} anon + * @return {void} + */ + function anon_consumer(greeting, anon) { + anon(greeting); + } + /** * @param {number} num * @param {string} def @@ -148,4 +186,4 @@ class Companies { /* module exports */ return { }; -})(); +})(hello); diff --git a/vlib/v/gen/js/tests/js.v b/vlib/v/gen/js/tests/js.v index ff533d9096..b508dbf35e 100644 --- a/vlib/v/gen/js/tests/js.v +++ b/vlib/v/gen/js/tests/js.v @@ -1,3 +1,5 @@ +import hello as greeting + fn JS.alert(arg string) fn JS.console.log(arg string) @@ -60,6 +62,18 @@ fn main() { } go async(0, "hello") + + fn_in_var := fn (number int) { + JS.console.log("number: $number") + } + + anon_consumer(greeting.excited(), fn (message string) { + JS.console.log(message) + }) +} + +fn anon_consumer (greeting string, anon fn(message string)) { + anon(greeting) } fn async(num int, def string) {}