diff --git a/spec/compilers/component_with_provider b/spec/compilers/component_with_provider index 004a2ba0b..8832c899d 100644 --- a/spec/compilers/component_with_provider +++ b/spec/compilers/component_with_provider @@ -43,13 +43,13 @@ class C extends _C { componentDidUpdate() { if (false) { B._subscribe(this, new A({ - moves: (a) => { - return null - }, - ups: (b) => { - return null - } - })) + moves: (a) => { + return null + }, + ups: (b) => { + return null + } + })) } else { B._unsubscribe(this) }; @@ -58,13 +58,13 @@ class C extends _C { componentDidMount() { if (false) { B._subscribe(this, new A({ - moves: (a) => { - return null - }, - ups: (b) => { - return null - } - })) + moves: (a) => { + return null + }, + ups: (b) => { + return null + } + })) } else { B._unsubscribe(this) }; diff --git a/spec/compilers/component_with_provider_and_lifecycle_functions b/spec/compilers/component_with_provider_and_lifecycle_functions index f567bd1b2..28c15f678 100644 --- a/spec/compilers/component_with_provider_and_lifecycle_functions +++ b/spec/compilers/component_with_provider_and_lifecycle_functions @@ -56,13 +56,13 @@ class C extends _C { componentDidUpdate() { if (false) { B._subscribe(this, new A({ - moves: (a) => { - return null - }, - ups: (b) => { - return null - } - })) + moves: (a) => { + return null + }, + ups: (b) => { + return null + } + })) } else { B._unsubscribe(this) }; @@ -73,13 +73,13 @@ class C extends _C { componentDidMount() { if (false) { B._subscribe(this, new A({ - moves: (a) => { - return null - }, - ups: (b) => { - return null - } - })) + moves: (a) => { + return null + }, + ups: (b) => { + return null + } + })) } else { B._unsubscribe(this) }; diff --git a/spec/compilers/component_with_provider_and_store b/spec/compilers/component_with_provider_and_store index f6c853e9e..44bf0e95d 100644 --- a/spec/compilers/component_with_provider_and_store +++ b/spec/compilers/component_with_provider_and_store @@ -60,13 +60,13 @@ class C extends _C { componentDidUpdate() { if (false) { B._subscribe(this, new A({ - moves: (a) => { - return null - }, - ups: (b) => { - return null - } - })) + moves: (a) => { + return null + }, + ups: (b) => { + return null + } + })) } else { B._unsubscribe(this) }; @@ -77,13 +77,13 @@ class C extends _C { if (false) { B._subscribe(this, new A({ - moves: (a) => { - return null - }, - ups: (b) => { - return null - } - })) + moves: (a) => { + return null + }, + ups: (b) => { + return null + } + })) } else { B._unsubscribe(this) }; diff --git a/spec/compilers/directives/format b/spec/compilers/directives/format index 59c313d95..2651297ad 100644 --- a/spec/compilers/directives/format +++ b/spec/compilers/directives/format @@ -17,6 +17,7 @@ class A extends _C { return (() => { const [a,b] = [`HelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHBello`, `"HelloHelloHelloHelloHelloHelloHelloHelloHelloHelloHelloH" \\ "Bello"`]; + return a + b; })(); } diff --git a/spec/compilers/module_access_subscriptions b/spec/compilers/module_access_subscriptions index 2f34db631..92527ee6a 100644 --- a/spec/compilers/module_access_subscriptions +++ b/spec/compilers/module_access_subscriptions @@ -47,8 +47,8 @@ class C extends _C { componentDidUpdate() { if (true) { B._subscribe(this, new A({ - test: `` - })) + test: `` + })) } else { B._unsubscribe(this) }; @@ -57,8 +57,8 @@ class C extends _C { componentDidMount() { if (true) { B._subscribe(this, new A({ - test: `` - })) + test: `` + })) } else { B._unsubscribe(this) }; diff --git a/spec/indented_string_builder_spec.cr b/spec/indented_string_builder_spec.cr new file mode 100644 index 000000000..2b1aace8a --- /dev/null +++ b/spec/indented_string_builder_spec.cr @@ -0,0 +1,67 @@ +require "./spec_helper" + +INDENT = 2 + +describe Mint::IndentedStringBuilder do + it "indent class with constructor and display name" do + b = Mint::IndentedStringBuilder.new + + b << "class " << "A" << " extends " << "_C" << " " << "{\n" + b.indent_size += INDENT + b << "constructor" << "(" << "props" << ") " + b << "{\n" + b.indent_size += INDENT + b << "super" << "(" << "props" << ")" << ";" + # js.call + b << "\n\n" + b << "this._d" << "(" + b << "{\n" + b.indent_size += INDENT + b << "a" << ": " << "[\n" + b.indent_size += INDENT + b << "null" << "," << "\n" << "`Hello`" + b.indent_size -= INDENT + b << "\n]" + b.indent_size -= INDENT + b << "\n}" + b << ")" << ";" + b.indent_size -= INDENT + b << "\n}" + b.indent_size -= INDENT + b << "\n}" + b << ";" + b << "\n\n" + b << "A.displayName = \"Test\"" + b << ";" + + expected = + <<-STR + class A extends _C { + constructor(props) { + super(props); + + this._d({ + a: [ + null, + `Hello` + ] + }); + } + }; + + A.displayName = "Test"; + STR + + result = b.build + + pos = b.get_position_for_next_input + pos[:line].should eq(13) + pos[:column].should eq(23) + + begin + result.should eq(expected) + rescue error + fail diff(expected, result) + end + end +end diff --git a/spec/js_spec.cr b/spec/js_spec.cr index 82a50917a..076c76667 100644 --- a/spec/js_spec.cr +++ b/spec/js_spec.cr @@ -6,7 +6,13 @@ describe "JS" do context "object" do it "renders object Optimized" do - optimized.object({"a" => "b", "c" => "d"}).should eq("{a:b,c:d}") + subject = + optimized.object({"a" => "b", "c" => "d"}) + + result = + Mint::Codegen.build(subject)[:code] + + result.should eq("{a:b,c:d}") end end end diff --git a/src/builder.cr b/src/builder.cr index 6809d3e5b..cce34db17 100644 --- a/src/builder.cr +++ b/src/builder.cr @@ -1,6 +1,6 @@ module Mint class Builder - def initialize(relative, skip_service_worker, skip_icons, optimize) + def initialize(relative, skip_service_worker, skip_icons, optimize, source_map) json = MintJson.parse_current if !skip_icons && !Process.find_executable("convert") @@ -26,10 +26,17 @@ module Mint terminal.puts "#{COG} Compiling your application:" - index_js, artifacts = - index(json.application.css_prefix, relative, optimize) + build, artifacts = + index(json.application.css_prefix, relative, optimize, source_map) - File.write Path[DIST_DIR, "index.js"], index_js + File.write Path[DIST_DIR, "index.js"], build[:code] + + if source_map + terminal.puts "#{COG} Generating source map:" + build[:source_map].try do |map| + File.write Path[DIST_DIR, "index.js.map"], map.build_json + end + end if SourceFiles.external_javascripts? terminal.measure "#{COG} Writing external javascripts..." do @@ -128,7 +135,7 @@ module Mint end end - def index(css_prefix, relative, optimize) + def index(css_prefix, relative, optimize, source_map) runtime = Assets.read("runtime.js") @@ -163,7 +170,10 @@ module Mint } end - {runtime + compiled.to_s, type_checker.artifacts} + build_result = + Codegen.build(Codegen.join([runtime, compiled].compact), source_map) + + {build_result, type_checker.artifacts} end def terminal diff --git a/src/commands/build.cr b/src/commands/build.cr index cfb9e9a80..f16e88791 100644 --- a/src/commands/build.cr +++ b/src/commands/build.cr @@ -23,13 +23,18 @@ module Mint default: true, short: "m" + define_flag source_map : Bool, + description: "If specified generate source mappings for debugging", + default: false + def run execute "Building for production" do Builder.new( flags.relative, flags.skip_service_worker, flags.skip_icons, - flags.minify + flags.minify, + flags.source_map ) end end diff --git a/src/commands/compile.cr b/src/commands/compile.cr index d642376fa..790b4e061 100644 --- a/src/commands/compile.cr +++ b/src/commands/compile.cr @@ -8,7 +8,6 @@ module Mint define_flag output : String, description: "The output file", default: "program.js", - required: false, short: "o" define_flag minify : Bool, @@ -16,18 +15,29 @@ module Mint default: true, short: "m" + define_flag source_map : Bool, + description: "If specified generate source mappings for debugging", + default: false + def run execute "Compiling" do - File.write(flags.output, compile(flags.minify)) + result = + compile(flags.minify, flags.source_map) + + File.write(flags.output, result[:code]) + + result[:source_map].try do |map| + File.write("#{flags.output}.map", map) + end end end - def compile(optimize) + def compile(optimize, generate_source_map : Bool) json = MintJson.parse_current runtime = - Assets.read("runtime.js") + Assets.read("runtime.js").as(Codegen::Node) sources = Dir.glob(SourceFiles.all) @@ -60,7 +70,18 @@ module Mint } end - runtime + compiled.to_s + result = + Codegen.build(Codegen.join([runtime, compiled].compact), generate_source_map) + + source_map : String? = nil + + result[:source_map].try do |map| + terminal.measure " #{ARROW} Generating source map: " do + source_map = map.build_json + end + end + + {code: result[:code], source_map: source_map} end end end diff --git a/src/commands/start.cr b/src/commands/start.cr index 7ede9d61a..a8c696f56 100644 --- a/src/commands/start.cr +++ b/src/commands/start.cr @@ -21,16 +21,21 @@ module Mint default: (ENV["PORT"]? || "3000").to_i, required: false, short: "p" - define_flag live_reload : Bool, description: "Whether or not to reload the browser when something changes. (Default true)", required: false, default: true, short: "r" + define_flag source_map : Bool, + description: "If specified generate source mappings for debugging", + default: true, + required: false, + short: "m" + def run execute "Running the development server" do - Reactor.start flags.host, flags.port, flags.auto_format, flags.live_reload + Reactor.start flags.host, flags.port, flags.auto_format, flags.live_reload, flags.source_map end end end diff --git a/src/compiler.cr b/src/compiler.cr index 66b19e2b6..090a610bf 100644 --- a/src/compiler.cr +++ b/src/compiler.cr @@ -1,7 +1,5 @@ module Mint class Compiler - include Skippable - delegate lookups, checked, cache, component_records, to: @artifacts delegate ast, types, variables, resolve_order, to: @artifacts delegate record_field_lookup, to: @artifacts @@ -9,7 +7,7 @@ module Mint getter js, style_builder, static_components, static_components_pool getter build, relative - @static_components = {} of String => String + @static_components = {} of String => Codegen::Node @static_components_pool = NamePool(String, Nil).new def initialize(@artifacts : TypeChecker::Artifacts, @optimize = false, css_prefix = nil, @relative = false, @build = false) @@ -26,23 +24,23 @@ module Mint # Helpers for compiling things # ---------------------------------------------------------------------------- - def compile(nodes : Array(Ast::Node), separator : String) - compile(nodes).join(separator) + def compile(nodes : Array(Ast::Node), separator : String) : Codegen::Node + Codegen.join(nodes.map { |node| compile(node).as(Codegen::Node) }, separator) end - def compile(nodes : Array(Ast::Node)) - nodes.compact_map { |node| compile(node).as(String).presence } + def compile(nodes : Array(Ast::Node)) : Array(Codegen::Node) + nodes.compact_map { |node| compile(node).as(Codegen::Node) } end - def compile(node : Ast::Node) : String + def compile(node : Ast::Node) : Codegen::Node if checked.includes?(node) - _compile(node) + _compile(node).as(Codegen::Node) else "" end end - def _compile(node : Ast::Node) : String + def _compile(node : Ast::Node) : Codegen::Node raise "Compiler not implemented for node #{node}!" end end diff --git a/src/compilers/access.cr b/src/compilers/access.cr index f7a5ccdbf..42df95601 100644 --- a/src/compilers/access.cr +++ b/src/compilers/access.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def _compile(node : Ast::Access) : String + def _compile(node : Ast::Access) : Codegen::Node first = compile node.lhs @@ -13,13 +13,16 @@ module Mint if node.safe? js.iif do + access = + Codegen.symbol_mapped(node.lhs, node, Codegen.join ["_.", field]) + js.statements([ js.const("_", first), - js.return(js.call("_s", ["_", "(_) => _.#{field}"])), + js.return(js.call("_s", ["_", Codegen.join ["(_) => ", access]])), ]) end else - "#{first}.#{field}" + Codegen.symbol_mapped(node.lhs, node, Codegen.join [first, ".", field]) end end end diff --git a/src/compilers/argument.cr b/src/compilers/argument.cr index 021a1aa27..5d08f5be8 100644 --- a/src/compilers/argument.cr +++ b/src/compilers/argument.cr @@ -1,7 +1,7 @@ module Mint class Compiler - def _compile(node : Ast::Argument) : String - js.variable_of(node) + def _compile(node : Ast::Argument) : Codegen::Node + Codegen.symbol_mapped(node, js.variable_of(node)) end end end diff --git a/src/compilers/array_access.cr b/src/compilers/array_access.cr index 4d33a2e48..d3b49e85e 100644 --- a/src/compilers/array_access.cr +++ b/src/compilers/array_access.cr @@ -1,24 +1,25 @@ module Mint class Compiler - def _compile(node : Ast::ArrayAccess) : String + def _compile(node : Ast::ArrayAccess) : Codegen::Node type = cache[node.lhs] lhs = - compile node.lhs + Codegen.source_mapped(node.lhs, compile node.lhs) index = case node.index - when Int64 - node.index - when Ast::Node - compile node.index.as(Ast::Node) + in Int64 + node.index.to_s + in Ast::Node + node_index = node.index.as(Ast::Node) + Codegen.source_mapped(node_index, compile node_index) end if type.name == "Tuple" && node.index.is_a?(Int64) - "#{lhs}[#{index}]" + Codegen.join [lhs, "[", index, "]"] else - "_at(#{lhs}, #{index})" + Codegen.join ["_at(", lhs, ", ", index, ")"] end end end diff --git a/src/compilers/array_destructuring.cr b/src/compilers/array_destructuring.cr index 9efea2688..f51a0b331 100644 --- a/src/compilers/array_destructuring.cr +++ b/src/compilers/array_destructuring.cr @@ -1,7 +1,7 @@ module Mint class Compiler - def _compile(node : Ast::ArrayDestructuring, variable : String) : Tuple(String, Array(String)) - statements = %w[] + def _compile(node : Ast::ArrayDestructuring, variable : String) : Tuple(String, Array(Codegen::Node)) + statements = [] of Codegen::Node if node.spread? statements << "const __ = Array.from(#{variable})" @@ -24,14 +24,13 @@ module Mint statements << "const #{js.variable_of(node.items.select(Ast::Spread).first.variable)} = __" else variables = - node - .items - .join(',') { |param| js.variable_of(param) } + node.items.map { |param| js.variable_of(param) }.join(",") statements << "const [#{variables}] = #{variable}" end condition = "Array.isArray(#{variable})" + if node.spread? condition += " && #{variable}.length >= #{node.items.size - 1}" else diff --git a/src/compilers/array_literal.cr b/src/compilers/array_literal.cr index d4ebd59f9..8b922beb1 100644 --- a/src/compilers/array_literal.cr +++ b/src/compilers/array_literal.cr @@ -1,10 +1,10 @@ module Mint class Compiler - def _compile(node : Ast::ArrayLiteral) : String + def _compile(node : Ast::ArrayLiteral) : Codegen::Node items = compile node.items, ", " - "[#{items}]" + Codegen.join ["[", items, "]"] end end end diff --git a/src/compilers/bool_literal.cr b/src/compilers/bool_literal.cr index 4c6227352..0b0da72aa 100644 --- a/src/compilers/bool_literal.cr +++ b/src/compilers/bool_literal.cr @@ -1,7 +1,7 @@ module Mint class Compiler - def _compile(node : Ast::BoolLiteral) : String - node.value.to_s + def _compile(node : Ast::BoolLiteral) : Codegen::Node + Codegen.symbol_mapped(node, node.value.to_s) end end end diff --git a/src/compilers/call.cr b/src/compilers/call.cr index 49fe2525f..659e66d60 100644 --- a/src/compilers/call.cr +++ b/src/compilers/call.cr @@ -1,11 +1,11 @@ module Mint class Compiler - def _compile(node : Ast::Call) : String + def _compile(node : Ast::Call) : Codegen::Node expression = compile node.expression arguments = - compile node.arguments, ", " + Codegen.join(node.arguments, ", ") { |arg| Codegen.source_mapped(arg, compile arg) } if node.safe? js.iif do @@ -14,10 +14,10 @@ module Mint if node.arguments.empty? "(_) => _" else - "((..._) => _(#{arguments}, ..._))" + Codegen.join ["((..._) => _(", arguments, ", ..._))"] end else - "(_) => _(#{arguments})" + Codegen.join ["(_) => _(", arguments, ")"] end js.statements([ @@ -31,12 +31,12 @@ module Mint if node.arguments.empty? expression else - "((..._) => #{expression}(#{arguments}, ..._))" + Codegen.join ["((..._) => ", expression, "(", arguments, ", ..._))"] end when node.expression.is_a?(Ast::InlineFunction) - "(#{expression})(#{arguments})" + Codegen.join ["(", expression, ")(", arguments, ")"] else - "#{expression}(#{arguments})" + Codegen.join [expression, "(", arguments, ")"] end end end diff --git a/src/compilers/case.cr b/src/compilers/case.cr index d3ef2196c..96e007ee2 100644 --- a/src/compilers/case.cr +++ b/src/compilers/case.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def compile(node : Ast::Case, block : Proc(String, String)? = nil) : String + def compile(node : Ast::Case, block : Proc(Codegen::Node, Codegen::Node)? = nil) : Codegen::Node if checked.includes?(node) _compile node, block else @@ -8,7 +8,7 @@ module Mint end end - def _compile(node : Ast::Case, block : Proc(String, String)? = nil) : String + def _compile(node : Ast::Case, block : Proc(Codegen::Node, Codegen::Node)? = nil) : Codegen::Node condition = compile node.condition diff --git a/src/compilers/case_branch.cr b/src/compilers/case_branch.cr index 9eb7374ee..dc5370048 100644 --- a/src/compilers/case_branch.cr +++ b/src/compilers/case_branch.cr @@ -2,8 +2,8 @@ module Mint class Compiler def _compile(node : Ast::CaseBranch, index : Int32, - variable : String, - block : Proc(String, String)? = nil) : Tuple(String?, String) + variable : Codegen::Node, + block : Proc(Codegen::Node, Codegen::Node)? = nil) : Tuple(Codegen::Node?, Codegen::Node) expression = case item = node.expression when Array(Ast::CssDefinition) @@ -14,7 +14,7 @@ module Mint "{}" end when Ast::Node - js.return(compile(item)) + Codegen.source_mapped(item, js.return(compile(item))) else "" end @@ -26,6 +26,7 @@ module Mint _compile(match, variable) compiled[1] << expression + { compiled[0], js.statements(compiled[1]), @@ -35,7 +36,7 @@ module Mint compile match { - "_compare(#{variable}, #{compiled})", + Codegen.join(["_compare(", variable, ", ", compiled, ")"]), expression, } end diff --git a/src/compilers/catch.cr b/src/compilers/catch.cr index d8edd17fe..4f94c8c25 100644 --- a/src/compilers/catch.cr +++ b/src/compilers/catch.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def _compile(node : Ast::Catch) : String + def _compile(node : Ast::Catch) : Codegen::Node body = compile node.expression diff --git a/src/compilers/component.cr b/src/compilers/component.cr index cd5b86741..c60f3d24e 100644 --- a/src/compilers/component.cr +++ b/src/compilers/component.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def _compile(node : Ast::Component) : String + def _compile(node : Ast::Component) : Codegen::Node name = js.class_of(node) @@ -12,13 +12,13 @@ module Mint end global_let = - "let #{name}" if node.global? + Codegen.join ["let ", name] if node.global? compile node.styles, node styles = node.styles.compact_map do |style_node| - style_builder.compile_style(style_node, self).presence + style_builder.compile_style(style_node, self) end functions = @@ -35,7 +35,9 @@ module Mint refs = node.refs.map do |(ref, _)| - js.get(js.variable_of(ref), "return (this._#{ref.value} ? new #{just}(this._#{ref.value}) : new #{nothing});") + js.get(js.variable_of(ref), + Codegen.join( + ["return (this._", ref.value, " ? new ", just, "(this._", ref.value, ") : new ", nothing, ");"])) end display_name = @@ -44,10 +46,10 @@ module Mint store_stuff = compile_component_store_data node - constructor_body = %w[] + constructor_body = [] of Codegen::Node default_props = - node.properties.each_with_object({} of String => String) do |prop, memo| + node.properties.each_with_object({} of Codegen::Node => Codegen::Node) do |prop, memo| prop_name = if prop.name.value == "children" %("children") @@ -79,27 +81,29 @@ module Mint values = node .states - .each_with_object({} of String => String) do |item, memo| + .each_with_object({} of Codegen::Node => Codegen::Node) do |item, memo| memo[js.variable_of(item)] = compile item.default end - constructor_body << "this.state = new Record(#{js.object(values)})" + constructor_body << Codegen.join ["this.state = new Record(", js.object(values), ")"] end constructor = - unless constructor_body.empty? - js.function("constructor", %w[props]) do + if constructor_body.empty? + [] of Codegen::Node + else + [js.function("constructor", %w[props]) do constructor_body.unshift js.call("super", %w[props]) js.statements(constructor_body) - end + end] end - functions << js.function("_persist", %w[], js.assign(name, "this")) if node.global? + functions << js.function("_persist", [] of Codegen::Node, js.assign(name, "this")) if node.global? body = - ([constructor] &+ styles + gets &+ refs &+ states &+ store_stuff &+ functions) - .compact + (constructor + styles + gets + refs + states + store_stuff + functions) + .reject! { |item| Codegen.empty?(item) } js.statements([ js.class(prefixed_name, extends: "_C", body: body), @@ -108,8 +112,8 @@ module Mint ].compact) end - def compile_component_store_data(node : Ast::Component) : Array(String) - node.connects.reduce(%w[]) do |memo, item| + def compile_component_store_data(node : Ast::Component) : Array(Codegen::Node) + node.connects.reduce([] of Codegen::Node) do |memo, item| store = ast.stores.find(&.name.==(item.store)) if store @@ -125,9 +129,9 @@ module Mint when store.constants.any?(&.name.==(original)), store.gets.any?(&.name.value.==(original)), store.states.find(&.name.value.==(original)) - memo << js.get(name, "return #{store_name}.#{id};") + memo << js.get(name, Codegen.join ["return ", store_name, ".", id, ";"]) when store.functions.any?(&.name.value.==(original)) - memo << "#{name} (...params) { return #{store_name}.#{id}(...params); }" + memo << Codegen.join [name, " (...params) { return ", store_name, ".", id, "(...params); }"] end end end @@ -136,11 +140,11 @@ module Mint end end - def compile_component_functions(node : Ast::Component) : Array(String) + def compile_component_functions(node : Ast::Component) : Array(Codegen::Node) heads = { - "componentWillUnmount" => %w[], - "componentDidUpdate" => %w[], - "componentDidMount" => %w[], + "componentWillUnmount" => [] of Codegen::Node, + "componentDidUpdate" => [] of Codegen::Node, + "componentDidMount" => [] of Codegen::Node, } node.connects.each do |item| @@ -151,8 +155,8 @@ module Mint name = js.class_of(store) - heads["componentWillUnmount"] << "#{name}._unsubscribe(this)" - heads["componentDidMount"] << "#{name}._subscribe(this)" + heads["componentWillUnmount"] << Codegen.join [name, "._unsubscribe(this)"] + heads["componentDidMount"] << Codegen.join [name, "._subscribe(this)"] end end @@ -168,16 +172,15 @@ module Mint data = compile use.data - body = - <<-JS - if (#{condition}) { - #{name}._subscribe(this, #{data}) - } else { - #{name}._unsubscribe(this) - } - JS + body = Codegen.join [ + "if (", condition, ") {\n", + Codegen.indent([name, "._subscribe(this, ", data, ")\n"]), + "} else {\n", + Codegen.indent([name, "._unsubscribe(this)\n"]), + "}", + ] - heads["componentWillUnmount"] << "#{name}._unsubscribe(this)" + heads["componentWillUnmount"] << Codegen.join([name, "._unsubscribe(this)"]) heads["componentDidUpdate"] << body heads["componentDidMount"] << body end @@ -186,7 +189,7 @@ module Mint node .functions .reject { |function| heads[function.name.value]? } - .map { |function| compile(function, "").as(String) } + .map { |function| compile(function, "") } specials = heads.map do |key, value| @@ -205,7 +208,7 @@ module Mint end end - (specials + others).compact_map(&.presence) + (specials + others).compact end end end diff --git a/src/compilers/constant.cr b/src/compilers/constant.cr index 857ea0247..e51e70373 100644 --- a/src/compilers/constant.cr +++ b/src/compilers/constant.cr @@ -1,12 +1,12 @@ module Mint class Compiler - def compile_constants(nodes : Array(Ast::Constant)) : Hash(String, String) + def compile_constants(nodes : Array(Ast::Constant)) : Hash(Codegen::Node, Codegen::Node) nodes .select(&.in?(checked)) .sort_by! { |item| resolve_order.index(item) || -1 } - .each_with_object({} of String => String) do |node, memo| + .each_with_object({} of Codegen::Node => Codegen::Node) do |node, memo| memo[js.variable_of(node)] = - js.arrow_function(%w[], js.return(compile(node.value))) + js.arrow_function([] of Codegen::Node, js.return(compile(node.value))) end end end diff --git a/src/compilers/css_definition.cr b/src/compilers/css_definition.cr new file mode 100644 index 000000000..d7ae4b7c4 --- /dev/null +++ b/src/compilers/css_definition.cr @@ -0,0 +1,22 @@ +module Mint + class Compiler + def _compile(items : Array(Ast::CssDefinition), block : Proc(Codegen::Node, Codegen::Node)?) : Codegen::Node + compiled = + items.each_with_object({} of Codegen::Node => Codegen::Node) do |definition, memo| + variable = + if block + block.call(definition.name) + else + "" + end + + value = + compile definition.value + + memo["[`#{variable}`]"] = value + end + + Codegen.join ["Object.assign(_, ", js.object(compiled), ")"] + end + end +end diff --git a/src/compilers/decode.cr b/src/compilers/decode.cr index 481b6abc4..0d068cf21 100644 --- a/src/compilers/decode.cr +++ b/src/compilers/decode.cr @@ -1,13 +1,13 @@ module Mint class Compiler - def _compile(node : Ast::Decode) : String + def _compile(node : Ast::Decode) : Codegen::Node expression = compile node.expression code = @serializer.decoder types[node] - "#{code}(#{expression})" + Codegen.join [code, "(", expression, ")"] end end end diff --git a/src/compilers/destructuring.cr b/src/compilers/destructuring.cr index 4eca58e67..a383376c8 100644 --- a/src/compilers/destructuring.cr +++ b/src/compilers/destructuring.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def _compile_destructuring(node : Ast::Node, variable : String) : Tuple(String, Array(String))? + def _compile_destructuring(node : Ast::Node, variable : String) : Tuple(String, Array(Codegen::Node))? case node when Ast::TupleDestructuring _compile(node, variable) diff --git a/src/compilers/directives/asset.cr b/src/compilers/directives/asset.cr index 43237fdc9..6b15a3d16 100644 --- a/src/compilers/directives/asset.cr +++ b/src/compilers/directives/asset.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def _compile(node : Ast::Directives::Asset) : String + def _compile(node : Ast::Directives::Asset) : Codegen::Node prefix = relative ? "" : "/" diff --git a/src/compilers/directives/documentation.cr b/src/compilers/directives/documentation.cr index 945e4e358..6c8fa1092 100644 --- a/src/compilers/directives/documentation.cr +++ b/src/compilers/directives/documentation.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def _compile(node : Ast::Directives::Documentation) : String + def _compile(node : Ast::Directives::Documentation) : Codegen::Node entity = lookups[node] diff --git a/src/compilers/directives/format.cr b/src/compilers/directives/format.cr index 7ecccaea1..d73bb3b21 100644 --- a/src/compilers/directives/format.cr +++ b/src/compilers/directives/format.cr @@ -1,19 +1,18 @@ module Mint class Compiler - def _compile(node : Ast::Directives::Format) : String + def _compile(node : Ast::Directives::Format) : Codegen::Node content = compile node.content formatted = - skip do + Codegen.no_indent( Formatter.new .format(node.content) .gsub('\\', "\\\\") .gsub('`', "\\`") - .gsub("${", "\\${") - end + .gsub("${", "\\${")) - "[#{content}, `#{formatted}`]" + Codegen.join ["[", content, ", `", formatted, "`]"] end end end diff --git a/src/compilers/directives/inline.cr b/src/compilers/directives/inline.cr index b3d8d904a..515449431 100644 --- a/src/compilers/directives/inline.cr +++ b/src/compilers/directives/inline.cr @@ -1,7 +1,8 @@ module Mint class Compiler - def _compile(node : Ast::Directives::Inline) : String - skip { "`#{node.file_contents}`" } + def _compile(node : Ast::Directives::Inline) : Codegen::Node + # skip { "`#{node.file_contents}`" } + "" end end end diff --git a/src/compilers/directives/svg.cr b/src/compilers/directives/svg.cr index 2ddb19f97..89f6e1cab 100644 --- a/src/compilers/directives/svg.cr +++ b/src/compilers/directives/svg.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def _compile(node : Ast::Directives::Svg) : String + def _compile(node : Ast::Directives::Svg) : Codegen::Node name = static_components_pool.of(node.path, nil) diff --git a/src/compilers/encode.cr b/src/compilers/encode.cr index 0a5ea50de..9e8d36a5a 100644 --- a/src/compilers/encode.cr +++ b/src/compilers/encode.cr @@ -1,13 +1,13 @@ module Mint class Compiler - def _compile(node : Ast::Encode) : String + def _compile(node : Ast::Encode) : Codegen::Node expression = compile node.expression code = @serializer.encoder cache[node.expression] - "#{code}(#{expression})" + Codegen.join [code || "", "(", expression, ")"] end end end diff --git a/src/compilers/enum.cr b/src/compilers/enum.cr index a7c647524..fa1871be0 100644 --- a/src/compilers/enum.cr +++ b/src/compilers/enum.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def _compile(node : Ast::Enum) : String + def _compile(node : Ast::Enum) : Codegen::Node enum_ids = node.options.map do |option| name = @@ -11,14 +11,14 @@ module Mint .map { |index| "_#{index - 1}" } assignments = - ids.map { |item| "this.#{item} = #{item}" } + ids.map { |item| Codegen.join ["this.", item, " = ", item] } js.class( name, extends: "_E", body: [js.function("constructor", ids) do js.statements([ - js.call("super", %w[]), + js.call("super", [] of Codegen::Node), assignments, "this.length = #{option.parameters.size}", ].flatten) diff --git a/src/compilers/enum_destructuring.cr b/src/compilers/enum_destructuring.cr index b012efa94..b6aeeed45 100644 --- a/src/compilers/enum_destructuring.cr +++ b/src/compilers/enum_destructuring.cr @@ -1,20 +1,20 @@ module Mint class Compiler - def _compile(node : Ast::EnumDestructuring, variable : String) : Tuple(String, Array(String)) + def _compile(node : Ast::EnumDestructuring, variable : String) : Tuple(String, Array(Codegen::Node)) variables = case lookups[node].as(Ast::EnumOption).parameters[0]? when Ast::EnumRecordDefinition node.parameters.compact_map do |param| case param when Ast::TypeVariable - "const #{js.variable_of(param)} = #{variable}._0.#{param.value}" + "const #{js.variable_of(param)} = #{variable}._0.#{param.value}".as(Codegen::Node) end end else node.parameters.map_with_index do |param, index1| - "const #{js.variable_of(param)} = #{variable}._#{index1}" + "const #{js.variable_of(param)} = #{variable}._#{index1}".as(Codegen::Node) end - end + end.compact name = js.class_of(lookups[node]) diff --git a/src/compilers/enum_id.cr b/src/compilers/enum_id.cr index 051cd6a18..51f7fd693 100644 --- a/src/compilers/enum_id.cr +++ b/src/compilers/enum_id.cr @@ -1,13 +1,13 @@ module Mint class Compiler - def _compile(node : Ast::EnumId) : String + def _compile(node : Ast::EnumId) : Codegen::Node name = js.class_of(lookups[node]) expressions = compile node.expressions, "," - "new #{name}(#{expressions})" + Codegen.join ["new ", name, "(", expressions, ")"] end end end diff --git a/src/compilers/env.cr b/src/compilers/env.cr index 8276b1064..cb405ddb3 100644 --- a/src/compilers/env.cr +++ b/src/compilers/env.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def compile(node : Ast::Env) : String + def compile(node : Ast::Env) : Codegen::Node value = MINT_ENV[node.name].to_s.gsub('`', "\\`") diff --git a/src/compilers/finally.cr b/src/compilers/finally.cr index ba82c684d..d9c35e685 100644 --- a/src/compilers/finally.cr +++ b/src/compilers/finally.cr @@ -1,10 +1,10 @@ module Mint class Compiler - def _compile(node : Ast::Finally) : String + def _compile(node : Ast::Finally) : Codegen::Node body = compile node.expression - "finally {\n#{body.indent}\n}" + Codegen.join ["finally {\n", Codegen.indent(body), "\n}"] end end end diff --git a/src/compilers/for_expression.cr b/src/compilers/for_expression.cr index a516c3168..9eea7b39a 100644 --- a/src/compilers/for_expression.cr +++ b/src/compilers/for_expression.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def _compile(node : Ast::For) : String + def _compile(node : Ast::For) : Codegen::Node subject_type = cache[node.subject] @@ -32,30 +32,30 @@ module Mint condition = node.condition.try do |item| - <<-JS - const _2 = #{compile(item.condition)} - if (!_2) { continue } - JS + Codegen.join [ + "const _2 = ", compile(item.condition), + "\nif (!_2) { continue }", + ] end index = if index_arg - "const #{js.variable_of(index_arg)} = _i" + Codegen.join(["const ", js.variable_of(index_arg), " = _i"]) end contents = if condition - [condition, index, "_0.push(#{body})", "_i++"] + [condition, index, Codegen.join(["_0.push(", body, ")"]), "_i++"].compact else - [index, "_0.push(#{body})", "_i++"] + [index, Codegen.join(["_0.push(", body, ")"]), "_i++"].compact end js.iif do js.statements([ "const _0 = []", - "const _1 = #{subject}", + Codegen.join(["const _1 = ", subject]), "let _i = 0", - js.for("let #{arguments} of _1", js.statements(contents.compact)), + js.for(Codegen.join(["let ", arguments, " of _1"]), js.statements(contents)), js.return("_0"), ]) end diff --git a/src/compilers/function.cr b/src/compilers/function.cr index 0dda72767..5cb49126e 100644 --- a/src/compilers/function.cr +++ b/src/compilers/function.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def compile(node : Ast::Function, contents = "") : String + def compile(node : Ast::Function, contents : Codegen::Node = "") : Codegen::Node if checked.includes?(node) _compile node, contents else @@ -8,29 +8,30 @@ module Mint end end - def _compile(node : Ast::Function, contents = "") : String + def _compile(node : Ast::Function, contents : Codegen::Node = "") : Codegen::Node name = - js.variable_of(node) + Codegen.source_mapped(node.name, js.variable_of(node)) expression = compile node.body wheres = - node.where + (node.where .try(&.statements) .try(&.sort_by { |item| resolve_order.index(item) || -1 }) - .try { |statements| compile statements } + .try { |statements| compile statements } || [] of Codegen::Node) + .reject! { |item| Codegen.empty?(item) } arguments = - compile node.arguments + node.arguments.map { |arg| Codegen.source_mapped(arg, compile arg) } last = - [js.return(expression)] + [Codegen.source_mapped(node.body, js.return(expression)).as(Codegen::Node)] - last.unshift(contents) unless contents.empty? + last.unshift(contents) unless Codegen.empty? contents body = - js.statements(%w[] &+ wheres &+ last) + Codegen.source_mapped(node.where || node.body, js.statements(wheres + last)) js.function(name, arguments, body) end diff --git a/src/compilers/get.cr b/src/compilers/get.cr index 2a5bc5227..c2fc825d3 100644 --- a/src/compilers/get.cr +++ b/src/compilers/get.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def _compile(node : Ast::Get) : String + def _compile(node : Ast::Get) : Codegen::Node body = compile node.body @@ -17,7 +17,9 @@ module Mint [js.return(body)] body = - js.statements(%w[] &+ wheres &+ last) + js.statements( + ([] of Codegen::Node &+ wheres &+ last) + .reject! { |item| Codegen.empty?(item) }) js.get(name, body) end diff --git a/src/compilers/html_attribute.cr b/src/compilers/html_attribute.cr index 734d44f61..1792ae938 100644 --- a/src/compilers/html_attribute.cr +++ b/src/compilers/html_attribute.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def resolve(node : Ast::HtmlAttribute, is_element = true) : Hash(String, String) + def resolve(node : Ast::HtmlAttribute, is_element = true) : Hash(Codegen::Node, Codegen::Node) value = compile node.value @@ -10,18 +10,18 @@ module Mint case downcase_name when .starts_with?("on") if is_element - value = "(event => (#{value})(_normalizeEvent(event)))" + value = Codegen.join ["(event => (", value, ")(_normalizeEvent(event)))"] end when "ref" - value = "(ref => { ref ? #{value}.call(this, ref) : null })" + value = Codegen.join ["(ref => { ref ? ", value, ".call(this, ref) : null })"] end if downcase_name == "readonly" && is_element - {"readOnly" => value} + {"readOnly".as(Codegen::Node) => value} elsif lookups[node]? {js.variable_of(lookups[node]) => value} else - { %("#{node.name.value}") => value } + {(%("#{node.name.value}").as(Codegen::Node)) => value} end end end diff --git a/src/compilers/html_component.cr b/src/compilers/html_component.cr index 36f9cef04..f3b6c6ff4 100644 --- a/src/compilers/html_component.cr +++ b/src/compilers/html_component.cr @@ -1,19 +1,19 @@ module Mint class Compiler - def _compile(node : Ast::HtmlComponent) : String + def _compile(node : Ast::HtmlComponent) : Codegen::Node if node.static? name = static_components_pool.of(node.static_hash, nil) static_components[name] ||= compile_html_component(node) - "$#{name}()" + "$#{name}()".as(Codegen::Node) else compile_html_component(node) end end - def compile_html_component(node : Ast::HtmlComponent) : String + def compile_html_component(node : Ast::HtmlComponent) : Codegen::Node name = js.class_of(lookups[node]) @@ -24,27 +24,25 @@ module Mint items = compile node.children, ", " - "_array(#{items})" + Codegen.join ["_array(", items, ")"] end attributes = node .attributes .map { |item| resolve(item, false) } - .reduce({} of String => String) { |memo, item| memo.merge(item) } + .reduce({} of Codegen::Node => Codegen::Node) { |memo, item| memo.merge(item) } node.ref.try do |ref| attributes["ref"] = "(instance) => { this._#{ref.value} = instance }" end contents = - [name, - js.object(attributes), - children] - .reject!(&.empty?) - .join(", ") + Codegen.join( + [name, js.object(attributes), children].reject! { |item| Codegen.empty? item }, + ", ") - "_h(#{contents})" + Codegen.join ["_h(", contents, ")"] end end end diff --git a/src/compilers/html_element.cr b/src/compilers/html_element.cr index 10edcd350..a0c45b4a6 100644 --- a/src/compilers/html_element.cr +++ b/src/compilers/html_element.cr @@ -1,19 +1,21 @@ module Mint class Compiler - def compile(value : String) + def compile(value : String) : Codegen::Node "`#{value}`" end def compile(value : Array(Ast::Node | String), quote_string : Bool = false) if value.any?(Ast::Node) - value.compact_map do |part| + Codegen.join(value, " + ") do |part| case part when Ast::StringLiteral compile part, quote: quote_string + when String + compile part else compile part - end.presence - end.join(" + ") + end + end else result = value @@ -24,7 +26,7 @@ module Mint end end - def _compile(node : Ast::HtmlElement) : String + def _compile(node : Ast::HtmlElement) : Codegen::Node tag = node.tag.value @@ -43,7 +45,7 @@ module Mint .attributes .reject(&.name.value.in?("class", "style")) .map { |attribute| resolve(attribute) } - .reduce({} of String => String) { |memo, item| memo.merge(item) } + .reduce({} of String => Codegen::Node) { |memo, item| memo.merge(item) } style_nodes = node.styles.map { |item| lookups[item].as(Ast::Style) } @@ -66,11 +68,11 @@ module Mint classes = case when class_name && class_name_attribute_value - "#{class_name_attribute_value} + ` #{class_name}`" + Codegen.join([class_name_attribute_value, " + ` ", class_name, "`"]) when class_name_attribute_value - "#{class_name_attribute_value}" + class_name_attribute_value.as(Codegen::Node) when class_name - "`#{class_name}`" + Codegen.join(["`" + class_name + "`"]) end attributes["className"] = classes if classes @@ -80,7 +82,7 @@ module Mint .find(&.name.value.==("style")) .try { |attribute| compile(attribute.value) } - styles = %w[] + styles = [] of Codegen::Node node.styles.each do |item| next unless style_builder.any?(lookups[item]) @@ -97,7 +99,7 @@ module Mint styles << custom_styles if custom_styles unless styles.empty? - attributes["style"] = "_style([#{styles.join(", ")}])" + attributes["style"] = Codegen.join ["_style([", Codegen.join(styles, ", "), "])"] end node.ref.try do |ref| @@ -112,13 +114,11 @@ module Mint end contents = - [%("#{tag}"), - attributes, - children] - .reject!(&.empty?) - .join(", ") + Codegen.join( + [%("#{tag}"), attributes, children].reject! { |item| Codegen.empty? item }, + ", ") - "_h(#{contents})" + Codegen.join ["_h(", contents, ")"] end end end diff --git a/src/compilers/html_expression.cr b/src/compilers/html_expression.cr index 29aea775d..507324af3 100644 --- a/src/compilers/html_expression.cr +++ b/src/compilers/html_expression.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def _compile(node : Ast::HtmlExpression) : String + def _compile(node : Ast::HtmlExpression) : Codegen::Node case node.expressions.size when 0 "null" @@ -10,7 +10,7 @@ module Mint children = compile node.expressions - "_h(React.Fragment, {}, #{js.array(children)})" + Codegen.join ["_h(React.Fragment, {}, ", js.array(children), ")"] end end end diff --git a/src/compilers/html_fragment.cr b/src/compilers/html_fragment.cr index 26180be0e..b0dedb861 100644 --- a/src/compilers/html_fragment.cr +++ b/src/compilers/html_fragment.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def _compile(node : Ast::HtmlFragment) : String + def _compile(node : Ast::HtmlFragment) : Codegen::Node attributes = if key = node.key js.object({"key" => compile key.value}) @@ -14,7 +14,7 @@ module Mint items = compile node.children - "_h(React.Fragment, #{attributes}, #{js.array(items)})" + Codegen.join ["_h(React.Fragment, ", attributes, ", ", js.array(items), ")"] end end end diff --git a/src/compilers/if.cr b/src/compilers/if.cr index 0befc1122..cbd6ce3c0 100644 --- a/src/compilers/if.cr +++ b/src/compilers/if.cr @@ -1,25 +1,6 @@ module Mint class Compiler - def _compile(items : Array(Ast::CssDefinition), block : Proc(String, String)?) - compiled = - items.each_with_object({} of String => String) do |definition, memo| - variable = - if block - block.call(definition.name) - else - "" - end - - value = - compile definition.value - - memo["[`#{variable}`]"] = value - end - - "Object.assign(_, #{js.object(compiled)})" - end - - def compile(node : Ast::If, block : Proc(String, String)? = nil) : String + def compile(node : Ast::If, block : Proc(Codegen::Node, Codegen::Node)? = nil) : Codegen::Node if checked.includes?(node) _compile node, block else @@ -27,7 +8,7 @@ module Mint end end - def _compile(node : Ast::If, block : Proc(String, String)? = nil) : String + def _compile(node : Ast::If, block : Proc(Codegen::Node, Codegen::Node)? = nil) : Codegen::Node condition = compile node.condition @@ -39,7 +20,7 @@ module Mint when Array(Ast::CssDefinition) _compile item, block: block else - compile item + Codegen.source_mapped(item, compile item) end falsy = @@ -47,14 +28,14 @@ module Mint when Array(Ast::CssDefinition) _compile item, block: block when Ast::If - compile item, block: block + Codegen.source_mapped(item, compile item, block: block) when Ast::Node - compile item + Codegen.source_mapped(item, compile item) else "null" end - "(#{condition} ? #{truthy} : #{falsy})" + Codegen.source_mapped(node, Codegen.join ["(", condition, " ? ", truthy, " : ", falsy, ")"]) end end end diff --git a/src/compilers/inline_function.cr b/src/compilers/inline_function.cr index 6dada9498..5f360b7c0 100644 --- a/src/compilers/inline_function.cr +++ b/src/compilers/inline_function.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def _compile(node : Ast::InlineFunction) : String + def _compile(node : Ast::InlineFunction) : Codegen::Node body = compile node.body diff --git a/src/compilers/interpolation.cr b/src/compilers/interpolation.cr index 6447f0e0d..c85994cc9 100644 --- a/src/compilers/interpolation.cr +++ b/src/compilers/interpolation.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def _compile(node : Ast::Interpolation) : String + def _compile(node : Ast::Interpolation) : Codegen::Node compile node.expression end end diff --git a/src/compilers/js.cr b/src/compilers/js.cr index 6457488a6..d7e41ecfb 100644 --- a/src/compilers/js.cr +++ b/src/compilers/js.cr @@ -1,20 +1,20 @@ module Mint class Compiler - def _compile(node : Ast::Js) : String + def _compile(node : Ast::Js) : Codegen::Node value = - node.value.join do |item| + Codegen.strip(Codegen.join(node.value) do |item| case item when Ast::Node compile item else item end - end.strip + end) - if value.empty? + if Codegen.empty? value "" else - "(#{value})" + Codegen.join ["(", Codegen.source_mapped(node, value), ")"] end end end diff --git a/src/compilers/member_access.cr b/src/compilers/member_access.cr index bc2e7d257..ed400e2cc 100644 --- a/src/compilers/member_access.cr +++ b/src/compilers/member_access.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def _compile(node : Ast::MemberAccess) : String + def _compile(node : Ast::MemberAccess) : Codegen::Node "((_) => _.#{node.name.value})" end end diff --git a/src/compilers/module.cr b/src/compilers/module.cr index 046508190..5298461b4 100644 --- a/src/compilers/module.cr +++ b/src/compilers/module.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def _compile(node : Ast::Module) : String + def _compile(node : Ast::Module) : Codegen::Node name = js.class_of(node) @@ -11,16 +11,20 @@ module Mint compile_constants node.constants constructor = - unless constants.empty? - [js.function("constructor", %w[]) do + if constants.empty? + [] of Codegen::Node + else + [js.function("constructor", [] of Codegen::Node) do js.statements([ - js.call("super", %w[]), + js.call("super", [] of Codegen::Node), js.call("this._d", [js.object(constants)]), ]) end] end - js.module(name, %w[] &+ functions &+ constructor) + js.module(name, + ([] of Codegen::Node &+ functions &+ constructor) + .reject! { |item| Codegen.empty?(item) }) end end end diff --git a/src/compilers/module_access.cr b/src/compilers/module_access.cr index 5f51c7a85..e0aac843c 100644 --- a/src/compilers/module_access.cr +++ b/src/compilers/module_access.cr @@ -1,20 +1,20 @@ module Mint class Compiler - def _compile(node : Ast::ModuleAccess) : String + def _compile(node : Ast::ModuleAccess) : Codegen::Node name = js.class_of(lookups[node]) case lookups[node] when Ast::Provider if node.variable.value == "subscriptions" - return "#{name}._subscriptions" + return Codegen.join [name, "._subscriptions"] end end variable = js.variable_of(lookups[node.variable]) - "#{name}.#{variable}" + Codegen.join [name, ".", variable] end end end diff --git a/src/compilers/negated_expression.cr b/src/compilers/negated_expression.cr index fbff1ef84..8f0c33166 100644 --- a/src/compilers/negated_expression.cr +++ b/src/compilers/negated_expression.cr @@ -1,10 +1,10 @@ module Mint class Compiler - def _compile(node : Ast::NegatedExpression) : String + def _compile(node : Ast::NegatedExpression) : Codegen::Node expression = compile node.expression - "#{node.negations}#{expression}" + Codegen.join [node.negations, expression] end end end diff --git a/src/compilers/next_call.cr b/src/compilers/next_call.cr index bce6558b1..adebf6430 100644 --- a/src/compilers/next_call.cr +++ b/src/compilers/next_call.cr @@ -1,11 +1,11 @@ module Mint class Compiler - def _compile(node : Ast::NextCall) : String + def _compile(node : Ast::NextCall) : Codegen::Node entity = lookups[node]? state = - node.data.fields.each_with_object({} of String => String) do |item, memo| + node.data.fields.each_with_object({} of Codegen::Node => Codegen::Node) do |item, memo| field = case entity when Ast::Provider @@ -19,15 +19,18 @@ module Mint end if field - memo[js.variable_of(field)] = compile item.value + memo[js.variable_of(field)] = + Codegen.source_mapped(item.value, compile item.value) else memo end end js.promise do - js.arrow_function(%w[_resolve]) do - "this.setState(_u(this.state, new Record(#{js.object(state)})), _resolve)\n" + js.arrow_function(["_resolve"]) do + Codegen.join [ + "this.setState(_u(this.state, new Record(", js.object(state), ")), _resolve)", + ] end end end diff --git a/src/compilers/number_literal.cr b/src/compilers/number_literal.cr index c405529ce..d052691b5 100644 --- a/src/compilers/number_literal.cr +++ b/src/compilers/number_literal.cr @@ -1,7 +1,7 @@ module Mint class Compiler - def _compile(node : Ast::NumberLiteral) : String - node.static_value + def _compile(node : Ast::NumberLiteral) : Codegen::Node + Codegen.symbol_mapped(node, node.static_value) end end end diff --git a/src/compilers/operation.cr b/src/compilers/operation.cr index 030e8648c..7abebe09a 100644 --- a/src/compilers/operation.cr +++ b/src/compilers/operation.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def _compile(node : Ast::Operation) : String + def _compile(node : Ast::Operation) : Codegen::Node left = compile node.left @@ -9,13 +9,13 @@ module Mint case node.operator when "or" - "(#{left}._0 || #{right})" + Codegen.join ["(", left, "._0 || ", right, ")"] when "==" - "_compare(#{left}, #{right})" + Codegen.join ["_compare(", left, ", ", right, ")"] when "!=" - "!_compare(#{left}, #{right})" + Codegen.join ["!_compare(", left, ", ", right, ")"] else - "#{left} #{node.operator} #{right}" + Codegen.join [left, node.operator, right], " " end end end diff --git a/src/compilers/parallel.cr b/src/compilers/parallel.cr index 14fec2a12..cf23ed5ab 100644 --- a/src/compilers/parallel.cr +++ b/src/compilers/parallel.cr @@ -1,26 +1,27 @@ module Mint class Compiler - protected def prefix(_node : Ast::Parallel, statement : Ast::Statement, value : String) + protected def prefix(_node : Ast::Parallel, statement : Ast::Statement, value : Codegen::Node) case target = statement.target when Ast::Variable - js.assign(js.variable_of(target), value) + js.assign(Codegen.symbol_mapped(target, js.variable_of(target)), value) when Ast::TupleDestructuring + params = + target.parameters.map { |param| Codegen.symbol_mapped(param, js.variable_of(param)) } + variables = - target - .parameters - .join(',') { |param| js.variable_of(param) } + Codegen.join(params, ",") - "[#{variables}] = #{value}" + Codegen.join ["[", variables, "] = ", value] else value end end - def _compile(node : Ast::Parallel) : String + def _compile(node : Ast::Parallel) : Codegen::Node _compile(node) { |statement| compile(statement) } end - def _compile(node : Ast::Parallel, & : Ast::Statement, Int32, Bool -> String) : String + def _compile(node : Ast::Parallel, & : Ast::Statement, Int32, Bool -> Codegen::Node) : Codegen::Node statements = node.statements statements_size = statements.size @@ -41,9 +42,11 @@ module Mint node .catches .select(&.type.==(type_param.name)) - .map { |item| compile(item).as(String) } + .map { |item| compile(item) } end - end || %w[] + else + # ignore + end || [] of Codegen::Node case type when TypeChecker::Type @@ -75,20 +78,20 @@ module Mint when "Promise" if catches && !catches.empty? js.asynciif do - js.try(prefix(node, statement, "await #{expression}"), + js.try(prefix(node, statement, Codegen.join(["await ", expression])), [js.catch("_error", js.statements(catches))], "") end end end end || js.asynciif do - prefix(node, statement, "await #{expression}") + prefix(node, statement, Codegen.join(["await ", expression])) end end catch_all = node.catch_all.try do |catch| - "return #{compile catch.expression}" + Codegen.join ["return ", compile catch.expression] end || js.statements([ "console.warn(`Unhandled error in parallel expression:`)", @@ -109,10 +112,10 @@ module Mint statements.compact_map do |statement| case target = statement.target when Ast::Variable - js.let(js.variable_of(target), "null") + js.let(Codegen.symbol_mapped(target, js.variable_of(target)), "null") when Ast::TupleDestructuring target.parameters.map do |variable| - js.let(js.variable_of(variable), "null") + js.let(Codegen.symbol_mapped(variable, js.variable_of(variable)), "null") end end end.flatten diff --git a/src/compilers/parenthesized_expression.cr b/src/compilers/parenthesized_expression.cr index cfac88078..edf9366f9 100644 --- a/src/compilers/parenthesized_expression.cr +++ b/src/compilers/parenthesized_expression.cr @@ -1,10 +1,10 @@ module Mint class Compiler - def _compile(node : Ast::ParenthesizedExpression) : String + def _compile(node : Ast::ParenthesizedExpression) : Codegen::Node expression = compile node.expression - "(#{expression})" + Codegen.join ["(", expression, ")"] end end end diff --git a/src/compilers/pipe.cr b/src/compilers/pipe.cr index c01ca501b..1a2b1b0f2 100644 --- a/src/compilers/pipe.cr +++ b/src/compilers/pipe.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def _compile(node : Ast::Pipe) : String + def _compile(node : Ast::Pipe) : Codegen::Node compile node.call end end diff --git a/src/compilers/property.cr b/src/compilers/property.cr index eee82642e..e83bd2fdf 100644 --- a/src/compilers/property.cr +++ b/src/compilers/property.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def _compile(node : Ast::Property) : String + def _compile(node : Ast::Property) : Codegen::Node prop_name = if node.name.value == "children" "children" @@ -12,7 +12,7 @@ module Mint js.variable_of(node) body = - "return this._p('#{prop_name}');" + Codegen.join ["return this._p('", prop_name, "');"] js.get(name, body) end diff --git a/src/compilers/provider.cr b/src/compilers/provider.cr index 02bb35c74..1d42d9b12 100644 --- a/src/compilers/provider.cr +++ b/src/compilers/provider.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def _compile(node : Ast::Provider) : String + def _compile(node : Ast::Provider) : Codegen::Node functions = compile node.functions @@ -14,7 +14,7 @@ module Mint compile_constructor node body = - [constructor] &+ states &+ gets &+ functions + [constructor.as(Codegen::Node)] &+ states &+ gets &+ functions name = js.class_of(node) diff --git a/src/compilers/record.cr b/src/compilers/record.cr index bb2cda5e9..8fe38f77e 100644 --- a/src/compilers/record.cr +++ b/src/compilers/record.cr @@ -1,10 +1,10 @@ module Mint class Compiler - def _compile(node : Ast::Record) : String + def _compile(node : Ast::Record) : Codegen::Node fields = node.fields .map { |item| resolve(item) } - .reduce({} of String => String) { |memo, item| memo.merge(item) } + .reduce({} of String => Codegen::Node) { |memo, item| memo.merge(item) } type = types[node]? @@ -13,9 +13,9 @@ module Mint name = js.class_of(type.name) - "new #{name}(#{js.object(fields)})" + Codegen.join ["new ", name, "(", js.object(fields), ")"] else - "new Record(#{js.object(fields)})" + Codegen.join ["new Record(", js.object(fields), ")"] end end end diff --git a/src/compilers/record_constructor.cr b/src/compilers/record_constructor.cr index 8d1855ae4..630aa1022 100644 --- a/src/compilers/record_constructor.cr +++ b/src/compilers/record_constructor.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def _compile(node : Ast::RecordConstructor) : String + def _compile(node : Ast::RecordConstructor) : Codegen::Node type = types[node]? @@ -9,13 +9,13 @@ module Mint name = js.class_of(type.name) - args = %w[] + args = [] of Codegen::Node fields = type .fields .each_with_index - .reduce({} of String => String) do |memo, value| + .reduce({} of String => Codegen::Node) do |memo, value| field, index = value key, _ = field @@ -32,7 +32,7 @@ module Mint end body = - "new #{name}(#{js.object(fields)})" + Codegen.join ["new ", name, "(", js.object(fields), ")"] if args.empty? body diff --git a/src/compilers/record_definition.cr b/src/compilers/record_definition.cr index 79b0474d5..9994f9f38 100644 --- a/src/compilers/record_definition.cr +++ b/src/compilers/record_definition.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def _compile(node : Ast::RecordDefinition) : String + def _compile(node : Ast::RecordDefinition) : Codegen::Node type = types[node] name = @@ -15,7 +15,7 @@ module Mint "{}" end - "const #{name} = _R(#{mappings})" + Codegen.join ["const ", name, " = _R(", mappings, ")"] else "" end diff --git a/src/compilers/record_field.cr b/src/compilers/record_field.cr index 1aeb22028..7f240db63 100644 --- a/src/compilers/record_field.cr +++ b/src/compilers/record_field.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def resolve(node : Ast::RecordField) : Hash(String, String) + def resolve(node : Ast::RecordField) : Hash(String, Codegen::Node) value = compile node.value diff --git a/src/compilers/record_update.cr b/src/compilers/record_update.cr index 8c3bb2f10..89ee7f9d8 100644 --- a/src/compilers/record_update.cr +++ b/src/compilers/record_update.cr @@ -1,15 +1,15 @@ module Mint class Compiler - def _compile(node : Ast::RecordUpdate) : String + def _compile(node : Ast::RecordUpdate) : Codegen::Node expression = compile node.expression fields = node.fields .map { |item| resolve(item) } - .reduce({} of String => String) { |memo, item| memo.merge(item) } + .reduce({} of Codegen::Node => Codegen::Node) { |memo, item| memo.merge(item) } - "_u(#{expression}, #{js.object(fields)})" + Codegen.join ["_u(", expression, ", ", js.object(fields), ")"] end end end diff --git a/src/compilers/regexp_literal.cr b/src/compilers/regexp_literal.cr index 83d137850..959fe4625 100644 --- a/src/compilers/regexp_literal.cr +++ b/src/compilers/regexp_literal.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def _compile(node : Ast::RegexpLiteral) : String + def _compile(node : Ast::RegexpLiteral) : Codegen::Node node.static_value end end diff --git a/src/compilers/route.cr b/src/compilers/route.cr index 91aa5c2c0..22d172cfc 100644 --- a/src/compilers/route.cr +++ b/src/compilers/route.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def _compile(node : Ast::Route) : String + def _compile(node : Ast::Route) : Codegen::Node expression = compile node.expression diff --git a/src/compilers/routes.cr b/src/compilers/routes.cr index 53d0adc89..3654fe0fa 100644 --- a/src/compilers/routes.cr +++ b/src/compilers/routes.cr @@ -1,10 +1,10 @@ module Mint class Compiler - def _compile(node : Ast::Routes) : String + def _compile(node : Ast::Routes) : Codegen::Node routes = compile node.routes - "_program.addRoutes(#{js.array(routes)})" + Codegen.join ["_program.addRoutes(", js.array(routes), ")"] end end end diff --git a/src/compilers/sequence.cr b/src/compilers/sequence.cr index 65853e6d8..d2e048d36 100644 --- a/src/compilers/sequence.cr +++ b/src/compilers/sequence.cr @@ -1,26 +1,27 @@ module Mint class Compiler - protected def prefix(_node : Ast::Sequence, statement : Ast::Statement, value : String) + protected def prefix(_node : Ast::Sequence, statement : Ast::Statement, value : Codegen::Node) case target = statement.target when Ast::Variable js.let(js.variable_of(target), value) when Ast::TupleDestructuring + params = + target.parameters.map { |param| js.variable_of(param) } + variables = - target - .parameters - .join(',') { |param| js.variable_of(param) } + Codegen.join(params, ",") - "const [#{variables}] = #{value}" + Codegen.join ["const [", variables, "] = ", value] else value end end - def _compile(node : Ast::Sequence) : String + def _compile(node : Ast::Sequence) : Codegen::Node _compile(node) { |statement| compile(statement) } end - def _compile(node : Ast::Sequence, & : Ast::Statement, Int32, Bool -> String) : String + def _compile(node : Ast::Sequence, & : Ast::Statement, Int32, Bool -> Codegen::Node) : Codegen::Node statements = node.statements statements_size = statements.size @@ -31,9 +32,9 @@ module Mint is_last = (index + 1) == statements_size - inner_prefix = ->(value : String) { + inner_prefix = ->(value : Codegen::Node) { if is_last - "_ = #{value}" + Codegen.join(["_ = ", value]) else prefix(node, statement, value) end @@ -52,12 +53,12 @@ module Mint node .catches .select(&.type.==(type.parameters[0].name)) - .map { |item| compile(item).as(String) } + .map { |item| compile(item) } else - %w[] + [] of Codegen::Node end else - %w[] + [] of Codegen::Node end case type @@ -87,21 +88,21 @@ module Mint unless catches.empty? try = js.asynciif do js.try( - body: "return await #{expression}", + body: Codegen.join(["return await ", expression]), catches: [ js.catch("_error") { js.statements(catches) }, ], finally: "") end - inner_prefix.call("await #{try}") + inner_prefix.call(Codegen.join(["await ", try])) end end - end || inner_prefix.call("await #{expression}") + end || inner_prefix.call(Codegen.join(["await ", expression])) end catch_all = node.catch_all.try do |catch| - "_ = #{compile catch.expression}" + Codegen.join ["_ = ", compile catch.expression] end || js.statements([ "console.warn(`Unhandled error in sequence expression:`)", "console.warn(_error)", @@ -122,7 +123,7 @@ module Mint js.if("!(_error instanceof DoError)", catch_all) end, ], - finally: finally.to_s), + finally: finally), "return _", ]) end diff --git a/src/compilers/state.cr b/src/compilers/state.cr index e9b22b2f4..404d66a4f 100644 --- a/src/compilers/state.cr +++ b/src/compilers/state.cr @@ -1,10 +1,10 @@ module Mint class Compiler - def _compile(node : Ast::State) : String + def _compile(node : Ast::State) : Codegen::Node name = js.variable_of(node) - js.get(name, "return this.state.#{name};") + js.get(name, Codegen.join ["return this.state.", name, ";"]) end end end diff --git a/src/compilers/statement.cr b/src/compilers/statement.cr index 9c0c74f03..5e8afd845 100644 --- a/src/compilers/statement.cr +++ b/src/compilers/statement.cr @@ -1,7 +1,7 @@ module Mint class Compiler - def _compile(node : Ast::Statement) : String - compile node.expression + def _compile(node : Ast::Statement) : Codegen::Node + Codegen.source_mapped(node, compile node.expression) end end end diff --git a/src/compilers/store.cr b/src/compilers/store.cr index 52e158339..54084f764 100644 --- a/src/compilers/store.cr +++ b/src/compilers/store.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def _compile(node : Ast::Store) : String + def _compile(node : Ast::Store) : Codegen::Node functions = compile node.functions @@ -14,7 +14,8 @@ module Mint compile_constructor node body = - [constructor] &+ states &+ gets &+ functions + ([constructor.as(Codegen::Node)] &+ states &+ gets &+ functions) + .reject! { |item| Codegen.empty?(item) } name = js.class_of(node) @@ -22,12 +23,12 @@ module Mint js.store(name, body) end - def compile_constructor(node : Ast::Store | Ast::Provider) : String + def compile_constructor(node : Ast::Store | Ast::Provider) : Codegen::Node states = node .states .select(&.in?(checked)) - .each_with_object({} of String => String) do |state, memo| + .each_with_object({} of Codegen::Node => Codegen::Node) do |state, memo| name = js.variable_of(state) @@ -42,9 +43,9 @@ module Mint js.call("this._d", [js.object(compile_constants(node.constants))]) end - js.function("constructor", %w[]) do + js.function("constructor", [] of Codegen::Node) do js.statements([ - js.call("super", %w[]), + js.call("super", [] of Codegen::Node), js.assign("this.state", js.object(states)), constants, ].compact) diff --git a/src/compilers/string_literal.cr b/src/compilers/string_literal.cr index 08898c20c..1850dba16 100644 --- a/src/compilers/string_literal.cr +++ b/src/compilers/string_literal.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def compile(node : Ast::StringLiteral, quote : Bool = false) : String + def compile(node : Ast::StringLiteral, quote : Bool = false) : Codegen::Node if checked.includes?(node) _compile(node, quote) else @@ -8,27 +8,25 @@ module Mint end end - def _compile(node : Ast::StringLiteral, quote : Bool = false) : String + def _compile(node : Ast::StringLiteral, quote : Bool = false) : Codegen::Node value = - node - .value - .join do |item| - case item - when Ast::Node - "${#{compile(item)}}" - when String - item - .gsub('`', "\\`") - .gsub("${", "\\${") - else - "" - end + Codegen.join(node.value) do |item| + case item + when Ast::Node + Codegen.join ["${", compile(item), "}"] + when String + item + .gsub('`', "\\`") + .gsub("${", "\\${") + else + "" end + end if quote - %(`"#{value}"`) + Codegen.join ["`\"", value, "\"`"] else - "`#{value}`" + Codegen.join ["`", value, "`"] end end end diff --git a/src/compilers/suite.cr b/src/compilers/suite.cr index 997873ba7..22f9dc7b9 100644 --- a/src/compilers/suite.cr +++ b/src/compilers/suite.cr @@ -1,19 +1,19 @@ module Mint class Compiler - def _compile(node : Ast::Suite) : String + def _compile(node : Ast::Suite) : Codegen::Node name = - compile node.name + (compile node.name).as(Codegen::Node) location = node.location.to_json tests = - compile node.tests, "," + (compile node.tests, ",").as(Codegen::Node) constants = compile_constants node.constants - "{ name: #{name}, location: #{location}, tests: [#{tests}], constants: #{js.object(constants)} }" + Codegen.join(["{ name: ", name, ", location: ", location, ", tests: [", tests, "], constants: ", js.object(constants), " }"]) end end end diff --git a/src/compilers/test.cr b/src/compilers/test.cr index dcc3da74e..97573663c 100644 --- a/src/compilers/test.cr +++ b/src/compilers/test.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def _compile_operation_test(operation : Ast::Operation) : String? + def _compile_operation_test(operation : Ast::Operation) : Codegen::Node? operator = operation.operator @@ -12,20 +12,21 @@ module Mint left = compile operation.left - <<-JS - ((constants) => { - const context = new TestContext(#{left}) - const right = #{right} + Codegen.join [ + "((constants) => { + const context = new TestContext(", left, ") + const right = ", right, " - context.step((subject) => { - if (#{"!" if operator == "=="}_compare(subject, right)) { - throw `Assertion failed: ${right} #{operator} ${subject}` - } - return true - }) - return context - })(constants) - JS + context.step((subject) => { + if (", operator == "==" ? "" : "!", "_compare(subject, right)) { + return true + } else { + throw `Assertion failed ${right.toString()} ", operator, " ${subject.toString()}` + } + }) + return context + })(constants)", + ] end def unwrap_parenthesized_expression(node) @@ -35,7 +36,7 @@ module Mint node end - def _compile(node : Ast::Test) : String + def _compile(node : Ast::Test) : Codegen::Node name = compile node.name @@ -62,13 +63,15 @@ module Mint end end end + compile(statement) end end expression ||= compile(raw_expression) - "{ name: #{name}, location: #{location}, proc: (constants) => { return #{expression} } }" + Codegen.join( + ["{ name: ", name, ", location: ", location, ", proc: (constants) => { return ", expression, " } }"]) end end end diff --git a/src/compilers/top_level.cr b/src/compilers/top_level.cr index 1fcc95ecc..17cc101f2 100644 --- a/src/compilers/top_level.cr +++ b/src/compilers/top_level.cr @@ -18,7 +18,7 @@ module Mint # Compiles the application with the runtime and the rendering of the $Main # component. - def self.compile(artifacts : TypeChecker::Artifacts, options = DEFAULT_OPTIONS) : String + def self.compile(artifacts : TypeChecker::Artifacts, options = DEFAULT_OPTIONS) : Codegen::Node compiler = new(artifacts, **options) @@ -29,11 +29,11 @@ module Mint .ast .components .select(&.global?) - .each_with_object({} of String => String) do |item, memo| + .each_with_object({} of String => Codegen::Node) do |item, memo| name = compiler.js.class_of(item) - memo[name] = "$#{name}" + memo[name] = Codegen.join ["$", name] end main_class = @@ -42,13 +42,13 @@ module Mint globals_object = compiler.js.object(globals) - "\n_program.render(#{main_class}, #{globals_object})" + Codegen.join ["\n_program.render(", main_class, ", ", globals_object, ")"] end || "" compiler.wrap_runtime(compiler.compile, main) end - def self.compile_embed(artifacts : TypeChecker::Artifacts, options = DEFAULT_OPTIONS) : String + def self.compile_embed(artifacts : TypeChecker::Artifacts, options = DEFAULT_OPTIONS) : Codegen::Node compiler = new(artifacts, **options) @@ -59,11 +59,11 @@ module Mint .ast .components .select(&.global?) - .each_with_object({} of String => String) do |item, memo| + .each_with_object({} of String => Codegen::Node) do |item, memo| name = compiler.js.class_of(item) - memo[name] = "$#{name}" + memo[name] = Codegen.join ["$", name] end main_class = @@ -72,7 +72,7 @@ module Mint globals_object = compiler.js.object(globals) - "\n Mint.embed = (base) => (new mint.EmbeddedProgram(base)).render(#{main_class}, #{globals_object})" + Codegen.join ["\n Mint.embed = (base) => (new mint.EmbeddedProgram(base)).render(", main_class, ", ", globals_object, ")"] end || "" compiler.wrap_runtime(compiler.compile, main) @@ -83,7 +83,7 @@ module Mint compiler = new(artifacts, **options) - compiler.compile + Codegen.build(compiler.compile)[:code] end # Compiles the application with the runtime and the tests @@ -91,11 +91,14 @@ module Mint compiler = new(artifacts) - compiler.wrap_runtime(compiler.compile(include_tests: true)) + compiled = + compiler.wrap_runtime(compiler.compile(include_tests: true)) + + (Codegen.build compiled)[:code] end # Compiles the application - def compile(include_tests : Bool = false) : String + def compile(include_tests : Bool = false) : Codegen::Node records = compile ast.records @@ -121,25 +124,31 @@ module Mint style_builder.compile footer = - unless all_css.empty? - ["_insertStyles(`\n#{all_css}\n`)"] + if all_css.empty? + "" + else + Codegen.join ["_insertStyles(`\n", all_css, "\n`)"] end suites = if include_tests - ["SUITES = [#{compile(ast.suites, ",")}]"] + [Codegen.join(["SUITES = [", compile(ast.suites, ","), "]"])] + else + [] of Codegen::Node end static = static_components.map do |name, compiled| - js.const("$#{name}", "_m(() => #{compiled})") + js.const( + "$#{name}", + Codegen.join(["_m(() => ", compiled, ")"])) end elements = - (%w[] &+ enums &+ records &+ modules &+ providers &+ routes &+ components &+ static &+ stores &+ footer &+ suites) - .reject!(&.empty?) + (enums &+ records &+ modules &+ providers &+ routes &+ components &+ static &+ stores &+ [footer] &+ suites) + .reject! { |n| Codegen.empty? n } - replace_skipped(js.statements(elements)) + js.statements(elements) end # -------------------------------------------------------------------------- @@ -185,16 +194,13 @@ module Mint # -------------------------------------------------------------------------- # Wraps the application with the runtime - def wrap_runtime(body, main = "") + def wrap_runtime(body : Codegen::Node, main : Codegen::Node = "") : Codegen::Node html_event_module = ast.unified_modules.find(&.name.==("Html.Event")).not_nil! from_event = html_event_module.functions.find(&.name.value.==("fromEvent")).not_nil! - from_event_call = - "#{js.class_of(html_event_module)}.#{js.variable_of(from_event)}" - minimized_class_warning = unless build <<-JS @@ -202,80 +208,102 @@ module Mint JS end - <<-RESULT - (() => { - const _enums = {} - const mint = Mint(_enums) - - const _normalizeEvent = (event) => { - return #{from_event_call}(mint.normalizeEvent(event)) - } - - const _R = mint.createRecord - const _h = mint.createElement - const _createPortal = mint.createPortal - const _insertStyles = mint.insertStyles - const _navigate = mint.navigate - const _compare = mint.compare - const _program = mint.program - const _encode = mint.encode - const _style = mint.style - const _array = mint.array - const _u = mint.update - const _at = mint.at - - window.TestContext = mint.TestContext - const TestContext = mint.TestContext - const ReactDOM = mint.ReactDOM - const Decoder = mint.Decoder - const Encoder = mint.Encoder - const DateFNS = mint.DateFNS - const Record = mint.Record - const React = mint.React - - const _C = mint.Component - const _P = mint.Provider - const _M = mint.Module - const _S = mint.Store - const _E = mint.Enum - - const _m = (method) => { - let value - return () => { - if (value) { return value } - value = method() - return value - } - } - - const _s = (item, callback) => { - if (item instanceof #{nothing}) { - return item - } else if (item instanceof #{just}) { - return new #{just}(callback(item._0)) - } else { - return callback(item) - } - } - - class DoError extends Error {} - - #{body} - - const Nothing = #{nothing} - const Just = #{just} - const Err = #{err} - const Ok = #{ok} - - _enums.nothing = #{nothing} - _enums.just = #{just} - _enums.err = #{err} - _enums.ok = #{ok} - - #{minimized_class_warning} - #{main} - })() - RESULT + from_event_call = + Codegen.join [js.class_of(html_event_module), ".", js.variable_of(from_event)] + + Codegen.join [ + ( + <<-JS1 + (() => { + const _enums = {} + const mint = Mint(_enums) + + const _normalizeEvent = function (event) { + return + JS1 + ) + " ", + from_event_call, + ( + <<-JS2 + (mint.normalizeEvent(event)) + }; + + const _R = mint.createRecord; + const _h = mint.createElement; + const _createPortal = mint.createPortal; + const _insertStyles = mint.insertStyles; + const _navigate = mint.navigate; + const _compare = mint.compare; + const _program = mint.program; + const _encode = mint.encode; + const _style = mint.style; + const _array = mint.array; + const _u = mint.update; + const _at = mint.at; + + window.TestContext = mint.TestContext; + const TestContext = mint.TestContext; + const ReactDOM = mint.ReactDOM; + const Decoder = mint.Decoder; + const Encoder = mint.Encoder; + const DateFNS = mint.DateFNS; + const Record = mint.Record; + const React = mint.React; + + const _C = mint.Component; + const _P = mint.Provider; + const _M = mint.Module; + const _S = mint.Store; + const _E = mint.Enum; + + const _m = (method) => { + let value; + return () => { + if (value) { return value } + value = method() + return value + } + } + + const _s = (item, callback) => { + if (item instanceof #{nothing}) { + return item + } else if (item instanceof #{just}) { + return new #{just}(callback(item._0)) + } else { + return callback(item) + } + } + + class DoError extends Error {} + + JS2 + ), + body, + ( + <<-JS3 + + const Nothing = #{nothing} + const Just = #{just} + const Err = #{err} + const Ok = #{ok} + + _enums.nothing = #{nothing} + _enums.just = #{just} + _enums.err = #{err} + _enums.ok = #{ok} + + JS3 + ), + minimized_class_warning, + main, + ( + <<-JS4 + + })() + JS4 + ), + ].compact end end end diff --git a/src/compilers/try.cr b/src/compilers/try.cr index 492b5fae5..5415b6e9e 100644 --- a/src/compilers/try.cr +++ b/src/compilers/try.cr @@ -1,29 +1,35 @@ module Mint class Compiler - protected def prefix(_node : Ast::Try, statement : Ast::Statement, value : String) + protected def prefix(_node : Ast::Try, statement : Ast::Statement, value : Codegen::Node) case target = statement.target when Ast::Variable js.let(js.variable_of(target), value) when Ast::TupleDestructuring + params = + target.parameters.map { |param| js.variable_of(param) } + variables = - target - .parameters - .join(',') { |param| js.variable_of(param) } + Codegen.join(params, ",") - "const [#{variables}] = #{value}" + Codegen.join ["const [", variables, "] = ", value] else value end end - def _compile(node : Ast::Try) : String + def _compile(node : Ast::Try) : Codegen::Node _compile(node) { |statement| compile(statement) } end - def _compile(node : Ast::Try, & : Ast::Statement, Int32, Bool -> String) : String + def _compile(node : Ast::Try, & : Ast::Statement, Int32, Bool -> Codegen::Node) : Codegen::Node catch_all = node.catch_all.try do |catch| - js.let("_catch_all", js.arrow_function(%w[], "return #{compile(catch.expression)}")) + "\n\n" + Codegen.join [ + js.let("_catch_all", + js.arrow_function([] of Codegen::Node, + Codegen.join(["return ", compile(catch.expression)]))), + "\n\n", + ] end statements = node.statements @@ -36,9 +42,9 @@ module Mint is_last = (index + 1) == statements_size - inner_prefix = ->(value : String) { + inner_prefix = ->(value : Codegen::Node) { if is_last - "return #{value}" + Codegen.source_mapped(statement, Codegen.join ["return ", value]) else prefix(node, statement, value) end @@ -64,7 +70,7 @@ module Mint js.statements([ js.let(variable, "_error"), - "return #{catch_body}", + Codegen.join ["return ", catch_body], ]) end end @@ -77,7 +83,7 @@ module Mint end end - if catches && !catches.empty? + if catches && !Codegen.empty?(catches) js.statements([ js.let("_#{index}", expression), js.if("_#{index} instanceof Err") do @@ -93,7 +99,7 @@ module Mint end end - js.iif("#{catch_all}#{js.statements(body)}") + js.iif(Codegen.join [catch_all, js.statements(body)].compact) end end end diff --git a/src/compilers/tuple_destructuring.cr b/src/compilers/tuple_destructuring.cr index ddd5ba1c5..5a5f489e5 100644 --- a/src/compilers/tuple_destructuring.cr +++ b/src/compilers/tuple_destructuring.cr @@ -1,10 +1,15 @@ module Mint class Compiler - def _compile(node : Ast::TupleDestructuring, variable : String) : Tuple(String, Array(String)) - conditions = ["Array.isArray(#{variable})"] + def _compile(node : Ast::TupleDestructuring, variable : String) : Tuple(String, Array(Codegen::Node)) + conditions = + ["Array.isArray(#{variable})"] + variables = node.parameters.map_with_index do |param, idx| - var_name = js.variable_of(param) - vars = ["const #{var_name} = #{variable}[#{idx}]"] + var_name = + js.variable_of(param) + + vars = + ["const #{var_name} = #{variable}[#{idx}]"] of Codegen::Node if res = _compile_destructuring(param, "#{variable}[#{idx}]") conditions << res[0] diff --git a/src/compilers/tuple_literal.cr b/src/compilers/tuple_literal.cr index c48153ea5..a43af258f 100644 --- a/src/compilers/tuple_literal.cr +++ b/src/compilers/tuple_literal.cr @@ -1,10 +1,10 @@ module Mint class Compiler - def _compile(node : Ast::TupleLiteral) : String + def _compile(node : Ast::TupleLiteral) : Codegen::Node items = compile node.items, ", " - "[#{items}]" + Codegen.join ["[", items, "]"] end end end diff --git a/src/compilers/unary_minus.cr b/src/compilers/unary_minus.cr index 91e7fbfd1..fd22e4b2e 100644 --- a/src/compilers/unary_minus.cr +++ b/src/compilers/unary_minus.cr @@ -1,10 +1,10 @@ module Mint class Compiler - def _compile(node : Ast::UnaryMinus) : String + def _compile(node : Ast::UnaryMinus) : Codegen::Node expression = compile node.expression - "-(#{expression})" + Codegen.join ["-(", expression, ")"] end end end diff --git a/src/compilers/variable.cr b/src/compilers/variable.cr index 6c4b7a954..2201b25d5 100644 --- a/src/compilers/variable.cr +++ b/src/compilers/variable.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def _compile(node : Ast::Variable) : String + def _compile(node : Ast::Variable) : Codegen::Node entity, parent = variables[node] # Subscriptions for providers are handled here @@ -37,88 +37,92 @@ module Mint end end - case parent - when Tuple(String, TypeChecker::Checkable, Ast::Node) - js.variable_of(parent[2]) - else - case entity - when Ast::Component, Ast::HtmlElement - case parent - when Ast::Component - ref = - parent - .refs - .find { |(ref, _)| ref.value == node.value } - .try { |(ref, _)| js.variable_of(ref) } + result = + case parent + when Tuple(String, TypeChecker::Checkable, Ast::Node) + js.variable_of(parent[2]) + else + case entity + when Ast::Component, Ast::HtmlElement + case parent + when Ast::Component + ref = + parent + .refs + .find { |(ref, _)| ref.value == node.value } + .try { |(ref, _)| js.variable_of(ref) } + .as(Codegen::Node) - "this.#{ref}" - else - raise "SHOULD NOT HAPPEN" - end - when Ast::Function - function = - if connected - js.variable_of(connected) + Codegen.join ["this.", ref] else - js.variable_of(entity.as(Ast::Node)) + raise "SHOULD NOT HAPPEN" end + when Ast::Function + function = + if connected + js.variable_of(connected) + else + js.variable_of(entity.as(Ast::Node)) + end - case parent - when Ast::Module, Ast::Store - name = - js.class_of(parent.as(Ast::Node)) + case parent + when Ast::Module, Ast::Store + name = + js.class_of(parent.as(Ast::Node)) - "#{name}.#{function}" - else - "this.#{function}" - end - when Ast::Property, Ast::Get, Ast::State, Ast::Constant - name = - if connected - js.variable_of(connected) + Codegen.join [name, ".", function] else - js.variable_of(entity.as(Ast::Node)) + Codegen.join ["this.", function] end + when Ast::Property, Ast::Get, Ast::State, Ast::Constant + name = + if connected + js.variable_of(connected) + else + js.variable_of(entity.as(Ast::Node)) + end - case parent - when Ast::Suite - # The variable is a constant in a test suite - "constants.#{name}()" - else - "this.#{name}" - end - when Ast::Argument - compile entity - when Ast::Statement, Ast::WhereStatement - case target = entity.target - when Ast::Variable - js.variable_of(target) - else - "SHOULD NEVER HAPPEN" - end - when Tuple(Ast::Node, Array(Int32) | Int32) - case item = entity[0] - when Ast::WhereStatement, Ast::Statement - case target = item.target - when Ast::TupleDestructuring - case val = entity[1] - in Int32 - js.variable_of(target.parameters[val]) - in Array(Int32) - js.variable_of(val.reduce(target) do |curr_type, curr_val| - curr_type.as(Ast::TupleDestructuring).parameters[curr_val] - end) + case parent + when Ast::Suite + # The variable is a constant in a test suite + Codegen.join ["constants.", name, "()"] + else + Codegen.join ["this.", name] + end + when Ast::Argument + compile entity + when Ast::Statement, Ast::WhereStatement + case target = entity.target + when Ast::Variable + js.variable_of(target) + else + "SHOULD NEVER HAPPEN" + end + when Tuple(Ast::Node, Array(Int32) | Int32) + case item = entity[0] + when Ast::WhereStatement, Ast::Statement + case target = item.target + when Ast::TupleDestructuring + case val = entity[1] + in Int32 + js.variable_of(target.parameters[val]) + in Array(Int32) + js.variable_of(val.reduce(target) do |curr_type, curr_val| + curr_type.as(Ast::TupleDestructuring).parameters[curr_val] + end) + end + else + js.variable_of(node) end else js.variable_of(node) end else - js.variable_of(node) + Codegen.join ["this.", node.value] end - else - "this.#{node.value}" end - end + + Codegen.source_mapped(node, result) end end end diff --git a/src/compilers/void.cr b/src/compilers/void.cr index 517cdf09d..bf14637ac 100644 --- a/src/compilers/void.cr +++ b/src/compilers/void.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def _compile(node : Ast::Void) : String + def _compile(node : Ast::Void) : Codegen::Node "null" end end diff --git a/src/compilers/where_statement.cr b/src/compilers/where_statement.cr index f35479be6..405690040 100644 --- a/src/compilers/where_statement.cr +++ b/src/compilers/where_statement.cr @@ -1,17 +1,17 @@ module Mint class Compiler - def _compile(node : Ast::WhereStatement) : String + def _compile(node : Ast::WhereStatement) : Codegen::Node expression = compile node.expression case target = node.target when Ast::Variable name = - js.variable_of(target) + Codegen.symbol_mapped(target, js.variable_of(target)) - "let #{name} = #{expression}" + Codegen.join ["let ", name, " = ", expression] when Ast::TupleDestructuring - "const #{_to_variable(target)} = #{expression}" + Codegen.join ["const ", _to_variable(target), " = ", expression] else "" end @@ -27,6 +27,7 @@ module Mint _to_variable(param) end end + "[#{variables}]" end diff --git a/src/compilers/with.cr b/src/compilers/with.cr index bb04176a9..8181e6a20 100644 --- a/src/compilers/with.cr +++ b/src/compilers/with.cr @@ -1,6 +1,6 @@ module Mint class Compiler - def _compile(node : Ast::With) : String + def _compile(node : Ast::With) : Codegen::Node compile node.body end end diff --git a/src/js.cr b/src/js.cr index 9dcc3f5a9..b89a3b3a4 100644 --- a/src/js.cr +++ b/src/js.cr @@ -1,53 +1,55 @@ module Mint abstract class Renderer - abstract def object(hash : Hash(String, String)) : String - abstract def function(name : String, arguments : Array(String), body : String) : String - abstract def arrow_function(arguments : Array(String), body : String) : String - abstract def const(name : String, value : String) : String - abstract def class(name : String, extends : String, body : Array(String)) : String - abstract def assign(name : String, value : String) : String - abstract def statements(items : Array(String)) : String - abstract def ifchain(items : Array(Tuple(String?, String))) : String - abstract def store(name : String, body : Array(String)) : String - abstract def module(name : String, body : Array(String)) : String - abstract def provider(name : String, body : Array(String)) : String - abstract def iic(body : Array(String)) : String - abstract def iif(body : String) : String - abstract def asynciif(body : String) : String - abstract def get(name : String, body : String) : String - abstract def if(condition : String, body : String) : String - abstract def elseif(condition, &block : Proc(String)) : String - abstract def else(&block : Proc(String)) : String - abstract def catch(condition : String, body : String) : String - abstract def try(body : String, catches : Array(String), finally : String) : String - abstract def promise(body : String) : String - abstract def array(items : Array(String)) : String + abstract def object(hash : Hash(Codegen::Node, Codegen::Node)) : Codegen::Node + abstract def function(name : Codegen::Node, arguments : Array(Codegen::Node), body : Codegen::Node) : Codegen::Node + abstract def arrow_function(arguments : Array(Codegen::Node), body : Codegen::Node) : Codegen::Node + abstract def const(name : String, value : Codegen::Node) : Codegen::Node + abstract def class(name : String | Codegen::SourceMappable, extends : Codegen::Node, body : Array(Codegen::Node)) : Codegen::Node + abstract def assign(name : Codegen::Node, value : Codegen::Node) : Codegen::Node + abstract def statements(items : Array(Codegen::Node)) : Codegen::Node + abstract def ifchain(items : Array(Tuple(Codegen::Node?, Codegen::Node))) : Codegen::Node + abstract def store(name : Codegen::Node, body : Array(Codegen::Node)) : Codegen::Node + abstract def module(name : String, body : Array(Codegen::Node)) : Codegen::Node + abstract def provider(name : String, body : Array(Codegen::Node)) : Codegen::Node + abstract def iif(body : Codegen::Node) : Codegen::Node + abstract def asynciif(body : Codegen::Node) : Codegen::Node + abstract def get(name : Codegen::Node, body : Codegen::Node) : Codegen::Node + abstract def if(condition : Codegen::Node, body : Codegen::Node) : Codegen::Node + abstract def elseif(condition : Codegen::Node, &block : Proc(Codegen::Node)) : Codegen::Node + abstract def else(&block : Proc(Codegen::Node)) : Codegen::Node + abstract def catch(condition : Codegen::Node, body : Codegen::Node) : Codegen::Node + abstract def try(body : Codegen::Node, catches : Array(Codegen::Node), finally : Codegen::Node?) : Codegen::Node + abstract def promise(body : Codegen::Node) : Codegen::Node + abstract def array(items : Array(Codegen::Node)) : Codegen::Node abstract def display_name(name : String, real_name : String) : String abstract def css_rule(name : String, definitions : Array(String)) : String abstract def css_rules(rules : Array(String)) : String - abstract def for(condition : String, body : String) : String - - def ifchain(items : Array(Tuple(String?, String))) : String - items - .sort_by { |(condition, _)| condition.nil? ? 1 : -1 } - .map_with_index do |(condition, body), index| - case {index, condition} - when {0, nil} - body # This branch handles only one item which does not have condition - when {_, nil} - self.else { body } - when {0, _} - self.if(condition.to_s, body) - else - self.elseif(condition) { body } + abstract def for(condition : Codegen::Node, body : Codegen::Node) : Codegen::Node + + def ifchain(items : Array(Tuple(Codegen::Node?, Codegen::Node))) : Codegen::Node + converted = + items + .sort_by { |(condition, _)| condition.nil? ? 1 : -1 } + .map_with_index do |(condition, body), index| + case {index, condition} + when {0, nil} + body # This branch handles only one item which does not have condition + when {_, nil} + self.else { body } + when {0, _} + self.if(condition, body) + else + self.elseif(condition) { body } + end end - end.join(' ') + + Codegen.join(converted, " ") end end class Optimized < Renderer - def for(condition, body) : String - "for(#{condition}){#{body}}" + def for(condition, body) : Codegen::Node + Codegen.join ["for(", condition, "){", body, "}"] end def css_rule(name, definitions) : String @@ -62,97 +64,95 @@ module Mint "" end - def object(hash : Hash(String, String)) : String - body = - hash.join(',') { |key, value| "#{key}:#{value}" } - - "{#{body}}" - end + def object(hash : Hash(Codegen::Node, Codegen::Node)) : Codegen::Node + body_parts = [] of Codegen::Node + hash.each do |key, value| + body_parts << Codegen.join [key, ":", value] + end - def function(name : String, arguments : Array(String), body : String) : String - "#{name}(#{arguments.join(',')}){#{body}}" + Codegen.join ["{", Codegen.join(body_parts, ","), "}"] end - def arrow_function(arguments : Array(String), body : String) : String - "((#{arguments.join(", ")})=>{#{body}})" + def function(name : Codegen::Node, arguments : Array(Codegen::Node), body : Codegen::Node) : Codegen::Node + Codegen.join [name, "(", Codegen.join(arguments, ","), "){", body, "}"] end - def const(name : String, value : String) : String - "const #{name}=#{value}" + def arrow_function(arguments : Array(Codegen::Node), body : Codegen::Node) : Codegen::Node + Codegen.join ["((", Codegen.join(arguments, ", "), ")=>{", body, "})"] end - def assign(name : String, value : String) : String - "#{name}=#{value}" + def const(name : String, value : Codegen::Node) : Codegen::Node + Codegen.join ["const ", name, "=", value] end - def class(name, extends : String, body : Array(String)) : String - "class #{name} extends #{extends}{#{body.join}}" + def assign(name : Codegen::Node, value : Codegen::Node) : Codegen::Node + Codegen.join [name, "=", value] end - def statements(items : Array(String)) : String - items.join(';') + def class(name : String | Codegen::SourceMappable, extends : Codegen::Node, body : Array(Codegen::Node)) : Codegen::Node + Codegen.join ["class ", name, " extends ", extends, "{", Codegen.join(body), "}"] end - def store(name : String, body : Array(String)) : String - const(name, "new(class extends _S{#{body.join}})") + def statements(items : Array(Codegen::Node)) : Codegen::Node + Codegen.join(items, ";") end - def module(name : String, body : Array(String)) : String - const(name, "new(class extends _M{#{body.join}})") + def store(name : Codegen::Node, body : Array(Codegen::Node)) : Codegen::Node + const(name, Codegen.join ["new(class extends _S{", Codegen.join(body), "})"]) end - def provider(name : String, body : Array(String)) : String - const(name, "new(class extends _P{#{body.join}})") + def module(name : String, body : Array(Codegen::Node)) : Codegen::Node + const(name, Codegen.join ["new(class extends _M{", Codegen.join(body), "})"]) end - def iic(body : Array(String)) : String - "new(class{#{body.join}})" + def provider(name : String, body : Array(Codegen::Node)) : Codegen::Node + const(name, Codegen.join ["new(class extends _P{", Codegen.join(body), "})"]) end - def iif(body : String) : String - "(()=>{#{body}})()" + def iif(body : Codegen::Node) : Codegen::Node + Codegen.join ["(()=>{", body, "})()"] end - def asynciif(body : String) : String - "(async()=>{#{body}})()" + def asynciif(body : Codegen::Node) : Codegen::Node + Codegen.join ["(async()=>{", body, "})()"] end - def get(name : String, body : String) : String - "get #{name}(){#{body}}" + def get(name : Codegen::Node, body : Codegen::Node) : Codegen::Node + Codegen.join ["get ", name, "(){", body, "}"] end - def if(condition : String, body : String) : String - "if(#{condition}){#{body}}" + def if(condition : Codegen::Node, body : Codegen::Node) : Codegen::Node + Codegen.join ["if(", condition, "){", body, "}"] end - def elseif(condition, &block : Proc(String)) : String - "else if(#{condition}){#{yield}}" + def elseif(condition : Codegen::Node, &block : Proc(Codegen::Node)) : Codegen::Node + Codegen.join ["else if(", condition, "){", yield, "}"] end - def else(&block : Proc(String)) : String - "else{#{yield}}" + def else(&block : Proc(Codegen::Node)) : Codegen::Node + Codegen.join ["else{", yield, "}"] end - def catch(condition : String, body : String) : String - "catch(#{condition}){#{body}}" + def catch(condition : Codegen::Node, body : Codegen::Node) : Codegen::Node + Codegen.join ["catch(", condition, "){", body, "}"] end - def try(body : String, catches : Array(String), finally : String) : String - "try{#{body}}#{catches.join}#{finally}" + def try(body : Codegen::Node, catches : Array(Codegen::Node), finally : Codegen::Node?) : Codegen::Node + Codegen.join ["try{", body, "}", Codegen.join(catches), finally].compact end - def promise(body : String) : String - "new Promise(#{body})" + def promise(body : Codegen::Node) : Codegen::Node + Codegen.join ["new Promise(", body, ")"] end - def array(items : Array(String)) : String - "[#{items.join(',')}]" + def array(items : Array(Codegen::Node)) : Codegen::Node + Codegen.join ["[", Codegen.join(items, ","), "]"] end end class Normal < Renderer - def for(condition, body) : String - "for (#{condition}) #{class_body(body)}" + def for(condition, body) : Codegen::Node + Codegen.join ["for (", condition, ") ", class_body(body)] end def css_rule(name, definitions) : String @@ -167,124 +167,125 @@ module Mint %(#{name}.displayName = "#{real_name}") end - def object(hash : Hash(String, String)) : String + def object(hash : Hash(Codegen::Node, Codegen::Node)) : Codegen::Node if hash.empty? "{}" else - body = - hash.join(",\n") { |key, value| "#{key}: #{value}" } + body_parts = [] of Codegen::Node + hash.each do |key, value| + body_parts << Codegen.join [key, ": ", value] + end - "{\n#{body.indent}\n}" + Codegen.join ["{\n", Codegen.indent(Codegen.join(body_parts, ",\n")), "\n}"] end end - def function(name : String, arguments : Array(String), body : String) : String - "#{name}(#{arguments.join(", ")}) #{class_body(body)}" + def function(name : Codegen::Node, arguments : Array(Codegen::Node), body : Codegen::Node) : Codegen::Node + Codegen.join [name, "(", Codegen.join(arguments, ", "), ") ", class_body(body)] end - def arrow_function(arguments : Array(String), body : String) : String - "(#{arguments.join(", ")}) => #{class_body(body)}" + def arrow_function(arguments : Array(Codegen::Node), body : Codegen::Node) : Codegen::Node + Codegen.join ["(", Codegen.join(arguments, ", "), ") => ", class_body(body)] end - def const(name : String, value : String) : String - "const #{name} = #{value}" + def const(name : String, value : Codegen::Node) : Codegen::Node + Codegen.join ["const ", name, " = ", value] end - def class(name : String, extends : String, body : Array(String)) : String - "class #{name} extends #{extends} #{class_body(body)}" + def class(name : String | Codegen::SourceMappable, extends : Codegen::Node, body : Array(Codegen::Node)) : Codegen::Node + Codegen.join ["class ", name, " extends ", extends, " ", class_body(body)] end - def assign(name : String, value : String) : String - "#{name} = #{value}" + def assign(name : Codegen::Node, value : Codegen::Node) : Codegen::Node + Codegen.join [name, " = ", value] end - def statements(items : Array(String)) : String - items.each_with_index.reduce("") do |memo, (item, index)| - last = items[index - 1]? if index > 0 + def statements(items : Array(Codegen::Node)) : Codegen::Node + nodes = + items.each_with_index.reduce([] of Codegen::Node) do |memo, (item, index)| + last = items[index - 1]? if index > 0 - if last - if last.includes?('\n') - memo += "\n\n" - elsif item.includes?('\n') - memo += "\n\n" - else - memo += "\n" + if last + if Codegen.includes_endl? last + memo << "\n\n" + elsif Codegen.includes_endl? item + memo << "\n\n" + else + memo << "\n" + end end - end - memo += item - memo += ";" unless memo.ends_with?(';') - memo - end - end + memo << item + memo << ";" unless Codegen.ends_with?(';', Codegen.join memo) + memo + end - def store(name : String, body : Array(String)) : String - const(name, "new(class extends _S #{class_body(body)})") + Codegen.join nodes end - def module(name : String, body : Array(String)) : String - const(name, "new(class extends _M #{class_body(body)})") + def store(name : Codegen::Node, body : Array(Codegen::Node)) : Codegen::Node + const(name, Codegen.join ["new(class extends _S ", class_body(body), ")"]) end - def provider(name : String, body : Array(String)) : String - const(name, "new(class extends _P #{class_body(body)})") + def module(name : String, body : Array(Codegen::Node)) : Codegen::Node + const(name, Codegen.join ["new(class extends _M ", class_body(body), ")"]) end - def iic(body : Array(String)) : String - "new(class #{class_body(body)})" + def provider(name : String, body : Array(Codegen::Node)) : Codegen::Node + const(name, Codegen.join ["new(class extends _P ", class_body(body), ")"]) end - def iif(body : String) : String - "(() => #{class_body(body)})()" + def iif(body : Codegen::Node) : Codegen::Node + Codegen.join ["(() => ", class_body(body), ")()"] end - def asynciif(body : String) : String - "(async () => #{class_body(body)})()" + def asynciif(body : Codegen::Node) : Codegen::Node + Codegen.join ["(async () => ", class_body(body), ")()"] end - def get(name : String, body : String) : String - "get #{name}() #{class_body(body)}" + def get(name : Codegen::Node, body : Codegen::Node) : Codegen::Node + Codegen.join ["get ", name, "() ", class_body(body)] end - def if(condition : String, body : String) : String - "if (#{condition}) #{class_body(body)}" + def if(condition : Codegen::Node, body : Codegen::Node) : Codegen::Node + Codegen.join ["if (", condition, ") ", class_body(body)] end - def elseif(condition, &block : Proc(String)) : String - "else if (#{condition}) #{class_body(yield)}" + def elseif(condition, &block : Proc(Codegen::Node)) : Codegen::Node + Codegen.join ["else if (", condition, ") ", class_body(yield)] end - def else(&block : Proc(String)) : String - "else #{class_body(yield)}" + def else(&block : Proc(Codegen::Node)) : Codegen::Node + Codegen.join ["else ", class_body(yield)] end - def catch(condition : String, body : String) : String - "catch (#{condition}) #{class_body(body)}" + def catch(condition : Codegen::Node, body : Codegen::Node) : Codegen::Node + Codegen.join ["catch (", condition, ") ", class_body(body)] end - def try(body : String, catches : Array(String), finally : String) : String - finally = " #{finally}" unless finally.empty? - "try #{class_body(body)} #{catches.join("\n ")}#{finally}" + def try(body : Codegen::Node, catches : Array(Codegen::Node), finally : Codegen::Node?) : Codegen::Node + finally = Codegen.join [" ", finally] unless finally.nil? || Codegen.empty? finally + Codegen.join ["try ", class_body(body), " ", Codegen.join(catches, "\n "), finally].compact end - def promise(body : String) : String - "new Promise(#{body})" + def promise(body : Codegen::Node) : Codegen::Node + Codegen.join ["new Promise(", body, ")"] end - def array(items : Array(String)) : String + def array(items : Array(Codegen::Node)) : Codegen::Node if items.empty? "[]" else - "[\n#{items.join(",\n").indent}\n]" + Codegen.join ["[\n", Codegen.indent(Codegen.join(items, ",\n")), "\n]"] end end - private def class_body(body : String) - "{\n#{body.indent}\n}" + private def class_body(body : Codegen::Node) + Codegen.join ["{\n", Codegen.indent(body), "\n}"] end - private def class_body(body : Array(String)) - "{\n#{body.join("\n\n").indent}\n}" + private def class_body(body : Array(Codegen::Node)) + Codegen.join ["{\n", Codegen.indent(Codegen.join(body, "\n\n")), "\n}"] end end @@ -294,7 +295,7 @@ module Mint @style_prop_cache : Hash(String, String) = {} of String => String @style_cache : Hash(Ast::Node, String) = {} of Ast::Node => String - @cache : Hash(Ast::Node, String) = {} of Ast::Node => String + @cache : Hash(Ast::Node, Codegen::Node) = {} of Ast::Node => Codegen::Node @type_cache : Hash(String, String) = {} of String => String @@ -311,7 +312,7 @@ module Mint @renderer = optimize ? Optimized.new : Normal.new end - def variable_of(node) + def variable_of(node : Ast::Node) : Codegen::Node case node when Ast::Function return node.name.value if node.keep_name? @@ -320,12 +321,17 @@ module Mint @cache[node] ||= next_variable end - def class_of(name : String) + def class_of(name : String) : String @type_cache[name] ||= next_class end - def class_of(node : Ast::Node) - @cache[node] ||= next_class + def class_of(node : Ast::Node) : String + val = (@cache[node] ||= next_class) + case val + when String + val + else raise "Can't determine class of #{typeof(node)}" + end end def style_of(node : Ast::Node) @@ -340,29 +346,30 @@ module Mint next_variable end - def let(name, value) - "let #{name} = #{value}" + def let(name : Codegen::Node, value : Codegen::Node) + Codegen.join ["let ", name, " = ", value] end - def let(value) + def let(value : Codegen::Node) variable = next_variable - {variable, "let #{variable} = #{value}"} + {variable, Codegen.join ["let ", variable, " = ", value]} end - def call(name, props) - "#{name}(#{props.join(',')})" + def call(name : Codegen::Node, props : Array(Codegen::Node)) + props_joined = Codegen.join(props, ",") + Codegen.join [name, "(", props_joined, ")"] end - def function(name, arguments = %w[]) : String + def function(name, arguments = [] of Codegen::Node) : Codegen::Node function(name, arguments, yield) end - def return(value) - if value.empty? + def return(value : Codegen::Node) : Codegen::Node + if Codegen.empty? value "return" else - "return #{value}" + Codegen.join ["return ", value] end end @@ -382,7 +389,7 @@ module Mint promise(yield) end - def arrow_function(arguments : Array(String)) + def arrow_function(arguments : Array(Codegen::Node)) arrow_function(arguments, yield) end diff --git a/src/reactor.cr b/src/reactor.cr index d9e430668..434a3b7a0 100644 --- a/src/reactor.cr +++ b/src/reactor.cr @@ -8,10 +8,12 @@ module Mint # * Keeps a cache of ASTs of the parsed files for faster recompilation # * When --auto-format flag is passed all source files are watched and if # any changes it formats the file + # * By default generates source map for debugging (--source_map flag) class Reactor @artifacts : TypeChecker::Artifacts? @live_reload : Bool @auto_format : Bool + @source_map : Bool @error : String? @host : String @port : Int32 @@ -21,11 +23,13 @@ module Mint getter ast : Ast = Ast.new getter script : String? - def self.start(host : String, port : Int32, auto_format : Bool, live_reload : Bool) - new host, port, auto_format, live_reload + def self.start(host : String, port : Int32, auto_format : Bool, live_reload : Bool, mappings : Bool) + new host, port, auto_format, live_reload, mappings end - def initialize(@host, @port, @auto_format, @live_reload) + def initialize(@host, @port, @auto_format, @live_reload, @source_map) + @script_map = "" + terminal.measure "#{COG} Ensuring dependencies..." do MintJson.parse_current.check_dependencies! end @@ -98,18 +102,22 @@ module Mint type_checker.check # Compile. - @script = Compiler.compile type_checker.artifacts, { + compiled_code = Compiler.compile type_checker.artifacts, { css_prefix: json.application.css_prefix, relative: false, optimize: false, build: false, } @artifacts = type_checker.artifacts + build = Codegen.build(compiled_code, @source_map) + @script = build[:code] + @script_map = build[:source_map].try(&.build_json) || "" @error = nil rescue error : Error @error = error.to_html @artifacts = nil @script = nil + @script_map = "" end def live_reload @@ -143,7 +151,19 @@ module Mint get "/index.js" do |env| env.response.content_type = "application/javascript" - script + if @source_map + env.response.headers["SourceMap"] = "index.js.map" + end + + @script + end + + if @source_map + get "/index.js.map" do |env| + env.response.content_type = "application/json" + + @script_map + end end get "/external-javascripts.js" do |env| diff --git a/src/sandbox_server.cr b/src/sandbox_server.cr index 46642ef5e..f7810d4ac 100644 --- a/src/sandbox_server.cr +++ b/src/sandbox_server.cr @@ -93,6 +93,9 @@ module Mint build: false, }) + build_result = + Codegen.build(Codegen.join([runtime, script]), false) + <<-HTML @@ -101,7 +104,7 @@ module Mint