diff --git a/moonscript/cmd/lint.lua b/moonscript/cmd/lint.lua index 50509e5..55f018b 100644 --- a/moonscript/cmd/lint.lua +++ b/moonscript/cmd/lint.lua @@ -65,6 +65,9 @@ do end end, lint_check_unused = function(self) + do + return + end if not (self.lint_unused_names and next(self.lint_unused_names)) then return end diff --git a/moonscript/cmd/lint.moon b/moonscript/cmd/lint.moon index 103c25e..853bab5 100644 --- a/moonscript/cmd/lint.moon +++ b/moonscript/cmd/lint.moon @@ -105,6 +105,7 @@ class LinterBlock extends Block @parent\lint_mark_used name lint_check_unused: => + do return return unless @lint_unused_names and next @lint_unused_names names_by_position = {} diff --git a/moonscript/parse.lua b/moonscript/parse.lua index 53c12f0..49b673d 100644 --- a/moonscript/parse.lua +++ b/moonscript/parse.lua @@ -27,10 +27,10 @@ Num = Space * (Num / function(v) v } end) -local Indent, Cut, ensure, extract_line, mark, pos, flatten_or_mark, is_assignable, check_assignable, format_assign, format_single_assign, sym, symx, simple_string, wrap_func_arg, join_chain, flatten_string_chain, wrap_decorator, check_lua_string, self_assign +local Indent, Cut, ensure, extract_line, mark, pos, flatten_or_mark, is_assignable, check_assignable, format_assign, format_single_assign, sym, symx, simple_string, wrap_func_arg, join_chain, wrap_decorator, check_lua_string, self_assign do local _obj_0 = require("moonscript.parse.util") - Indent, Cut, ensure, extract_line, mark, pos, flatten_or_mark, is_assignable, check_assignable, format_assign, format_single_assign, sym, symx, simple_string, wrap_func_arg, join_chain, flatten_string_chain, wrap_decorator, check_lua_string, self_assign = _obj_0.Indent, _obj_0.Cut, _obj_0.ensure, _obj_0.extract_line, _obj_0.mark, _obj_0.pos, _obj_0.flatten_or_mark, _obj_0.is_assignable, _obj_0.check_assignable, _obj_0.format_assign, _obj_0.format_single_assign, _obj_0.sym, _obj_0.symx, _obj_0.simple_string, _obj_0.wrap_func_arg, _obj_0.join_chain, _obj_0.flatten_string_chain, _obj_0.wrap_decorator, _obj_0.check_lua_string, _obj_0.self_assign + Indent, Cut, ensure, extract_line, mark, pos, flatten_or_mark, is_assignable, check_assignable, format_assign, format_single_assign, sym, symx, simple_string, wrap_func_arg, join_chain, wrap_decorator, check_lua_string, self_assign = _obj_0.Indent, _obj_0.Cut, _obj_0.ensure, _obj_0.extract_line, _obj_0.mark, _obj_0.pos, _obj_0.flatten_or_mark, _obj_0.is_assignable, _obj_0.check_assignable, _obj_0.format_assign, _obj_0.format_single_assign, _obj_0.sym, _obj_0.symx, _obj_0.simple_string, _obj_0.wrap_func_arg, _obj_0.join_chain, _obj_0.wrap_decorator, _obj_0.check_lua_string, _obj_0.self_assign end local build_grammar = wrap_env(debug_grammar, function(root) local _indent = Stack(0) diff --git a/moonscript/parse.moon b/moonscript/parse.moon index 96c26e5..0c6bc82 100644 --- a/moonscript/parse.moon +++ b/moonscript/parse.moon @@ -29,7 +29,7 @@ Num = Space * (Num / (v) -> {"number", v}) :Indent, :Cut, :ensure, :extract_line, :mark, :pos, :flatten_or_mark, :is_assignable, :check_assignable, :format_assign, :format_single_assign, :sym, :symx, :simple_string, :wrap_func_arg, :join_chain, - :flatten_string_chain, :wrap_decorator, :check_lua_string, :self_assign + :wrap_decorator, :check_lua_string, :self_assign } = require "moonscript.parse.util" diff --git a/moonscript/parse/util.lua b/moonscript/parse/util.lua index a93665d..e9d4554 100644 --- a/moonscript/parse/util.lua +++ b/moonscript/parse/util.lua @@ -197,17 +197,6 @@ join_chain = function(callee, args) args } end -local flatten_string_chain -flatten_string_chain = function(str, chain, args) - if not (chain) then - return str - end - return flatten_chain({ - "chain", - str, - unpack(chain) - }, args) -end local wrap_decorator wrap_decorator = function(stm, dec) if not (dec) then @@ -254,7 +243,6 @@ return { simple_string = simple_string, wrap_func_arg = wrap_func_arg, join_chain = join_chain, - flatten_string_chain = flatten_string_chain, wrap_decorator = wrap_decorator, check_lua_string = check_lua_string, self_assign = self_assign diff --git a/moonscript/parse/util.moon b/moonscript/parse/util.moon index 2039e3b..af33279 100644 --- a/moonscript/parse/util.moon +++ b/moonscript/parse/util.moon @@ -139,10 +139,6 @@ join_chain = (callee, args) -> {"chain", callee, args} -flatten_string_chain = (str, chain, args) -> - return str unless chain - flatten_chain {"chain", str, unpack chain}, args - -- constructor for decorator node wrap_decorator = (stm, dec) -> return stm unless dec @@ -157,5 +153,5 @@ self_assign = (name, pos) -> { :Indent, :Cut, :ensure, :extract_line, :mark, :pos, :flatten_or_mark, :is_assignable, :check_assignable, :format_assign, :format_single_assign, - :sym, :symx, :simple_string, :wrap_func_arg, :join_chain, - :flatten_string_chain, :wrap_decorator, :check_lua_string, :self_assign } + :sym, :symx, :simple_string, :wrap_func_arg, :join_chain, :wrap_decorator, + :check_lua_string, :self_assign } diff --git a/moonscript/transform.lua b/moonscript/transform.lua index 922bef9..c95f14d 100644 --- a/moonscript/transform.lua +++ b/moonscript/transform.lua @@ -1,1573 +1,4 @@ -local types = require("moonscript.types") -local util = require("moonscript.util") -local data = require("moonscript.data") -local reversed, unpack -reversed, unpack = util.reversed, util.unpack -local ntype, mtype, build, smart_node, is_slice, value_is_singular -ntype, mtype, build, smart_node, is_slice, value_is_singular = types.ntype, types.mtype, types.build, types.smart_node, types.is_slice, types.value_is_singular -local insert -insert = table.insert -local NameProxy, LocalName -do - local _obj_0 = require("moonscript.transform.names") - NameProxy, LocalName = _obj_0.NameProxy, _obj_0.LocalName -end -local Run, transform_last_stm, last_stm, chain_is_stub -do - local _obj_0 = require("moonscript.transform.statements") - Run, transform_last_stm, last_stm, chain_is_stub = _obj_0.Run, _obj_0.transform_last_stm, _obj_0.last_stm, _obj_0.chain_is_stub -end -local Transformer -Transformer = require("moonscript.transform.transformer").Transformer -local destructure = require("moonscript.transform.destructure") -local NOOP = { - "noop" -} -local is_singular, extract_declarations, expand_elseif_assign, constructor_name, with_continue_listener, construct_comprehension, Statement, Accumulator, default_accumulator, implicitly_return, Value -is_singular = function(body) - if #body ~= 1 then - return false - end - if "group" == ntype(body) then - return is_singular(body[2]) - else - return body[1] - end -end -extract_declarations = function(self, body, start, out) - if body == nil then - body = self.current_stms - end - if start == nil then - start = self.current_stm_i + 1 - end - if out == nil then - out = { } - end - for i = start, #body do - local _continue_0 = false - repeat - local stm = body[i] - if stm == nil then - _continue_0 = true - break - end - stm = self.transform.statement(stm) - body[i] = stm - local _exp_0 = stm[1] - if "assign" == _exp_0 or "declare" == _exp_0 then - local _list_0 = stm[2] - for _index_0 = 1, #_list_0 do - local name = _list_0[_index_0] - if ntype(name) == "ref" then - insert(out, name) - elseif type(name) == "string" then - insert(out, name) - end - end - elseif "group" == _exp_0 then - extract_declarations(self, stm[2], 1, out) - end - _continue_0 = true - until true - if not _continue_0 then - break - end - end - return out -end -expand_elseif_assign = function(ifstm) - for i = 4, #ifstm do - local case = ifstm[i] - if ntype(case) == "elseif" and ntype(case[2]) == "assign" then - local split = { - unpack(ifstm, 1, i - 1) - } - insert(split, { - "else", - { - { - "if", - case[2], - case[3], - unpack(ifstm, i + 1) - } - } - }) - return split - end - end - return ifstm -end -constructor_name = "new" -with_continue_listener = function(body) - local continue_name = nil - return { - Run(function(self) - return self:listen("continue", function() - if not (continue_name) then - continue_name = NameProxy("continue") - self:put_name(continue_name) - end - return continue_name - end) - end), - build.group(body), - Run(function(self) - if not (continue_name) then - return - end - local last = last_stm(body) - local enclose_lines = types.terminating[last and ntype(last)] - self:put_name(continue_name, nil) - return self:splice(function(lines) - if enclose_lines then - lines = { - "do", - { - lines - } - } - end - return { - { - "assign", - { - continue_name - }, - { - "false" - } - }, - { - "repeat", - "true", - { - lines, - { - "assign", - { - continue_name - }, - { - "true" - } - } - } - }, - { - "if", - { - "not", - continue_name - }, - { - { - "break" - } - } - } - } - end) - end) - } -end -construct_comprehension = function(inner, clauses) - local current_stms = inner - for _, clause in reversed(clauses) do - local t = clause[1] - local _exp_0 = t - if "for" == _exp_0 then - local name, bounds - _, name, bounds = clause[1], clause[2], clause[3] - current_stms = { - "for", - name, - bounds, - current_stms - } - elseif "foreach" == _exp_0 then - local names, iter - _, names, iter = clause[1], clause[2], clause[3] - current_stms = { - "foreach", - names, - { - iter - }, - current_stms - } - elseif "when" == _exp_0 then - local cond - _, cond = clause[1], clause[2] - current_stms = { - "if", - cond, - current_stms - } - else - current_stms = error("Unknown comprehension clause: " .. t) - end - current_stms = { - current_stms - } - end - return current_stms[1] -end -Statement = Transformer({ - transform = function(self, tuple) - local _, node, fn - _, node, fn = tuple[1], tuple[2], tuple[3] - return fn(node) - end, - root_stms = function(self, body) - return transform_last_stm(body, implicitly_return(self)) - end, - ["return"] = function(self, node) - local ret_val = node[2] - local ret_val_type = ntype(ret_val) - if ret_val_type == "explist" and #ret_val == 2 then - ret_val = ret_val[2] - ret_val_type = ntype(ret_val) - end - if types.cascading[ret_val_type] then - return implicitly_return(self)(ret_val) - end - if ret_val_type == "chain" or ret_val_type == "comprehension" or ret_val_type == "tblcomprehension" then - ret_val = Value:transform_once(self, ret_val) - if ntype(ret_val) == "block_exp" then - return build.group(transform_last_stm(ret_val[2], function(stm) - return { - "return", - stm - } - end)) - end - end - node[2] = ret_val - return node - end, - declare_glob = function(self, node) - local names = extract_declarations(self) - if node[2] == "^" then - do - local _accum_0 = { } - local _len_0 = 1 - for _index_0 = 1, #names do - local _continue_0 = false - repeat - local name = names[_index_0] - if not (name[2]:match("^%u")) then - _continue_0 = true - break - end - local _value_0 = name - _accum_0[_len_0] = _value_0 - _len_0 = _len_0 + 1 - _continue_0 = true - until true - if not _continue_0 then - break - end - end - names = _accum_0 - end - end - return { - "declare", - names - } - end, - assign = function(self, node) - local names, values = unpack(node, 2) - local num_values = #values - local num_names = #values - if num_names == 1 and num_values == 1 then - local first_value = values[1] - local first_name = names[1] - local first_type = ntype(first_value) - if first_type == "chain" then - first_value = Value:transform_once(self, first_value) - first_type = ntype(first_value) - end - local _exp_0 = ntype(first_value) - if "block_exp" == _exp_0 then - local block_body = first_value[2] - local idx = #block_body - block_body[idx] = build.assign_one(first_name, block_body[idx]) - return build.group({ - { - "declare", - { - first_name - } - }, - { - "do", - block_body - } - }) - elseif "comprehension" == _exp_0 or "tblcomprehension" == _exp_0 or "foreach" == _exp_0 or "for" == _exp_0 or "while" == _exp_0 then - return build.assign_one(first_name, Value:transform_once(self, first_value)) - else - values[1] = first_value - end - end - local transformed - if num_values == 1 then - local value = values[1] - local t = ntype(value) - if t == "decorated" then - value = self.transform.statement(value) - t = ntype(value) - end - if types.cascading[t] then - local ret - ret = function(stm) - if types.is_value(stm) then - return { - "assign", - names, - { - stm - } - } - else - return stm - end - end - transformed = build.group({ - { - "declare", - names - }, - self.transform.statement(value, ret, node) - }) - end - end - node = transformed or node - if destructure.has_destructure(names) then - return destructure.split_assign(self, node) - end - return node - end, - continue = function(self, node) - local continue_name = self:send("continue") - if not (continue_name) then - error("continue must be inside of a loop") - end - return build.group({ - build.assign_one(continue_name, "true"), - { - "break" - } - }) - end, - export = function(self, node) - if #node > 2 then - if node[2] == "class" then - local cls = smart_node(node[3]) - return build.group({ - { - "export", - { - cls.name - } - }, - cls - }) - else - return build.group({ - { - "export", - node[2] - }, - build.assign({ - names = node[2], - values = node[3] - }) - }) - end - else - return nil - end - end, - update = function(self, node) - local _, name, op, exp = unpack(node) - local op_final = op:match("^(.+)=$") - if not op_final then - error("Unknown op: " .. op) - end - if not (value_is_singular(exp)) then - exp = { - "parens", - exp - } - end - return build.assign_one(name, { - "exp", - name, - op_final, - exp - }) - end, - import = function(self, node) - local _, names, source = unpack(node) - local table_values - do - local _accum_0 = { } - local _len_0 = 1 - for _index_0 = 1, #names do - local name = names[_index_0] - local dest_name - if ntype(name) == "colon" then - dest_name = name[2] - else - dest_name = name - end - local _value_0 = { - { - "key_literal", - name - }, - dest_name - } - _accum_0[_len_0] = _value_0 - _len_0 = _len_0 + 1 - end - table_values = _accum_0 - end - local dest = { - "table", - table_values - } - return { - "assign", - { - dest - }, - { - source - }, - [-1] = node[-1] - } - end, - comprehension = function(self, node, action) - local _, exp, clauses = unpack(node) - action = action or function(exp) - return { - exp - } - end - return construct_comprehension(action(exp), clauses) - end, - ["do"] = function(self, node, ret) - if ret then - node[2] = transform_last_stm(node[2], ret) - end - return node - end, - decorated = function(self, node) - local stm, dec = unpack(node, 2) - local wrapped - local _exp_0 = dec[1] - if "if" == _exp_0 then - local cond, fail = unpack(dec, 2) - if fail then - fail = { - "else", - { - fail - } - } - end - wrapped = { - "if", - cond, - { - stm - }, - fail - } - elseif "unless" == _exp_0 then - wrapped = { - "unless", - dec[2], - { - stm - } - } - elseif "comprehension" == _exp_0 then - wrapped = { - "comprehension", - stm, - dec[2] - } - else - wrapped = error("Unknown decorator " .. dec[1]) - end - if ntype(stm) == "assign" then - wrapped = build.group({ - build.declare({ - names = (function() - local _accum_0 = { } - local _len_0 = 1 - local _list_0 = stm[2] - for _index_0 = 1, #_list_0 do - local name = _list_0[_index_0] - if ntype(name) == "ref" then - _accum_0[_len_0] = name - _len_0 = _len_0 + 1 - end - end - return _accum_0 - end)() - }), - wrapped - }) - end - return wrapped - end, - unless = function(self, node) - return { - "if", - { - "not", - { - "parens", - node[2] - } - }, - unpack(node, 3) - } - end, - ["if"] = function(self, node, ret) - if ntype(node[2]) == "assign" then - local _, assign, body = unpack(node) - if destructure.has_destructure(assign[2]) then - local name = NameProxy("des") - body = { - destructure.build_assign(self, assign[2][1], name), - build.group(node[3]) - } - return build["do"]({ - build.assign_one(name, assign[3][1]), - { - "if", - name, - body, - unpack(node, 4) - } - }) - else - local name = assign[2][1] - return build["do"]({ - assign, - { - "if", - name, - unpack(node, 3) - } - }) - end - end - node = expand_elseif_assign(node) - if ret then - smart_node(node) - node['then'] = transform_last_stm(node['then'], ret) - for i = 4, #node do - local case = node[i] - local body_idx = #node[i] - case[body_idx] = transform_last_stm(case[body_idx], ret) - end - end - return node - end, - with = function(self, node, ret) - local exp, block = unpack(node, 2) - local copy_scope = true - local scope_name, named_assign - do - local last = last_stm(block) - if last then - if types.terminating[ntype(last)] then - ret = false - end - end - end - if ntype(exp) == "assign" then - local names, values = unpack(exp, 2) - local first_name = names[1] - if ntype(first_name) == "ref" then - scope_name = first_name - named_assign = exp - exp = values[1] - copy_scope = false - else - scope_name = NameProxy("with") - exp = values[1] - values[1] = scope_name - named_assign = { - "assign", - names, - values - } - end - elseif self:is_local(exp) then - scope_name = exp - copy_scope = false - end - scope_name = scope_name or NameProxy("with") - local out = build["do"]({ - copy_scope and build.assign_one(scope_name, exp) or NOOP, - named_assign or NOOP, - Run(function(self) - return self:set("scope_var", scope_name) - end), - unpack(block) - }) - if ret then - table.insert(out[2], ret(scope_name)) - end - return out - end, - foreach = function(self, node, _) - smart_node(node) - local source = unpack(node.iter) - local destructures = { } - do - local _accum_0 = { } - local _len_0 = 1 - for i, name in ipairs(node.names) do - if ntype(name) == "table" then - do - local proxy = NameProxy("des") - insert(destructures, destructure.build_assign(self, name, proxy)) - _accum_0[_len_0] = proxy - end - else - _accum_0[_len_0] = name - end - _len_0 = _len_0 + 1 - end - node.names = _accum_0 - end - if next(destructures) then - insert(destructures, build.group(node.body)) - node.body = destructures - end - if ntype(source) == "unpack" then - local list = source[2] - local index_name = NameProxy("index") - local list_name = self:is_local(list) and list or NameProxy("list") - local slice_var = nil - local bounds - if is_slice(list) then - local slice = list[#list] - table.remove(list) - table.remove(slice, 1) - if self:is_local(list) then - list_name = list - end - if slice[2] and slice[2] ~= "" then - local max_tmp_name = NameProxy("max") - slice_var = build.assign_one(max_tmp_name, slice[2]) - slice[2] = { - "exp", - max_tmp_name, - "<", - 0, - "and", - { - "length", - list_name - }, - "+", - max_tmp_name, - "or", - max_tmp_name - } - else - slice[2] = { - "length", - list_name - } - end - bounds = slice - else - bounds = { - 1, - { - "length", - list_name - } - } - end - return build.group({ - list_name ~= list and build.assign_one(list_name, list) or NOOP, - slice_var or NOOP, - build["for"]({ - name = index_name, - bounds = bounds, - body = { - { - "assign", - node.names, - { - NameProxy.index(list_name, index_name) - } - }, - build.group(node.body) - } - }) - }) - end - node.body = with_continue_listener(node.body) - end, - ["while"] = function(self, node) - smart_node(node) - node.body = with_continue_listener(node.body) - end, - ["for"] = function(self, node) - smart_node(node) - node.body = with_continue_listener(node.body) - end, - switch = function(self, node, ret) - local _, exp, conds = unpack(node) - local exp_name = NameProxy("exp") - local convert_cond - convert_cond = function(cond) - local t, case_exps, body = unpack(cond) - local out = { } - insert(out, t == "case" and "elseif" or "else") - if t ~= "else" then - local cond_exp = { } - for i, case in ipairs(case_exps) do - if i == 1 then - insert(cond_exp, "exp") - else - insert(cond_exp, "or") - end - if not (value_is_singular(case)) then - case = { - "parens", - case - } - end - insert(cond_exp, { - "exp", - case, - "==", - exp_name - }) - end - insert(out, cond_exp) - else - body = case_exps - end - if ret then - body = transform_last_stm(body, ret) - end - insert(out, body) - return out - end - local first = true - local if_stm = { - "if" - } - for _index_0 = 1, #conds do - local cond = conds[_index_0] - local if_cond = convert_cond(cond) - if first then - first = false - insert(if_stm, if_cond[2]) - insert(if_stm, if_cond[3]) - else - insert(if_stm, if_cond) - end - end - return build.group({ - build.assign_one(exp_name, exp), - if_stm - }) - end, - class = function(self, node, ret, parent_assign) - local _, name, parent_val, body = unpack(node) - if parent_val == "" then - parent_val = nil - end - local statements = { } - local properties = { } - for _index_0 = 1, #body do - local item = body[_index_0] - local _exp_0 = item[1] - if "stm" == _exp_0 then - insert(statements, item[2]) - elseif "props" == _exp_0 then - for _index_1 = 2, #item do - local tuple = item[_index_1] - if ntype(tuple[1]) == "self" then - insert(statements, build.assign_one(unpack(tuple))) - else - insert(properties, tuple) - end - end - end - end - local constructor - do - local _accum_0 = { } - local _len_0 = 1 - for _index_0 = 1, #properties do - local _continue_0 = false - repeat - local tuple = properties[_index_0] - local key = tuple[1] - local _value_0 - if key[1] == "key_literal" and key[2] == constructor_name then - constructor = tuple[2] - _continue_0 = true - break - else - _value_0 = tuple - end - _accum_0[_len_0] = _value_0 - _len_0 = _len_0 + 1 - _continue_0 = true - until true - if not _continue_0 then - break - end - end - properties = _accum_0 - end - local parent_cls_name = NameProxy("parent") - local base_name = NameProxy("base") - local self_name = NameProxy("self") - local cls_name = NameProxy("class") - if not (constructor) then - if parent_val then - constructor = build.fndef({ - args = { - { - "..." - } - }, - arrow = "fat", - body = { - build.chain({ - base = "super", - { - "call", - { - "..." - } - } - }) - } - }) - else - constructor = build.fndef() - end - end - local real_name = name or parent_assign and parent_assign[2][1] - local _exp_0 = ntype(real_name) - if "chain" == _exp_0 then - local last = real_name[#real_name] - local _exp_1 = ntype(last) - if "dot" == _exp_1 then - real_name = { - "string", - '"', - last[2] - } - elseif "index" == _exp_1 then - real_name = last[2] - else - real_name = "nil" - end - elseif "nil" == _exp_0 then - real_name = "nil" - else - local name_t = type(real_name) - local flattened_name - if name_t == "string" then - flattened_name = real_name - elseif name_t == "table" and real_name[1] == "ref" then - flattened_name = real_name[2] - else - flattened_name = error("don't know how to extract name from " .. tostring(name_t)) - end - real_name = { - "string", - '"', - flattened_name - } - end - local cls = build.table({ - { - "__init", - constructor - }, - { - "__base", - base_name - }, - { - "__name", - real_name - }, - parent_val and { - "__parent", - parent_cls_name - } or nil - }) - local class_index - if parent_val then - local class_lookup = build["if"]({ - cond = { - "exp", - { - "ref", - "val" - }, - "==", - "nil" - }, - ["then"] = { - build.assign_one(LocalName("parent"), build.chain({ - base = "rawget", - { - "call", - { - { - "ref", - "cls" - }, - { - "string", - '"', - "__parent" - } - } - } - })), - build["if"]({ - cond = LocalName("parent"), - ["then"] = { - build.chain({ - base = LocalName("parent"), - { - "index", - "name" - } - }) - } - }) - } - }) - insert(class_lookup, { - "else", - { - "val" - } - }) - class_index = build.fndef({ - args = { - { - "cls" - }, - { - "name" - } - }, - body = { - build.assign_one(LocalName("val"), build.chain({ - base = "rawget", - { - "call", - { - base_name, - { - "ref", - "name" - } - } - } - })), - class_lookup - } - }) - else - class_index = base_name - end - local cls_mt = build.table({ - { - "__index", - class_index - }, - { - "__call", - build.fndef({ - args = { - { - "cls" - }, - { - "..." - } - }, - body = { - build.assign_one(self_name, build.chain({ - base = "setmetatable", - { - "call", - { - "{}", - base_name - } - } - })), - build.chain({ - base = "cls.__init", - { - "call", - { - self_name, - "..." - } - } - }), - self_name - } - }) - } - }) - cls = build.chain({ - base = "setmetatable", - { - "call", - { - cls, - cls_mt - } - } - }) - local value = nil - do - local out_body = { - Run(function(self) - if name then - self:put_name(name) - end - return self:set("super", function(block, chain) - local relative_parent = { - "chain", - cls_name, - { - "dot", - "__parent" - } - } - if not (chain) then - return relative_parent - end - local chain_tail = { - unpack(chain, 3) - } - local head = chain_tail[1] - if head == nil then - return relative_parent - end - local new_chain = relative_parent - local _exp_1 = head[1] - if "call" == _exp_1 then - local calling_name = block:get("current_block") - assert(calling_name, "missing calling name") - chain_tail[1] = { - "call", - { - "self", - unpack(head[2]) - } - } - if ntype(calling_name) == "key_literal" then - insert(new_chain, { - "dot", - calling_name[2] - }) - else - insert(new_chain, { - "index", - calling_name - }) - end - elseif "colon" == _exp_1 then - local call = chain_tail[2] - if call and call[1] == "call" then - chain_tail[1] = { - "dot", - head[2] - } - chain_tail[2] = { - "call", - { - "self", - unpack(call[2]) - } - } - end - end - for _index_0 = 1, #chain_tail do - local item = chain_tail[_index_0] - insert(new_chain, item) - end - return new_chain - end) - end), - { - "declare", - { - cls_name - } - }, - { - "declare_glob", - "*" - }, - parent_val and build.assign_one(parent_cls_name, parent_val) or NOOP, - build.assign_one(base_name, { - "table", - properties - }), - build.assign_one(base_name:chain("__index"), base_name), - parent_val and build.chain({ - base = "setmetatable", - { - "call", - { - base_name, - build.chain({ - base = parent_cls_name, - { - "dot", - "__base" - } - }) - } - } - }) or NOOP, - build.assign_one(cls_name, cls), - build.assign_one(base_name:chain("__class"), cls_name), - build.group((function() - if #statements > 0 then - return { - build.assign_one(LocalName("self"), cls_name), - build.group(statements) - } - end - end)()), - parent_val and build["if"]({ - cond = { - "exp", - parent_cls_name:chain("__inherited") - }, - ["then"] = { - parent_cls_name:chain("__inherited", { - "call", - { - parent_cls_name, - cls_name - } - }) - } - }) or NOOP, - build.group((function() - if name then - return { - build.assign_one(name, cls_name) - } - end - end)()), - (function() - if ret then - return ret(cls_name) - end - end)() - } - value = build.group({ - build.group((function() - if ntype(name) == "value" then - return { - build.declare({ - names = { - name - } - }) - } - end - end)()), - build["do"](out_body) - }) - end - return value - end -}) -do - local _class_0 - local _base_0 = { - body_idx = { - ["for"] = 4, - ["while"] = 3, - foreach = 4 - }, - convert = function(self, node) - local index = self.body_idx[ntype(node)] - node[index] = self:mutate_body(node[index]) - return self:wrap(node) - end, - wrap = function(self, node, group_type) - if group_type == nil then - group_type = "block_exp" - end - return build[group_type]({ - build.assign_one(self.accum_name, build.table()), - build.assign_one(self.len_name, 1), - node, - group_type == "block_exp" and self.accum_name or NOOP - }) - end, - mutate_body = function(self, body) - local single_stm = is_singular(body) - local val - if single_stm and types.is_value(single_stm) then - body = { } - val = single_stm - else - body = transform_last_stm(body, function(n) - if types.is_value(n) then - return build.assign_one(self.value_name, n) - else - return build.group({ - { - "declare", - { - self.value_name - } - }, - n - }) - end - end) - val = self.value_name - end - local update = { - build.assign_one(NameProxy.index(self.accum_name, self.len_name), val), - { - "update", - self.len_name, - "+=", - 1 - } - } - insert(body, build.group(update)) - return body - end - } - _base_0.__index = _base_0 - _class_0 = setmetatable({ - __init = function(self, accum_name) - self.accum_name = NameProxy("accum") - self.value_name = NameProxy("value") - self.len_name = NameProxy("len") - end, - __base = _base_0, - __name = "Accumulator" - }, { - __index = _base_0, - __call = function(cls, ...) - local _self_0 = setmetatable({}, _base_0) - cls.__init(_self_0, ...) - return _self_0 - end - }) - _base_0.__class = _class_0 - Accumulator = _class_0 -end -default_accumulator = function(self, node) - return Accumulator():convert(node) -end -implicitly_return = function(scope) - local is_top = true - local fn - fn = function(stm) - local t = ntype(stm) - if t == "decorated" then - stm = scope.transform.statement(stm) - t = ntype(stm) - end - if types.cascading[t] then - is_top = false - return scope.transform.statement(stm, fn) - elseif types.manual_return[t] or not types.is_value(stm) then - if is_top and t == "return" and stm[2] == "" then - return NOOP - else - return stm - end - else - if t == "comprehension" and not types.comprehension_has_value(stm) then - return stm - else - return { - "return", - stm - } - end - end - end - return fn -end -Value = Transformer({ - ["for"] = default_accumulator, - ["while"] = default_accumulator, - foreach = default_accumulator, - ["do"] = function(self, node) - return build.block_exp(node[2]) - end, - decorated = function(self, node) - return self.transform.statement(node) - end, - class = function(self, node) - return build.block_exp({ - node - }) - end, - string = function(self, node) - local delim = node[2] - local convert_part - convert_part = function(part) - if type(part) == "string" or part == nil then - return { - "string", - delim, - part or "" - } - else - return build.chain({ - base = "tostring", - { - "call", - { - part[2] - } - } - }) - end - end - if #node <= 3 then - if type(node[3]) == "string" then - return node - else - return convert_part(node[3]) - end - end - local e = { - "exp", - convert_part(node[3]) - } - for i = 4, #node do - insert(e, "..") - insert(e, convert_part(node[i])) - end - return e - end, - comprehension = function(self, node) - local a = Accumulator() - node = self.transform.statement(node, function(exp) - return a:mutate_body({ - exp - }) - end) - return a:wrap(node) - end, - tblcomprehension = function(self, node) - local _, explist, clauses = unpack(node) - local key_exp, value_exp = unpack(explist) - local accum = NameProxy("tbl") - local inner - if value_exp then - local dest = build.chain({ - base = accum, - { - "index", - key_exp - } - }) - inner = { - build.assign_one(dest, value_exp) - } - else - local key_name, val_name = NameProxy("key"), NameProxy("val") - local dest = build.chain({ - base = accum, - { - "index", - key_name - } - }) - inner = { - build.assign({ - names = { - key_name, - val_name - }, - values = { - key_exp - } - }), - build.assign_one(dest, val_name) - } - end - return build.block_exp({ - build.assign_one(accum, build.table()), - construct_comprehension(inner, clauses), - accum - }) - end, - fndef = function(self, node) - smart_node(node) - node.body = transform_last_stm(node.body, implicitly_return(self)) - node.body = { - Run(function(self) - return self:listen("varargs", function() end) - end), - unpack(node.body) - } - return node - end, - ["if"] = function(self, node) - return build.block_exp({ - node - }) - end, - unless = function(self, node) - return build.block_exp({ - node - }) - end, - with = function(self, node) - return build.block_exp({ - node - }) - end, - switch = function(self, node) - return build.block_exp({ - node - }) - end, - chain = function(self, node) - for i = 2, #node do - local part = node[i] - if ntype(part) == "dot" and data.lua_keywords[part[2]] then - node[i] = { - "index", - { - "string", - '"', - part[2] - } - } - end - end - if ntype(node[2]) == "string" then - node[2] = { - "parens", - node[2] - } - end - if chain_is_stub(node) then - local base_name = NameProxy("base") - local fn_name = NameProxy("fn") - local colon = table.remove(node) - local is_super = ntype(node[2]) == "ref" and node[2][2] == "super" - return build.block_exp({ - build.assign({ - names = { - base_name - }, - values = { - node - } - }), - build.assign({ - names = { - fn_name - }, - values = { - build.chain({ - base = base_name, - { - "dot", - colon[2] - } - }) - } - }), - build.fndef({ - args = { - { - "..." - } - }, - body = { - build.chain({ - base = fn_name, - { - "call", - { - is_super and "self" or base_name, - "..." - } - } - }) - } - }) - }) - end - end, - block_exp = function(self, node) - local _, body = unpack(node) - local fn = nil - local arg_list = { } - fn = smart_node(build.fndef({ - body = { - Run(function(self) - return self:listen("varargs", function() - insert(arg_list, "...") - insert(fn.args, { - "..." - }) - return self:unlisten("varargs") - end) - end), - unpack(body) - } - })) - return build.chain({ - base = { - "parens", - fn - }, - { - "call", - arg_list - } - }) - end -}) return { - Statement = Statement, - Value = Value, - Run = Run + Statement = require("moonscript.transform.statement"), + Value = require("moonscript.transform.value") } diff --git a/moonscript/transform.moon b/moonscript/transform.moon index 27127d2..29a93ad 100644 --- a/moonscript/transform.moon +++ b/moonscript/transform.moon @@ -1,977 +1,5 @@ -types = require "moonscript.types" -util = require "moonscript.util" -data = require "moonscript.data" - -import reversed, unpack from util -import ntype, mtype, build, smart_node, is_slice, value_is_singular from types -import insert from table - -import NameProxy, LocalName from require "moonscript.transform.names" -import Run, transform_last_stm, last_stm, chain_is_stub from require "moonscript.transform.statements" - -import Transformer from require "moonscript.transform.transformer" - -destructure = require "moonscript.transform.destructure" -NOOP = {"noop"} - -local * - --- is a body a sindle expression/statement -is_singular = (body) -> - return false if #body != 1 - if "group" == ntype body - is_singular body[2] - else - body[1] - --- this mutates body searching for assigns -extract_declarations = (body=@current_stms, start=@current_stm_i + 1, out={}) => - for i=start,#body - stm = body[i] - continue if stm == nil - stm = @transform.statement stm - body[i] = stm - switch stm[1] - when "assign", "declare" - for name in *stm[2] - if ntype(name) == "ref" - insert out, name - elseif type(name) == "string" - -- TODO: don't use string literal as ref - insert out, name - when "group" - extract_declarations @, stm[2], 1, out - out - -expand_elseif_assign = (ifstm) -> - for i = 4, #ifstm - case = ifstm[i] - if ntype(case) == "elseif" and ntype(case[2]) == "assign" - split = { unpack ifstm, 1, i - 1 } - insert split, { - "else", { - {"if", case[2], case[3], unpack ifstm, i + 1} - } - } - return split - - ifstm - -constructor_name = "new" - -with_continue_listener = (body) -> - continue_name = nil - - { - Run => - @listen "continue", -> - unless continue_name - continue_name = NameProxy"continue" - @put_name continue_name - continue_name - - build.group body - - Run => - return unless continue_name - last = last_stm body - enclose_lines = types.terminating[last and ntype(last)] - - @put_name continue_name, nil - @splice (lines) -> - lines = {"do", {lines}} if enclose_lines - - { - {"assign", {continue_name}, {"false"}} - {"repeat", "true", { - lines - {"assign", {continue_name}, {"true"}} - }} - {"if", {"not", continue_name}, { - {"break"} - }} - } - } - - -construct_comprehension = (inner, clauses) -> - current_stms = inner - for _, clause in reversed clauses - t = clause[1] - current_stms = switch t - when "for" - {_, name, bounds} = clause - {"for", name, bounds, current_stms} - when "foreach" - {_, names, iter} = clause - {"foreach", names, {iter}, current_stms} - when "when" - {_, cond} = clause - {"if", cond, current_stms} - else - error "Unknown comprehension clause: "..t - - current_stms = {current_stms} - - current_stms[1] - -Statement = Transformer { - transform: (tuple) => - {_, node, fn} = tuple - fn node - - root_stms: (body) => - transform_last_stm body, implicitly_return @ - - return: (node) => - ret_val = node[2] - ret_val_type = ntype ret_val - - if ret_val_type == "explist" and #ret_val == 2 - ret_val = ret_val[2] - ret_val_type = ntype ret_val - - if types.cascading[ret_val_type] - return implicitly_return(@) ret_val - - -- flatten things that create block exp - if ret_val_type == "chain" or ret_val_type == "comprehension" or ret_val_type == "tblcomprehension" - ret_val = Value\transform_once @, ret_val - if ntype(ret_val) == "block_exp" - return build.group transform_last_stm ret_val[2], (stm)-> - {"return", stm} - - node[2] = ret_val - node - - declare_glob: (node) => - names = extract_declarations @ - - if node[2] == "^" - names = for name in *names - continue unless name[2]\match "^%u" - name - - {"declare", names} - - assign: (node) => - names, values = unpack node, 2 - - num_values = #values - num_names = #values - - -- special code simplifications for single assigns - if num_names == 1 and num_values == 1 - first_value = values[1] - first_name = names[1] - first_type = ntype first_value - - -- reduce colon stub chain to block exp - if first_type == "chain" - first_value = Value\transform_once @, first_value - first_type = ntype first_value - - switch ntype first_value - when "block_exp" - block_body = first_value[2] - idx = #block_body - block_body[idx] = build.assign_one first_name, block_body[idx] - - return build.group { - {"declare", {first_name}} - {"do", block_body} - } - - when "comprehension", "tblcomprehension", "foreach", "for", "while" - return build.assign_one first_name, Value\transform_once @, first_value - else - values[1] = first_value - - -- bubble cascading assigns - transformed = if num_values == 1 - value = values[1] - t = ntype value - - if t == "decorated" - value = @transform.statement value - t = ntype value - - if types.cascading[t] - ret = (stm) -> - if types.is_value stm - {"assign", names, {stm}} - else - stm - - build.group { - {"declare", names} - @transform.statement value, ret, node - } - - node = transformed or node - - if destructure.has_destructure names - return destructure.split_assign @, node - - node - - continue: (node) => - continue_name = @send "continue" - error "continue must be inside of a loop" unless continue_name - build.group { - build.assign_one continue_name, "true" - {"break"} - } - - export: (node) => - -- assign values if they are included - if #node > 2 - if node[2] == "class" - cls = smart_node node[3] - build.group { - {"export", {cls.name}} - cls - } - else - -- pull out vawlues and assign them after the export - build.group { - { "export", node[2] } - build.assign { - names: node[2] - values: node[3] - } - } - else - nil - - update: (node) => - _, name, op, exp = unpack node - op_final = op\match "^(.+)=$" - error "Unknown op: "..op if not op_final - exp = {"parens", exp} unless value_is_singular exp - build.assign_one name, {"exp", name, op_final, exp} - - import: (node) => - _, names, source = unpack node - table_values = for name in *names - dest_name = if ntype(name) == "colon" - name[2] - else - name - - {{"key_literal", name}, dest_name} - - dest = { "table", table_values } - { "assign", {dest}, {source}, [-1]: node[-1] } - - comprehension: (node, action) => - _, exp, clauses = unpack node - - action = action or (exp) -> {exp} - construct_comprehension action(exp), clauses - - do: (node, ret) => - node[2] = transform_last_stm node[2], ret if ret - node - - decorated: (node) => - stm, dec = unpack node, 2 - - wrapped = switch dec[1] - when "if" - cond, fail = unpack dec, 2 - fail = { "else", { fail } } if fail - { "if", cond, { stm }, fail } - when "unless" - { "unless", dec[2], { stm } } - when "comprehension" - { "comprehension", stm, dec[2] } - else - error "Unknown decorator " .. dec[1] - - if ntype(stm) == "assign" - wrapped = build.group { - build.declare names: [name for name in *stm[2] when ntype(name) == "ref"] - wrapped - } - - wrapped - - unless: (node) => - { "if", {"not", {"parens", node[2]}}, unpack node, 3 } - - if: (node, ret) => - -- expand assign in cond - if ntype(node[2]) == "assign" - _, assign, body = unpack node - if destructure.has_destructure assign[2] - name = NameProxy "des" - - body = { - destructure.build_assign @, assign[2][1], name - build.group node[3] - } - - return build.do { - build.assign_one name, assign[3][1] - {"if", name, body, unpack node, 4} - } - else - name = assign[2][1] - return build["do"] { - assign - {"if", name, unpack node, 3} - } - - node = expand_elseif_assign node - - -- apply cascading return decorator - if ret - smart_node node - -- mutate all the bodies - node['then'] = transform_last_stm node['then'], ret - for i = 4, #node - case = node[i] - body_idx = #node[i] - case[body_idx] = transform_last_stm case[body_idx], ret - - node - - with: (node, ret) => - exp, block = unpack node, 2 - - copy_scope = true - local scope_name, named_assign - - if last = last_stm block - ret = false if types.terminating[ntype(last)] - - if ntype(exp) == "assign" - names, values = unpack exp, 2 - first_name = names[1] - - if ntype(first_name) == "ref" - scope_name = first_name - named_assign = exp - exp = values[1] - copy_scope = false - else - scope_name = NameProxy "with" - exp = values[1] - values[1] = scope_name - named_assign = {"assign", names, values} - - elseif @is_local exp - scope_name = exp - copy_scope = false - - scope_name or= NameProxy "with" - - out = build.do { - copy_scope and build.assign_one(scope_name, exp) or NOOP - named_assign or NOOP - Run => @set "scope_var", scope_name - unpack block - } - - if ret - table.insert out[2], ret scope_name - - out - - foreach: (node, _) => - smart_node node - source = unpack node.iter - - destructures = {} - node.names = for i, name in ipairs node.names - if ntype(name) == "table" - with proxy = NameProxy "des" - insert destructures, destructure.build_assign @, name, proxy - else - name - - if next destructures - insert destructures, build.group node.body - node.body = destructures - - if ntype(source) == "unpack" - list = source[2] - - index_name = NameProxy "index" - - list_name = @is_local(list) and list or NameProxy "list" - - slice_var = nil - bounds = if is_slice list - slice = list[#list] - table.remove list - table.remove slice, 1 - - list_name = list if @is_local list - - slice[2] = if slice[2] and slice[2] != "" - max_tmp_name = NameProxy "max" - slice_var = build.assign_one max_tmp_name, slice[2] - {"exp", max_tmp_name, "<", 0 - "and", {"length", list_name}, "+", max_tmp_name - "or", max_tmp_name } - else - {"length", list_name} - - slice - else - {1, {"length", list_name}} - - return build.group { - list_name != list and build.assign_one(list_name, list) or NOOP - slice_var or NOOP - build["for"] { - name: index_name - bounds: bounds - body: { - {"assign", node.names, { NameProxy.index list_name, index_name }} - build.group node.body - } - } - } - - node.body = with_continue_listener node.body - - while: (node) => - smart_node node - node.body = with_continue_listener node.body - - for: (node) => - smart_node node - node.body = with_continue_listener node.body - - switch: (node, ret) => - _, exp, conds = unpack node - exp_name = NameProxy "exp" - - -- convert switch conds into if statment conds - convert_cond = (cond) -> - t, case_exps, body = unpack cond - out = {} - insert out, t == "case" and "elseif" or "else" - if t != "else" - cond_exp = {} - for i, case in ipairs case_exps - if i == 1 - insert cond_exp, "exp" - else - insert cond_exp, "or" - - case = {"parens", case} unless value_is_singular case - insert cond_exp, {"exp", case, "==", exp_name} - - insert out, cond_exp - else - body = case_exps - - if ret - body = transform_last_stm body, ret - - insert out, body - - out - - first = true - if_stm = {"if"} - for cond in *conds - if_cond = convert_cond cond - if first - first = false - insert if_stm, if_cond[2] - insert if_stm, if_cond[3] - else - insert if_stm, if_cond - - build.group { - build.assign_one exp_name, exp - if_stm - } - - class: (node, ret, parent_assign) => - _, name, parent_val, body = unpack node - parent_val = nil if parent_val == "" - - -- split apart properties and statements - statements = {} - properties = {} - for item in *body - switch item[1] - when "stm" - insert statements, item[2] - when "props" - for tuple in *item[2,] - if ntype(tuple[1]) == "self" - insert statements, build.assign_one unpack tuple - else - insert properties, tuple - - -- find constructor - local constructor - properties = for tuple in *properties - key = tuple[1] - if key[1] == "key_literal" and key[2] == constructor_name - constructor = tuple[2] - continue - else - tuple - - parent_cls_name = NameProxy "parent" - base_name = NameProxy "base" - self_name = NameProxy "self" - cls_name = NameProxy "class" - - unless constructor - constructor = if parent_val - build.fndef { - args: {{"..."}} - arrow: "fat" - body: { - build.chain { base: "super", {"call", {"..."}} } - } - } - else - build.fndef! - - real_name = name or parent_assign and parent_assign[2][1] - real_name = switch ntype real_name - when "chain" - last = real_name[#real_name] - switch ntype last - when "dot" - {"string", '"', last[2]} - when "index" - last[2] - else - "nil" - when "nil" - "nil" - else - name_t = type real_name - -- TODO: don't use string literal as ref - flattened_name = if name_t == "string" - real_name - elseif name_t == "table" and real_name[1] == "ref" - real_name[2] - else - error "don't know how to extract name from #{name_t}" - - {"string", '"', flattened_name} - - cls = build.table { - {"__init", constructor} - {"__base", base_name} - {"__name", real_name} -- "quote the string" - parent_val and {"__parent", parent_cls_name} or nil - } - - -- looking up a name in the class object - class_index = if parent_val - class_lookup = build["if"] { - cond: { "exp", {"ref", "val"}, "==", "nil" } - then: { - build.assign_one LocalName"parent", build.chain { - base: "rawget" - { - "call", { - {"ref", "cls"} - {"string", '"', "__parent"} - } - } - } - - build.if { - cond: LocalName "parent" - then: { - build.chain { - base: LocalName "parent" - {"index", "name"} - } - } - } - } - } - insert class_lookup, {"else", {"val"}} - - build.fndef { - args: {{"cls"}, {"name"}} - body: { - build.assign_one LocalName"val", build.chain { - base: "rawget", {"call", {base_name, {"ref", "name"}}} - } - class_lookup - } - } - else - base_name - - cls_mt = build.table { - {"__index", class_index} - {"__call", build.fndef { - args: {{"cls"}, {"..."}} - body: { - build.assign_one self_name, build.chain { - base: "setmetatable" - {"call", {"{}", base_name}} - } - build.chain { - base: "cls.__init" - {"call", {self_name, "..."}} - } - self_name - } - }} - } - - cls = build.chain { - base: "setmetatable" - {"call", {cls, cls_mt}} - } - - value = nil - with build - out_body = { - Run => - -- make sure we don't assign the class to a local inside the do - @put_name name if name - - @set "super", (block, chain) -> - relative_parent = { - "chain", - cls_name - {"dot", "__parent"} - } - - return relative_parent unless chain - - chain_tail = { unpack chain, 3 } - head = chain_tail[1] - - if head == nil - return relative_parent - - new_chain = relative_parent - - switch head[1] - -- calling super, inject calling name and self into chain - when "call" - calling_name = block\get"current_block" - assert calling_name, "missing calling name" - chain_tail[1] = {"call", {"self", unpack head[2]}} - - if ntype(calling_name) == "key_literal" - insert new_chain, {"dot", calling_name[2]} - else - insert new_chain, {"index", calling_name} - - -- colon call on super, replace class with self as first arg - when "colon" - call = chain_tail[2] - -- calling chain tail - if call and call[1] == "call" - chain_tail[1] = { - "dot" - head[2] - } - - chain_tail[2] = { - "call" - { - "self" - unpack call[2] - } - } - - insert new_chain, item for item in *chain_tail - new_chain - - {"declare", { cls_name }} - {"declare_glob", "*"} - - parent_val and .assign_one(parent_cls_name, parent_val) or NOOP - - .assign_one base_name, {"table", properties} - .assign_one base_name\chain"__index", base_name - - parent_val and .chain({ - base: "setmetatable" - {"call", { - base_name, - .chain { base: parent_cls_name, {"dot", "__base"}} - }} - }) or NOOP - - .assign_one cls_name, cls - .assign_one base_name\chain"__class", cls_name - - .group if #statements > 0 then { - .assign_one LocalName"self", cls_name - .group statements - } - - -- run the inherited callback - parent_val and .if({ - cond: {"exp", parent_cls_name\chain "__inherited" } - then: { - parent_cls_name\chain "__inherited", {"call", { - parent_cls_name, cls_name - }} - } - }) or NOOP - - .group if name then { - .assign_one name, cls_name - } - - if ret - ret cls_name - } - - value = .group { - .group if ntype(name) == "value" then { - .declare names: {name} - } - - .do out_body - } - - value +{ + Statement: require "moonscript.transform.statement" + Value: require "moonscript.transform.value" } - -class Accumulator - body_idx: { for: 4, while: 3, foreach: 4 } - - new: (accum_name) => - @accum_name = NameProxy "accum" - @value_name = NameProxy "value" - @len_name = NameProxy "len" - - -- wraps node and mutates body - convert: (node) => - index = @body_idx[ntype node] - node[index] = @mutate_body node[index] - @wrap node - - -- wrap the node into a block_exp - wrap: (node, group_type="block_exp") => - build[group_type] { - build.assign_one @accum_name, build.table! - build.assign_one @len_name, 1 - node - group_type == "block_exp" and @accum_name or NOOP - } - - -- mutates the body of a loop construct to save last value into accumulator - mutate_body: (body) => - -- shortcut to write simpler code if body is a single expression - single_stm = is_singular body - val = if single_stm and types.is_value single_stm - body = {} - single_stm - else - body = transform_last_stm body, (n) -> - if types.is_value n - build.assign_one @value_name, n - else - -- just ignore it - build.group { - {"declare", {@value_name}} - n - } - @value_name - - update = { - build.assign_one NameProxy.index(@accum_name, @len_name), val - {"update", @len_name, "+=", 1} - } - - insert body, build.group update - body - -default_accumulator = (node) => - Accumulator!\convert node - -implicitly_return = (scope) -> - is_top = true - fn = (stm) -> - t = ntype stm - - -- expand decorated - if t == "decorated" - stm = scope.transform.statement stm - t = ntype stm - - if types.cascading[t] - is_top = false - scope.transform.statement stm, fn - elseif types.manual_return[t] or not types.is_value stm - -- remove blank return statement - if is_top and t == "return" and stm[2] == "" - NOOP - else - stm - else - if t == "comprehension" and not types.comprehension_has_value stm - stm - else - {"return", stm} - - fn - -Value = Transformer { - for: default_accumulator - while: default_accumulator - foreach: default_accumulator - - do: (node) => - build.block_exp node[2] - - decorated: (node) => - @transform.statement node - - class: (node) => - build.block_exp { node } - - string: (node) => - delim = node[2] - - convert_part = (part) -> - if type(part) == "string" or part == nil - {"string", delim, part or ""} - else - build.chain { base: "tostring", {"call", {part[2]}} } - - -- reduced to single item - if #node <= 3 - return if type(node[3]) == "string" - node - else - convert_part node[3] - - e = {"exp", convert_part node[3]} - - for i=4, #node - insert e, ".." - insert e, convert_part node[i] - e - - comprehension: (node) => - a = Accumulator! - node = @transform.statement node, (exp) -> - a\mutate_body {exp} - a\wrap node - - tblcomprehension: (node) => - _, explist, clauses = unpack node - key_exp, value_exp = unpack explist - - accum = NameProxy "tbl" - - inner = if value_exp - dest = build.chain { base: accum, {"index", key_exp} } - { build.assign_one dest, value_exp } - else - -- If we only have single expression then - -- unpack the result into key and value - key_name, val_name = NameProxy"key", NameProxy"val" - dest = build.chain { base: accum, {"index", key_name} } - { - build.assign names: {key_name, val_name}, values: {key_exp} - build.assign_one dest, val_name - } - - build.block_exp { - build.assign_one accum, build.table! - construct_comprehension inner, clauses - accum - } - - fndef: (node) => - smart_node node - node.body = transform_last_stm node.body, implicitly_return self - node.body = { - Run => @listen "varargs", -> -- capture event - unpack node.body - } - - node - - if: (node) => - build.block_exp { node } - - unless: (node) => - build.block_exp { node } - - with: (node) => - build.block_exp { node } - - switch: (node) => - build.block_exp { node } - - -- pull out colon chain - chain: (node) => - -- escape lua keywords used in dot accessors - for i=2,#node - part = node[i] - if ntype(part) == "dot" and data.lua_keywords[part[2]] - node[i] = { "index", {"string", '"', part[2]} } - - if ntype(node[2]) == "string" - -- add parens if callee is raw string - node[2] = {"parens", node[2] } - - if chain_is_stub node - base_name = NameProxy "base" - fn_name = NameProxy "fn" - colon = table.remove node - - is_super = ntype(node[2]) == "ref" and node[2][2] == "super" - build.block_exp { - build.assign { - names: {base_name} - values: {node} - } - - build.assign { - names: {fn_name} - values: { - build.chain { base: base_name, {"dot", colon[2]} } - } - } - - build.fndef { - args: {{"..."}} - body: { - build.chain { - base: fn_name, {"call", {is_super and "self" or base_name, "..."}} - } - } - } - } - - block_exp: (node) => - _, body = unpack node - - fn = nil - arg_list = {} - - fn = smart_node build.fndef body: { - Run => - @listen "varargs", -> - insert arg_list, "..." - insert fn.args, {"..."} - @unlisten "varargs" - - unpack body - } - - build.chain { base: {"parens", fn}, {"call", arg_list} } -} - -{ :Statement, :Value, :Run } diff --git a/moonscript/transform/accumulator.lua b/moonscript/transform/accumulator.lua new file mode 100644 index 0000000..539c65f --- /dev/null +++ b/moonscript/transform/accumulator.lua @@ -0,0 +1,110 @@ +local types = require("moonscript.types") +local build, ntype, NOOP +build, ntype, NOOP = types.build, types.ntype, types.NOOP +local NameProxy +NameProxy = require("moonscript.transform.names").NameProxy +local insert +insert = table.insert +local is_singular +is_singular = function(body) + if #body ~= 1 then + return false + end + if "group" == ntype(body) then + return is_singular(body[2]) + else + return body[1] + end +end +local transform_last_stm +transform_last_stm = require("moonscript.transform.statements").transform_last_stm +local Accumulator +do + local _class_0 + local _base_0 = { + body_idx = { + ["for"] = 4, + ["while"] = 3, + foreach = 4 + }, + convert = function(self, node) + local index = self.body_idx[ntype(node)] + node[index] = self:mutate_body(node[index]) + return self:wrap(node) + end, + wrap = function(self, node, group_type) + if group_type == nil then + group_type = "block_exp" + end + return build[group_type]({ + build.assign_one(self.accum_name, build.table()), + build.assign_one(self.len_name, 1), + node, + group_type == "block_exp" and self.accum_name or NOOP + }) + end, + mutate_body = function(self, body) + local single_stm = is_singular(body) + local val + if single_stm and types.is_value(single_stm) then + body = { } + val = single_stm + else + body = transform_last_stm(body, function(n) + if types.is_value(n) then + return build.assign_one(self.value_name, n) + else + return build.group({ + { + "declare", + { + self.value_name + } + }, + n + }) + end + end) + val = self.value_name + end + local update = { + build.assign_one(NameProxy.index(self.accum_name, self.len_name), val), + { + "update", + self.len_name, + "+=", + 1 + } + } + insert(body, build.group(update)) + return body + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, accum_name) + self.accum_name = NameProxy("accum") + self.value_name = NameProxy("value") + self.len_name = NameProxy("len") + end, + __base = _base_0, + __name = "Accumulator" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + Accumulator = _class_0 +end +local default_accumulator +default_accumulator = function(self, node) + return Accumulator():convert(node) +end +return { + Accumulator = Accumulator, + default_accumulator = default_accumulator +} diff --git a/moonscript/transform/accumulator.moon b/moonscript/transform/accumulator.moon new file mode 100644 index 0000000..62ff015 --- /dev/null +++ b/moonscript/transform/accumulator.moon @@ -0,0 +1,71 @@ +types = require "moonscript.types" + +import build, ntype, NOOP from types +import NameProxy from require "moonscript.transform.names" + +import insert from table + +-- is a body a single expression/statement +is_singular = (body) -> + return false if #body != 1 + if "group" == ntype body + is_singular body[2] + else + body[1] + +import transform_last_stm from require "moonscript.transform.statements" + +class Accumulator + body_idx: { for: 4, while: 3, foreach: 4 } + + new: (accum_name) => + @accum_name = NameProxy "accum" + @value_name = NameProxy "value" + @len_name = NameProxy "len" + + -- wraps node and mutates body + convert: (node) => + index = @body_idx[ntype node] + node[index] = @mutate_body node[index] + @wrap node + + -- wrap the node into a block_exp + wrap: (node, group_type="block_exp") => + build[group_type] { + build.assign_one @accum_name, build.table! + build.assign_one @len_name, 1 + node + group_type == "block_exp" and @accum_name or NOOP + } + + -- mutates the body of a loop construct to save last value into accumulator + mutate_body: (body) => + -- shortcut to write simpler code if body is a single expression + single_stm = is_singular body + val = if single_stm and types.is_value single_stm + body = {} + single_stm + else + body = transform_last_stm body, (n) -> + if types.is_value n + build.assign_one @value_name, n + else + -- just ignore it + build.group { + {"declare", {@value_name}} + n + } + @value_name + + update = { + build.assign_one NameProxy.index(@accum_name, @len_name), val + {"update", @len_name, "+=", 1} + } + + insert body, build.group update + body + +default_accumulator = (node) => + Accumulator!\convert node + +{ :Accumulator, :default_accumulator } diff --git a/moonscript/transform/statement.lua b/moonscript/transform/statement.lua new file mode 100644 index 0000000..683fb90 --- /dev/null +++ b/moonscript/transform/statement.lua @@ -0,0 +1,1161 @@ +local Transformer +Transformer = require("moonscript.transform.transformer").Transformer +local NameProxy, LocalName +do + local _obj_0 = require("moonscript.transform.names") + NameProxy, LocalName = _obj_0.NameProxy, _obj_0.LocalName +end +local Run, transform_last_stm, implicitly_return, construct_comprehension, last_stm +do + local _obj_0 = require("moonscript.transform.statements") + Run, transform_last_stm, implicitly_return, construct_comprehension, last_stm = _obj_0.Run, _obj_0.transform_last_stm, _obj_0.implicitly_return, _obj_0.construct_comprehension, _obj_0.last_stm +end +local types = require("moonscript.types") +local build, ntype, is_value, smart_node, value_is_singular, is_slice, NOOP +build, ntype, is_value, smart_node, value_is_singular, is_slice, NOOP = types.build, types.ntype, types.is_value, types.smart_node, types.value_is_singular, types.is_slice, types.NOOP +local insert +insert = table.insert +local destructure = require("moonscript.transform.destructure") +local CONSTRUCTOR_NAME = "new" +local with_continue_listener +with_continue_listener = function(body) + local continue_name = nil + return { + Run(function(self) + return self:listen("continue", function() + if not (continue_name) then + continue_name = NameProxy("continue") + self:put_name(continue_name) + end + return continue_name + end) + end), + build.group(body), + Run(function(self) + if not (continue_name) then + return + end + local last = last_stm(body) + local enclose_lines = types.terminating[last and ntype(last)] + self:put_name(continue_name, nil) + return self:splice(function(lines) + if enclose_lines then + lines = { + "do", + { + lines + } + } + end + return { + { + "assign", + { + continue_name + }, + { + "false" + } + }, + { + "repeat", + "true", + { + lines, + { + "assign", + { + continue_name + }, + { + "true" + } + } + } + }, + { + "if", + { + "not", + continue_name + }, + { + { + "break" + } + } + } + } + end) + end) + } +end +local extract_declarations +extract_declarations = function(self, body, start, out) + if body == nil then + body = self.current_stms + end + if start == nil then + start = self.current_stm_i + 1 + end + if out == nil then + out = { } + end + for i = start, #body do + local _continue_0 = false + repeat + local stm = body[i] + if stm == nil then + _continue_0 = true + break + end + stm = self.transform.statement(stm) + body[i] = stm + local _exp_0 = stm[1] + if "assign" == _exp_0 or "declare" == _exp_0 then + local _list_0 = stm[2] + for _index_0 = 1, #_list_0 do + local name = _list_0[_index_0] + if ntype(name) == "ref" then + insert(out, name) + elseif type(name) == "string" then + insert(out, name) + end + end + elseif "group" == _exp_0 then + extract_declarations(self, stm[2], 1, out) + end + _continue_0 = true + until true + if not _continue_0 then + break + end + end + return out +end +local expand_elseif_assign +expand_elseif_assign = function(ifstm) + for i = 4, #ifstm do + local case = ifstm[i] + if ntype(case) == "elseif" and ntype(case[2]) == "assign" then + local split = { + unpack(ifstm, 1, i - 1) + } + insert(split, { + "else", + { + { + "if", + case[2], + case[3], + unpack(ifstm, i + 1) + } + } + }) + return split + end + end + return ifstm +end +return Transformer({ + transform = function(self, tuple) + local _, node, fn + _, node, fn = tuple[1], tuple[2], tuple[3] + return fn(node) + end, + root_stms = function(self, body) + return transform_last_stm(body, implicitly_return(self)) + end, + ["return"] = function(self, node) + local ret_val = node[2] + local ret_val_type = ntype(ret_val) + if ret_val_type == "explist" and #ret_val == 2 then + ret_val = ret_val[2] + ret_val_type = ntype(ret_val) + end + if types.cascading[ret_val_type] then + return implicitly_return(self)(ret_val) + end + if ret_val_type == "chain" or ret_val_type == "comprehension" or ret_val_type == "tblcomprehension" then + local Value = require("moonscript.transform.value") + ret_val = Value:transform_once(self, ret_val) + if ntype(ret_val) == "block_exp" then + return build.group(transform_last_stm(ret_val[2], function(stm) + return { + "return", + stm + } + end)) + end + end + node[2] = ret_val + return node + end, + declare_glob = function(self, node) + local names = extract_declarations(self) + if node[2] == "^" then + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #names do + local _continue_0 = false + repeat + local name = names[_index_0] + if not (name[2]:match("^%u")) then + _continue_0 = true + break + end + local _value_0 = name + _accum_0[_len_0] = _value_0 + _len_0 = _len_0 + 1 + _continue_0 = true + until true + if not _continue_0 then + break + end + end + names = _accum_0 + end + end + return { + "declare", + names + } + end, + assign = function(self, node) + local names, values = unpack(node, 2) + local num_values = #values + local num_names = #values + if num_names == 1 and num_values == 1 then + local first_value = values[1] + local first_name = names[1] + local first_type = ntype(first_value) + if first_type == "chain" then + local Value = require("moonscript.transform.value") + first_value = Value:transform_once(self, first_value) + first_type = ntype(first_value) + end + local _exp_0 = ntype(first_value) + if "block_exp" == _exp_0 then + local block_body = first_value[2] + local idx = #block_body + block_body[idx] = build.assign_one(first_name, block_body[idx]) + return build.group({ + { + "declare", + { + first_name + } + }, + { + "do", + block_body + } + }) + elseif "comprehension" == _exp_0 or "tblcomprehension" == _exp_0 or "foreach" == _exp_0 or "for" == _exp_0 or "while" == _exp_0 then + local Value = require("moonscript.transform.value") + return build.assign_one(first_name, Value:transform_once(self, first_value)) + else + values[1] = first_value + end + end + local transformed + if num_values == 1 then + local value = values[1] + local t = ntype(value) + if t == "decorated" then + value = self.transform.statement(value) + t = ntype(value) + end + if types.cascading[t] then + local ret + ret = function(stm) + if is_value(stm) then + return { + "assign", + names, + { + stm + } + } + else + return stm + end + end + transformed = build.group({ + { + "declare", + names + }, + self.transform.statement(value, ret, node) + }) + end + end + node = transformed or node + if destructure.has_destructure(names) then + return destructure.split_assign(self, node) + end + return node + end, + continue = function(self, node) + local continue_name = self:send("continue") + if not (continue_name) then + error("continue must be inside of a loop") + end + return build.group({ + build.assign_one(continue_name, "true"), + { + "break" + } + }) + end, + export = function(self, node) + if #node > 2 then + if node[2] == "class" then + local cls = smart_node(node[3]) + return build.group({ + { + "export", + { + cls.name + } + }, + cls + }) + else + return build.group({ + { + "export", + node[2] + }, + build.assign({ + names = node[2], + values = node[3] + }) + }) + end + else + return nil + end + end, + update = function(self, node) + local _, name, op, exp = unpack(node) + local op_final = op:match("^(.+)=$") + if not op_final then + error("Unknown op: " .. op) + end + if not (value_is_singular(exp)) then + exp = { + "parens", + exp + } + end + return build.assign_one(name, { + "exp", + name, + op_final, + exp + }) + end, + import = function(self, node) + local _, names, source = unpack(node) + local table_values + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #names do + local name = names[_index_0] + local dest_name + if ntype(name) == "colon" then + dest_name = name[2] + else + dest_name = name + end + local _value_0 = { + { + "key_literal", + name + }, + dest_name + } + _accum_0[_len_0] = _value_0 + _len_0 = _len_0 + 1 + end + table_values = _accum_0 + end + local dest = { + "table", + table_values + } + return { + "assign", + { + dest + }, + { + source + }, + [-1] = node[-1] + } + end, + comprehension = function(self, node, action) + local _, exp, clauses = unpack(node) + action = action or function(exp) + return { + exp + } + end + return construct_comprehension(action(exp), clauses) + end, + ["do"] = function(self, node, ret) + if ret then + node[2] = transform_last_stm(node[2], ret) + end + return node + end, + decorated = function(self, node) + local stm, dec = unpack(node, 2) + local wrapped + local _exp_0 = dec[1] + if "if" == _exp_0 then + local cond, fail = unpack(dec, 2) + if fail then + fail = { + "else", + { + fail + } + } + end + wrapped = { + "if", + cond, + { + stm + }, + fail + } + elseif "unless" == _exp_0 then + wrapped = { + "unless", + dec[2], + { + stm + } + } + elseif "comprehension" == _exp_0 then + wrapped = { + "comprehension", + stm, + dec[2] + } + else + wrapped = error("Unknown decorator " .. dec[1]) + end + if ntype(stm) == "assign" then + wrapped = build.group({ + build.declare({ + names = (function() + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = stm[2] + for _index_0 = 1, #_list_0 do + local name = _list_0[_index_0] + if ntype(name) == "ref" then + _accum_0[_len_0] = name + _len_0 = _len_0 + 1 + end + end + return _accum_0 + end)() + }), + wrapped + }) + end + return wrapped + end, + unless = function(self, node) + return { + "if", + { + "not", + { + "parens", + node[2] + } + }, + unpack(node, 3) + } + end, + ["if"] = function(self, node, ret) + if ntype(node[2]) == "assign" then + local _, assign, body = unpack(node) + if destructure.has_destructure(assign[2]) then + local name = NameProxy("des") + body = { + destructure.build_assign(self, assign[2][1], name), + build.group(node[3]) + } + return build["do"]({ + build.assign_one(name, assign[3][1]), + { + "if", + name, + body, + unpack(node, 4) + } + }) + else + local name = assign[2][1] + return build["do"]({ + assign, + { + "if", + name, + unpack(node, 3) + } + }) + end + end + node = expand_elseif_assign(node) + if ret then + smart_node(node) + node['then'] = transform_last_stm(node['then'], ret) + for i = 4, #node do + local case = node[i] + local body_idx = #node[i] + case[body_idx] = transform_last_stm(case[body_idx], ret) + end + end + return node + end, + with = function(self, node, ret) + local exp, block = unpack(node, 2) + local copy_scope = true + local scope_name, named_assign + do + local last = last_stm(block) + if last then + if types.terminating[ntype(last)] then + ret = false + end + end + end + if ntype(exp) == "assign" then + local names, values = unpack(exp, 2) + local first_name = names[1] + if ntype(first_name) == "ref" then + scope_name = first_name + named_assign = exp + exp = values[1] + copy_scope = false + else + scope_name = NameProxy("with") + exp = values[1] + values[1] = scope_name + named_assign = { + "assign", + names, + values + } + end + elseif self:is_local(exp) then + scope_name = exp + copy_scope = false + end + scope_name = scope_name or NameProxy("with") + local out = build["do"]({ + copy_scope and build.assign_one(scope_name, exp) or NOOP, + named_assign or NOOP, + Run(function(self) + return self:set("scope_var", scope_name) + end), + unpack(block) + }) + if ret then + table.insert(out[2], ret(scope_name)) + end + return out + end, + foreach = function(self, node, _) + smart_node(node) + local source = unpack(node.iter) + local destructures = { } + do + local _accum_0 = { } + local _len_0 = 1 + for i, name in ipairs(node.names) do + if ntype(name) == "table" then + do + local proxy = NameProxy("des") + insert(destructures, destructure.build_assign(self, name, proxy)) + _accum_0[_len_0] = proxy + end + else + _accum_0[_len_0] = name + end + _len_0 = _len_0 + 1 + end + node.names = _accum_0 + end + if next(destructures) then + insert(destructures, build.group(node.body)) + node.body = destructures + end + if ntype(source) == "unpack" then + local list = source[2] + local index_name = NameProxy("index") + local list_name = self:is_local(list) and list or NameProxy("list") + local slice_var = nil + local bounds + if is_slice(list) then + local slice = list[#list] + table.remove(list) + table.remove(slice, 1) + if self:is_local(list) then + list_name = list + end + if slice[2] and slice[2] ~= "" then + local max_tmp_name = NameProxy("max") + slice_var = build.assign_one(max_tmp_name, slice[2]) + slice[2] = { + "exp", + max_tmp_name, + "<", + 0, + "and", + { + "length", + list_name + }, + "+", + max_tmp_name, + "or", + max_tmp_name + } + else + slice[2] = { + "length", + list_name + } + end + bounds = slice + else + bounds = { + 1, + { + "length", + list_name + } + } + end + return build.group({ + list_name ~= list and build.assign_one(list_name, list) or NOOP, + slice_var or NOOP, + build["for"]({ + name = index_name, + bounds = bounds, + body = { + { + "assign", + node.names, + { + NameProxy.index(list_name, index_name) + } + }, + build.group(node.body) + } + }) + }) + end + node.body = with_continue_listener(node.body) + end, + ["while"] = function(self, node) + smart_node(node) + node.body = with_continue_listener(node.body) + end, + ["for"] = function(self, node) + smart_node(node) + node.body = with_continue_listener(node.body) + end, + switch = function(self, node, ret) + local _, exp, conds = unpack(node) + local exp_name = NameProxy("exp") + local convert_cond + convert_cond = function(cond) + local t, case_exps, body = unpack(cond) + local out = { } + insert(out, t == "case" and "elseif" or "else") + if t ~= "else" then + local cond_exp = { } + for i, case in ipairs(case_exps) do + if i == 1 then + insert(cond_exp, "exp") + else + insert(cond_exp, "or") + end + if not (value_is_singular(case)) then + case = { + "parens", + case + } + end + insert(cond_exp, { + "exp", + case, + "==", + exp_name + }) + end + insert(out, cond_exp) + else + body = case_exps + end + if ret then + body = transform_last_stm(body, ret) + end + insert(out, body) + return out + end + local first = true + local if_stm = { + "if" + } + for _index_0 = 1, #conds do + local cond = conds[_index_0] + local if_cond = convert_cond(cond) + if first then + first = false + insert(if_stm, if_cond[2]) + insert(if_stm, if_cond[3]) + else + insert(if_stm, if_cond) + end + end + return build.group({ + build.assign_one(exp_name, exp), + if_stm + }) + end, + class = function(self, node, ret, parent_assign) + local _, name, parent_val, body = unpack(node) + if parent_val == "" then + parent_val = nil + end + local statements = { } + local properties = { } + for _index_0 = 1, #body do + local item = body[_index_0] + local _exp_0 = item[1] + if "stm" == _exp_0 then + insert(statements, item[2]) + elseif "props" == _exp_0 then + for _index_1 = 2, #item do + local tuple = item[_index_1] + if ntype(tuple[1]) == "self" then + insert(statements, build.assign_one(unpack(tuple))) + else + insert(properties, tuple) + end + end + end + end + local constructor + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #properties do + local _continue_0 = false + repeat + local tuple = properties[_index_0] + local key = tuple[1] + local _value_0 + if key[1] == "key_literal" and key[2] == CONSTRUCTOR_NAME then + constructor = tuple[2] + _continue_0 = true + break + else + _value_0 = tuple + end + _accum_0[_len_0] = _value_0 + _len_0 = _len_0 + 1 + _continue_0 = true + until true + if not _continue_0 then + break + end + end + properties = _accum_0 + end + local parent_cls_name = NameProxy("parent") + local base_name = NameProxy("base") + local self_name = NameProxy("self") + local cls_name = NameProxy("class") + if not (constructor) then + if parent_val then + constructor = build.fndef({ + args = { + { + "..." + } + }, + arrow = "fat", + body = { + build.chain({ + base = "super", + { + "call", + { + "..." + } + } + }) + } + }) + else + constructor = build.fndef() + end + end + local real_name = name or parent_assign and parent_assign[2][1] + local _exp_0 = ntype(real_name) + if "chain" == _exp_0 then + local last = real_name[#real_name] + local _exp_1 = ntype(last) + if "dot" == _exp_1 then + real_name = { + "string", + '"', + last[2] + } + elseif "index" == _exp_1 then + real_name = last[2] + else + real_name = "nil" + end + elseif "nil" == _exp_0 then + real_name = "nil" + else + local name_t = type(real_name) + local flattened_name + if name_t == "string" then + flattened_name = real_name + elseif name_t == "table" and real_name[1] == "ref" then + flattened_name = real_name[2] + else + flattened_name = error("don't know how to extract name from " .. tostring(name_t)) + end + real_name = { + "string", + '"', + flattened_name + } + end + local cls = build.table({ + { + "__init", + constructor + }, + { + "__base", + base_name + }, + { + "__name", + real_name + }, + parent_val and { + "__parent", + parent_cls_name + } or nil + }) + local class_index + if parent_val then + local class_lookup = build["if"]({ + cond = { + "exp", + { + "ref", + "val" + }, + "==", + "nil" + }, + ["then"] = { + build.assign_one(LocalName("parent"), build.chain({ + base = "rawget", + { + "call", + { + { + "ref", + "cls" + }, + { + "string", + '"', + "__parent" + } + } + } + })), + build["if"]({ + cond = LocalName("parent"), + ["then"] = { + build.chain({ + base = LocalName("parent"), + { + "index", + "name" + } + }) + } + }) + } + }) + insert(class_lookup, { + "else", + { + "val" + } + }) + class_index = build.fndef({ + args = { + { + "cls" + }, + { + "name" + } + }, + body = { + build.assign_one(LocalName("val"), build.chain({ + base = "rawget", + { + "call", + { + base_name, + { + "ref", + "name" + } + } + } + })), + class_lookup + } + }) + else + class_index = base_name + end + local cls_mt = build.table({ + { + "__index", + class_index + }, + { + "__call", + build.fndef({ + args = { + { + "cls" + }, + { + "..." + } + }, + body = { + build.assign_one(self_name, build.chain({ + base = "setmetatable", + { + "call", + { + "{}", + base_name + } + } + })), + build.chain({ + base = "cls.__init", + { + "call", + { + self_name, + "..." + } + } + }), + self_name + } + }) + } + }) + cls = build.chain({ + base = "setmetatable", + { + "call", + { + cls, + cls_mt + } + } + }) + local value = nil + do + local out_body = { + Run(function(self) + if name then + self:put_name(name) + end + return self:set("super", function(block, chain) + local relative_parent = { + "chain", + cls_name, + { + "dot", + "__parent" + } + } + if not (chain) then + return relative_parent + end + local chain_tail = { + unpack(chain, 3) + } + local head = chain_tail[1] + if head == nil then + return relative_parent + end + local new_chain = relative_parent + local _exp_1 = head[1] + if "call" == _exp_1 then + local calling_name = block:get("current_block") + assert(calling_name, "missing calling name") + chain_tail[1] = { + "call", + { + "self", + unpack(head[2]) + } + } + if ntype(calling_name) == "key_literal" then + insert(new_chain, { + "dot", + calling_name[2] + }) + else + insert(new_chain, { + "index", + calling_name + }) + end + elseif "colon" == _exp_1 then + local call = chain_tail[2] + if call and call[1] == "call" then + chain_tail[1] = { + "dot", + head[2] + } + chain_tail[2] = { + "call", + { + "self", + unpack(call[2]) + } + } + end + end + for _index_0 = 1, #chain_tail do + local item = chain_tail[_index_0] + insert(new_chain, item) + end + return new_chain + end) + end), + { + "declare", + { + cls_name + } + }, + { + "declare_glob", + "*" + }, + parent_val and build.assign_one(parent_cls_name, parent_val) or NOOP, + build.assign_one(base_name, { + "table", + properties + }), + build.assign_one(base_name:chain("__index"), base_name), + parent_val and build.chain({ + base = "setmetatable", + { + "call", + { + base_name, + build.chain({ + base = parent_cls_name, + { + "dot", + "__base" + } + }) + } + } + }) or NOOP, + build.assign_one(cls_name, cls), + build.assign_one(base_name:chain("__class"), cls_name), + build.group((function() + if #statements > 0 then + return { + build.assign_one(LocalName("self"), cls_name), + build.group(statements) + } + end + end)()), + parent_val and build["if"]({ + cond = { + "exp", + parent_cls_name:chain("__inherited") + }, + ["then"] = { + parent_cls_name:chain("__inherited", { + "call", + { + parent_cls_name, + cls_name + } + }) + } + }) or NOOP, + build.group((function() + if name then + return { + build.assign_one(name, cls_name) + } + end + end)()), + (function() + if ret then + return ret(cls_name) + end + end)() + } + value = build.group({ + build.group((function() + if ntype(name) == "value" then + return { + build.declare({ + names = { + name + } + }) + } + end + end)()), + build["do"](out_body) + }) + end + return value + end +}) diff --git a/moonscript/transform/statement.moon b/moonscript/transform/statement.moon new file mode 100644 index 0000000..646403f --- /dev/null +++ b/moonscript/transform/statement.moon @@ -0,0 +1,720 @@ +import Transformer from require "moonscript.transform.transformer" + +import NameProxy, LocalName from require "moonscript.transform.names" + +import Run, transform_last_stm, implicitly_return, construct_comprehension, + last_stm from require "moonscript.transform.statements" + +types = require "moonscript.types" + +import build, ntype, is_value, smart_node, value_is_singular, is_slice, NOOP + from types + +import insert from table + +destructure = require "moonscript.transform.destructure" + +CONSTRUCTOR_NAME = "new" + +with_continue_listener = (body) -> + continue_name = nil + + { + Run => + @listen "continue", -> + unless continue_name + continue_name = NameProxy"continue" + @put_name continue_name + continue_name + + build.group body + + Run => + return unless continue_name + last = last_stm body + enclose_lines = types.terminating[last and ntype(last)] + + @put_name continue_name, nil + @splice (lines) -> + lines = {"do", {lines}} if enclose_lines + + { + {"assign", {continue_name}, {"false"}} + {"repeat", "true", { + lines + {"assign", {continue_name}, {"true"}} + }} + {"if", {"not", continue_name}, { + {"break"} + }} + } + } + + +-- this mutates body searching for assigns +extract_declarations = (body=@current_stms, start=@current_stm_i + 1, out={}) => + for i=start,#body + stm = body[i] + continue if stm == nil + stm = @transform.statement stm + body[i] = stm + switch stm[1] + when "assign", "declare" + for name in *stm[2] + if ntype(name) == "ref" + insert out, name + elseif type(name) == "string" + -- TODO: don't use string literal as ref + insert out, name + when "group" + extract_declarations @, stm[2], 1, out + out + +expand_elseif_assign = (ifstm) -> + for i = 4, #ifstm + case = ifstm[i] + if ntype(case) == "elseif" and ntype(case[2]) == "assign" + split = { unpack ifstm, 1, i - 1 } + insert split, { + "else", { + {"if", case[2], case[3], unpack ifstm, i + 1} + } + } + return split + + ifstm + + +Transformer { + transform: (tuple) => + {_, node, fn} = tuple + fn node + + root_stms: (body) => + transform_last_stm body, implicitly_return @ + + return: (node) => + ret_val = node[2] + ret_val_type = ntype ret_val + + if ret_val_type == "explist" and #ret_val == 2 + ret_val = ret_val[2] + ret_val_type = ntype ret_val + + if types.cascading[ret_val_type] + return implicitly_return(@) ret_val + + -- flatten things that create block exp + if ret_val_type == "chain" or ret_val_type == "comprehension" or ret_val_type == "tblcomprehension" + -- TODO: clean this up + Value = require "moonscript.transform.value" + ret_val = Value\transform_once @, ret_val + if ntype(ret_val) == "block_exp" + return build.group transform_last_stm ret_val[2], (stm)-> + {"return", stm} + + node[2] = ret_val + node + + declare_glob: (node) => + names = extract_declarations @ + + if node[2] == "^" + names = for name in *names + continue unless name[2]\match "^%u" + name + + {"declare", names} + + assign: (node) => + names, values = unpack node, 2 + + num_values = #values + num_names = #values + + -- special code simplifications for single assigns + if num_names == 1 and num_values == 1 + first_value = values[1] + first_name = names[1] + first_type = ntype first_value + + -- reduce colon stub chain to block exp + if first_type == "chain" + -- TODO: clean this up + Value = require "moonscript.transform.value" + first_value = Value\transform_once @, first_value + first_type = ntype first_value + + switch ntype first_value + when "block_exp" + block_body = first_value[2] + idx = #block_body + block_body[idx] = build.assign_one first_name, block_body[idx] + + return build.group { + {"declare", {first_name}} + {"do", block_body} + } + + when "comprehension", "tblcomprehension", "foreach", "for", "while" + -- TODO: clean this up + Value = require "moonscript.transform.value" + return build.assign_one first_name, Value\transform_once @, first_value + else + values[1] = first_value + + -- bubble cascading assigns + transformed = if num_values == 1 + value = values[1] + t = ntype value + + if t == "decorated" + value = @transform.statement value + t = ntype value + + if types.cascading[t] + ret = (stm) -> + if is_value stm + {"assign", names, {stm}} + else + stm + + build.group { + {"declare", names} + @transform.statement value, ret, node + } + + node = transformed or node + + if destructure.has_destructure names + return destructure.split_assign @, node + + node + + continue: (node) => + continue_name = @send "continue" + error "continue must be inside of a loop" unless continue_name + build.group { + build.assign_one continue_name, "true" + {"break"} + } + + export: (node) => + -- assign values if they are included + if #node > 2 + if node[2] == "class" + cls = smart_node node[3] + build.group { + {"export", {cls.name}} + cls + } + else + -- pull out vawlues and assign them after the export + build.group { + { "export", node[2] } + build.assign { + names: node[2] + values: node[3] + } + } + else + nil + + update: (node) => + _, name, op, exp = unpack node + op_final = op\match "^(.+)=$" + error "Unknown op: "..op if not op_final + exp = {"parens", exp} unless value_is_singular exp + build.assign_one name, {"exp", name, op_final, exp} + + import: (node) => + _, names, source = unpack node + table_values = for name in *names + dest_name = if ntype(name) == "colon" + name[2] + else + name + + {{"key_literal", name}, dest_name} + + dest = { "table", table_values } + { "assign", {dest}, {source}, [-1]: node[-1] } + + comprehension: (node, action) => + _, exp, clauses = unpack node + + action = action or (exp) -> {exp} + construct_comprehension action(exp), clauses + + do: (node, ret) => + node[2] = transform_last_stm node[2], ret if ret + node + + decorated: (node) => + stm, dec = unpack node, 2 + + wrapped = switch dec[1] + when "if" + cond, fail = unpack dec, 2 + fail = { "else", { fail } } if fail + { "if", cond, { stm }, fail } + when "unless" + { "unless", dec[2], { stm } } + when "comprehension" + { "comprehension", stm, dec[2] } + else + error "Unknown decorator " .. dec[1] + + if ntype(stm) == "assign" + wrapped = build.group { + build.declare names: [name for name in *stm[2] when ntype(name) == "ref"] + wrapped + } + + wrapped + + unless: (node) => + { "if", {"not", {"parens", node[2]}}, unpack node, 3 } + + if: (node, ret) => + -- expand assign in cond + if ntype(node[2]) == "assign" + _, assign, body = unpack node + if destructure.has_destructure assign[2] + name = NameProxy "des" + + body = { + destructure.build_assign @, assign[2][1], name + build.group node[3] + } + + return build.do { + build.assign_one name, assign[3][1] + {"if", name, body, unpack node, 4} + } + else + name = assign[2][1] + return build["do"] { + assign + {"if", name, unpack node, 3} + } + + node = expand_elseif_assign node + + -- apply cascading return decorator + if ret + smart_node node + -- mutate all the bodies + node['then'] = transform_last_stm node['then'], ret + for i = 4, #node + case = node[i] + body_idx = #node[i] + case[body_idx] = transform_last_stm case[body_idx], ret + + node + + with: (node, ret) => + exp, block = unpack node, 2 + + copy_scope = true + local scope_name, named_assign + + if last = last_stm block + ret = false if types.terminating[ntype(last)] + + if ntype(exp) == "assign" + names, values = unpack exp, 2 + first_name = names[1] + + if ntype(first_name) == "ref" + scope_name = first_name + named_assign = exp + exp = values[1] + copy_scope = false + else + scope_name = NameProxy "with" + exp = values[1] + values[1] = scope_name + named_assign = {"assign", names, values} + + elseif @is_local exp + scope_name = exp + copy_scope = false + + scope_name or= NameProxy "with" + + out = build.do { + copy_scope and build.assign_one(scope_name, exp) or NOOP + named_assign or NOOP + Run => @set "scope_var", scope_name + unpack block + } + + if ret + table.insert out[2], ret scope_name + + out + + foreach: (node, _) => + smart_node node + source = unpack node.iter + + destructures = {} + node.names = for i, name in ipairs node.names + if ntype(name) == "table" + with proxy = NameProxy "des" + insert destructures, destructure.build_assign @, name, proxy + else + name + + if next destructures + insert destructures, build.group node.body + node.body = destructures + + if ntype(source) == "unpack" + list = source[2] + + index_name = NameProxy "index" + + list_name = @is_local(list) and list or NameProxy "list" + + slice_var = nil + bounds = if is_slice list + slice = list[#list] + table.remove list + table.remove slice, 1 + + list_name = list if @is_local list + + slice[2] = if slice[2] and slice[2] != "" + max_tmp_name = NameProxy "max" + slice_var = build.assign_one max_tmp_name, slice[2] + {"exp", max_tmp_name, "<", 0 + "and", {"length", list_name}, "+", max_tmp_name + "or", max_tmp_name } + else + {"length", list_name} + + slice + else + {1, {"length", list_name}} + + return build.group { + list_name != list and build.assign_one(list_name, list) or NOOP + slice_var or NOOP + build["for"] { + name: index_name + bounds: bounds + body: { + {"assign", node.names, { NameProxy.index list_name, index_name }} + build.group node.body + } + } + } + + node.body = with_continue_listener node.body + + while: (node) => + smart_node node + node.body = with_continue_listener node.body + + for: (node) => + smart_node node + node.body = with_continue_listener node.body + + switch: (node, ret) => + _, exp, conds = unpack node + exp_name = NameProxy "exp" + + -- convert switch conds into if statment conds + convert_cond = (cond) -> + t, case_exps, body = unpack cond + out = {} + insert out, t == "case" and "elseif" or "else" + if t != "else" + cond_exp = {} + for i, case in ipairs case_exps + if i == 1 + insert cond_exp, "exp" + else + insert cond_exp, "or" + + case = {"parens", case} unless value_is_singular case + insert cond_exp, {"exp", case, "==", exp_name} + + insert out, cond_exp + else + body = case_exps + + if ret + body = transform_last_stm body, ret + + insert out, body + + out + + first = true + if_stm = {"if"} + for cond in *conds + if_cond = convert_cond cond + if first + first = false + insert if_stm, if_cond[2] + insert if_stm, if_cond[3] + else + insert if_stm, if_cond + + build.group { + build.assign_one exp_name, exp + if_stm + } + + class: (node, ret, parent_assign) => + _, name, parent_val, body = unpack node + parent_val = nil if parent_val == "" + + -- split apart properties and statements + statements = {} + properties = {} + for item in *body + switch item[1] + when "stm" + insert statements, item[2] + when "props" + for tuple in *item[2,] + if ntype(tuple[1]) == "self" + insert statements, build.assign_one unpack tuple + else + insert properties, tuple + + -- find constructor + local constructor + properties = for tuple in *properties + key = tuple[1] + if key[1] == "key_literal" and key[2] == CONSTRUCTOR_NAME + constructor = tuple[2] + continue + else + tuple + + parent_cls_name = NameProxy "parent" + base_name = NameProxy "base" + self_name = NameProxy "self" + cls_name = NameProxy "class" + + unless constructor + constructor = if parent_val + build.fndef { + args: {{"..."}} + arrow: "fat" + body: { + build.chain { base: "super", {"call", {"..."}} } + } + } + else + build.fndef! + + real_name = name or parent_assign and parent_assign[2][1] + real_name = switch ntype real_name + when "chain" + last = real_name[#real_name] + switch ntype last + when "dot" + {"string", '"', last[2]} + when "index" + last[2] + else + "nil" + when "nil" + "nil" + else + name_t = type real_name + -- TODO: don't use string literal as ref + flattened_name = if name_t == "string" + real_name + elseif name_t == "table" and real_name[1] == "ref" + real_name[2] + else + error "don't know how to extract name from #{name_t}" + + {"string", '"', flattened_name} + + cls = build.table { + {"__init", constructor} + {"__base", base_name} + {"__name", real_name} -- "quote the string" + parent_val and {"__parent", parent_cls_name} or nil + } + + -- looking up a name in the class object + class_index = if parent_val + class_lookup = build["if"] { + cond: { "exp", {"ref", "val"}, "==", "nil" } + then: { + build.assign_one LocalName"parent", build.chain { + base: "rawget" + { + "call", { + {"ref", "cls"} + {"string", '"', "__parent"} + } + } + } + + build.if { + cond: LocalName "parent" + then: { + build.chain { + base: LocalName "parent" + {"index", "name"} + } + } + } + } + } + insert class_lookup, {"else", {"val"}} + + build.fndef { + args: {{"cls"}, {"name"}} + body: { + build.assign_one LocalName"val", build.chain { + base: "rawget", {"call", {base_name, {"ref", "name"}}} + } + class_lookup + } + } + else + base_name + + cls_mt = build.table { + {"__index", class_index} + {"__call", build.fndef { + args: {{"cls"}, {"..."}} + body: { + build.assign_one self_name, build.chain { + base: "setmetatable" + {"call", {"{}", base_name}} + } + build.chain { + base: "cls.__init" + {"call", {self_name, "..."}} + } + self_name + } + }} + } + + cls = build.chain { + base: "setmetatable" + {"call", {cls, cls_mt}} + } + + value = nil + with build + out_body = { + Run => + -- make sure we don't assign the class to a local inside the do + @put_name name if name + + @set "super", (block, chain) -> + relative_parent = { + "chain", + cls_name + {"dot", "__parent"} + } + + return relative_parent unless chain + + chain_tail = { unpack chain, 3 } + head = chain_tail[1] + + if head == nil + return relative_parent + + new_chain = relative_parent + + switch head[1] + -- calling super, inject calling name and self into chain + when "call" + calling_name = block\get"current_block" + assert calling_name, "missing calling name" + chain_tail[1] = {"call", {"self", unpack head[2]}} + + if ntype(calling_name) == "key_literal" + insert new_chain, {"dot", calling_name[2]} + else + insert new_chain, {"index", calling_name} + + -- colon call on super, replace class with self as first arg + when "colon" + call = chain_tail[2] + -- calling chain tail + if call and call[1] == "call" + chain_tail[1] = { + "dot" + head[2] + } + + chain_tail[2] = { + "call" + { + "self" + unpack call[2] + } + } + + insert new_chain, item for item in *chain_tail + new_chain + + {"declare", { cls_name }} + {"declare_glob", "*"} + + parent_val and .assign_one(parent_cls_name, parent_val) or NOOP + + .assign_one base_name, {"table", properties} + .assign_one base_name\chain"__index", base_name + + parent_val and .chain({ + base: "setmetatable" + {"call", { + base_name, + .chain { base: parent_cls_name, {"dot", "__base"}} + }} + }) or NOOP + + .assign_one cls_name, cls + .assign_one base_name\chain"__class", cls_name + + .group if #statements > 0 then { + .assign_one LocalName"self", cls_name + .group statements + } + + -- run the inherited callback + parent_val and .if({ + cond: {"exp", parent_cls_name\chain "__inherited" } + then: { + parent_cls_name\chain "__inherited", {"call", { + parent_cls_name, cls_name + }} + } + }) or NOOP + + .group if name then { + .assign_one name, cls_name + } + + if ret + ret cls_name + } + + value = .group { + .group if ntype(name) == "value" then { + .declare names: {name} + } + + .do out_body + } + + value +} diff --git a/moonscript/transform/statements.lua b/moonscript/transform/statements.lua index 994df89..dc562a5 100644 --- a/moonscript/transform/statements.lua +++ b/moonscript/transform/statements.lua @@ -1,8 +1,6 @@ -local ntype, mtype -do - local _obj_0 = require("moonscript.types") - ntype, mtype = _obj_0.ntype, _obj_0.mtype -end +local types = require("moonscript.types") +local ntype, mtype, is_value, NOOP +ntype, mtype, is_value, NOOP = types.ntype, types.mtype, types.is_value, types.NOOP local Run do local _class_0 @@ -74,9 +72,88 @@ chain_is_stub = function(chain) local stub = chain[#chain] return stub and ntype(stub) == "colon" end +local implicitly_return +implicitly_return = function(scope) + local is_top = true + local fn + fn = function(stm) + local t = ntype(stm) + if t == "decorated" then + stm = scope.transform.statement(stm) + t = ntype(stm) + end + if types.cascading[t] then + is_top = false + return scope.transform.statement(stm, fn) + elseif types.manual_return[t] or not is_value(stm) then + if is_top and t == "return" and stm[2] == "" then + return NOOP + else + return stm + end + else + if t == "comprehension" and not types.comprehension_has_value(stm) then + return stm + else + return { + "return", + stm + } + end + end + end + return fn +end +local reversed +reversed = require("moonscript.util").reversed +local construct_comprehension +construct_comprehension = function(inner, clauses) + local current_stms = inner + for _, clause in reversed(clauses) do + local t = clause[1] + local _exp_0 = t + if "for" == _exp_0 then + local name, bounds + _, name, bounds = clause[1], clause[2], clause[3] + current_stms = { + "for", + name, + bounds, + current_stms + } + elseif "foreach" == _exp_0 then + local names, iter + _, names, iter = clause[1], clause[2], clause[3] + current_stms = { + "foreach", + names, + { + iter + }, + current_stms + } + elseif "when" == _exp_0 then + local cond + _, cond = clause[1], clause[2] + current_stms = { + "if", + cond, + current_stms + } + else + current_stms = error("Unknown comprehension clause: " .. t) + end + current_stms = { + current_stms + } + end + return current_stms[1] +end return { Run = Run, last_stm = last_stm, transform_last_stm = transform_last_stm, - chain_is_stub = chain_is_stub + chain_is_stub = chain_is_stub, + implicitly_return = implicitly_return, + construct_comprehension = construct_comprehension } diff --git a/moonscript/transform/statements.moon b/moonscript/transform/statements.moon index 59d8e36..3edb0de 100644 --- a/moonscript/transform/statements.moon +++ b/moonscript/transform/statements.moon @@ -1,5 +1,6 @@ -import ntype, mtype from require "moonscript.types" +types = require "moonscript.types" +import ntype, mtype, is_value, NOOP from types -- A Run is a special statement node that lets a function run and mutate the -- state of the compiler @@ -44,5 +45,56 @@ chain_is_stub = (chain) -> stub = chain[#chain] stub and ntype(stub) == "colon" -{:Run, :last_stm, :transform_last_stm, :chain_is_stub} +implicitly_return = (scope) -> + is_top = true + fn = (stm) -> + t = ntype stm + + -- expand decorated + if t == "decorated" + stm = scope.transform.statement stm + t = ntype stm + + if types.cascading[t] + is_top = false + scope.transform.statement stm, fn + elseif types.manual_return[t] or not is_value stm + -- remove blank return statement + if is_top and t == "return" and stm[2] == "" + NOOP + else + stm + else + if t == "comprehension" and not types.comprehension_has_value stm + stm + else + {"return", stm} + + fn + +-- TODO: reversed unecessary +import reversed from require "moonscript.util" +construct_comprehension = (inner, clauses) -> + current_stms = inner + for _, clause in reversed clauses + t = clause[1] + current_stms = switch t + when "for" + {_, name, bounds} = clause + {"for", name, bounds, current_stms} + when "foreach" + {_, names, iter} = clause + {"foreach", names, {iter}, current_stms} + when "when" + {_, cond} = clause + {"if", cond, current_stms} + else + error "Unknown comprehension clause: "..t + + current_stms = {current_stms} + + current_stms[1] + +{:Run, :last_stm, :transform_last_stm, :chain_is_stub, :implicitly_return, + :construct_comprehension} diff --git a/moonscript/transform/transformer.lua b/moonscript/transform/transformer.lua new file mode 100644 index 0000000..4ea1695 --- /dev/null +++ b/moonscript/transform/transformer.lua @@ -0,0 +1,74 @@ +local ntype +ntype = require("moonscript.types").ntype +local Transformer +do + local _class_0 + local _base_0 = { + transform_once = function(self, scope, node, ...) + if self.seen_nodes[node] then + return node + end + self.seen_nodes[node] = true + local transformer = self.transformers[ntype(node)] + if transformer then + return transformer(scope, node, ...) or node + else + return node + end + end, + transform = function(self, scope, node, ...) + if self.seen_nodes[node] then + return node + end + self.seen_nodes[node] = true + while true do + local transformer = self.transformers[ntype(node)] + local res + if transformer then + res = transformer(scope, node, ...) or node + else + res = node + end + if res == node then + return node + end + node = res + end + return node + end, + bind = function(self, scope) + return function(...) + return self:transform(scope, ...) + end + end, + __call = function(self, ...) + return self:transform(...) + end, + can_transform = function(self, node) + return self.transformers[ntype(node)] ~= nil + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, transformers) + self.transformers = transformers + self.seen_nodes = setmetatable({ }, { + __mode = "k" + }) + end, + __base = _base_0, + __name = "Transformer" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + Transformer = _class_0 +end +return { + Transformer = Transformer +} diff --git a/moonscript/transform/transformer.moon b/moonscript/transform/transformer.moon new file mode 100644 index 0000000..8a40c3c --- /dev/null +++ b/moonscript/transform/transformer.moon @@ -0,0 +1,42 @@ +import ntype from require "moonscript.types" + +class Transformer + new: (@transformers) => + @seen_nodes = setmetatable {}, __mode: "k" + + transform_once: (scope, node, ...) => + return node if @seen_nodes[node] + @seen_nodes[node] = true + + transformer = @transformers[ntype node] + if transformer + transformer(scope, node, ...) or node + else + node + + transform: (scope, node, ...) => + return node if @seen_nodes[node] + + @seen_nodes[node] = true + while true + transformer = @transformers[ntype node] + res = if transformer + transformer(scope, node, ...) or node + else + node + + return node if res == node + node = res + + node + + bind: (scope) => + (...) -> @transform scope, ... + + __call: (...) => @transform ... + + can_transform: (node) => + @transformers[ntype node] != nil + + +{ :Transformer } diff --git a/moonscript/transform/value.lua b/moonscript/transform/value.lua new file mode 100644 index 0000000..6b56a98 --- /dev/null +++ b/moonscript/transform/value.lua @@ -0,0 +1,261 @@ +local Transformer +Transformer = require("moonscript.transform.transformer").Transformer +local build, ntype, smart_node +do + local _obj_0 = require("moonscript.types") + build, ntype, smart_node = _obj_0.build, _obj_0.ntype, _obj_0.smart_node +end +local NameProxy +NameProxy = require("moonscript.transform.names").NameProxy +local Accumulator, default_accumulator +do + local _obj_0 = require("moonscript.transform.accumulator") + Accumulator, default_accumulator = _obj_0.Accumulator, _obj_0.default_accumulator +end +local lua_keywords +lua_keywords = require("moonscript.data").lua_keywords +local Run, transform_last_stm, implicitly_return, chain_is_stub, construct_comprehension +do + local _obj_0 = require("moonscript.transform.statements") + Run, transform_last_stm, implicitly_return, chain_is_stub, construct_comprehension = _obj_0.Run, _obj_0.transform_last_stm, _obj_0.implicitly_return, _obj_0.chain_is_stub, _obj_0.construct_comprehension +end +local insert +insert = table.insert +return Transformer({ + ["for"] = default_accumulator, + ["while"] = default_accumulator, + foreach = default_accumulator, + ["do"] = function(self, node) + return build.block_exp(node[2]) + end, + decorated = function(self, node) + return self.transform.statement(node) + end, + class = function(self, node) + return build.block_exp({ + node + }) + end, + string = function(self, node) + local delim = node[2] + local convert_part + convert_part = function(part) + if type(part) == "string" or part == nil then + return { + "string", + delim, + part or "" + } + else + return build.chain({ + base = "tostring", + { + "call", + { + part[2] + } + } + }) + end + end + if #node <= 3 then + if type(node[3]) == "string" then + return node + else + return convert_part(node[3]) + end + end + local e = { + "exp", + convert_part(node[3]) + } + for i = 4, #node do + insert(e, "..") + insert(e, convert_part(node[i])) + end + return e + end, + comprehension = function(self, node) + local a = Accumulator() + node = self.transform.statement(node, function(exp) + return a:mutate_body({ + exp + }) + end) + return a:wrap(node) + end, + tblcomprehension = function(self, node) + local _, explist, clauses = unpack(node) + local key_exp, value_exp = unpack(explist) + local accum = NameProxy("tbl") + local inner + if value_exp then + local dest = build.chain({ + base = accum, + { + "index", + key_exp + } + }) + inner = { + build.assign_one(dest, value_exp) + } + else + local key_name, val_name = NameProxy("key"), NameProxy("val") + local dest = build.chain({ + base = accum, + { + "index", + key_name + } + }) + inner = { + build.assign({ + names = { + key_name, + val_name + }, + values = { + key_exp + } + }), + build.assign_one(dest, val_name) + } + end + return build.block_exp({ + build.assign_one(accum, build.table()), + construct_comprehension(inner, clauses), + accum + }) + end, + fndef = function(self, node) + smart_node(node) + node.body = transform_last_stm(node.body, implicitly_return(self)) + node.body = { + Run(function(self) + return self:listen("varargs", function() end) + end), + unpack(node.body) + } + return node + end, + ["if"] = function(self, node) + return build.block_exp({ + node + }) + end, + unless = function(self, node) + return build.block_exp({ + node + }) + end, + with = function(self, node) + return build.block_exp({ + node + }) + end, + switch = function(self, node) + return build.block_exp({ + node + }) + end, + chain = function(self, node) + for i = 2, #node do + local part = node[i] + if ntype(part) == "dot" and lua_keywords[part[2]] then + node[i] = { + "index", + { + "string", + '"', + part[2] + } + } + end + end + if ntype(node[2]) == "string" then + node[2] = { + "parens", + node[2] + } + end + if chain_is_stub(node) then + local base_name = NameProxy("base") + local fn_name = NameProxy("fn") + local colon = table.remove(node) + local is_super = ntype(node[2]) == "ref" and node[2][2] == "super" + return build.block_exp({ + build.assign({ + names = { + base_name + }, + values = { + node + } + }), + build.assign({ + names = { + fn_name + }, + values = { + build.chain({ + base = base_name, + { + "dot", + colon[2] + } + }) + } + }), + build.fndef({ + args = { + { + "..." + } + }, + body = { + build.chain({ + base = fn_name, + { + "call", + { + is_super and "self" or base_name, + "..." + } + } + }) + } + }) + }) + end + end, + block_exp = function(self, node) + local _, body = unpack(node) + local fn = nil + local arg_list = { } + fn = smart_node(build.fndef({ + body = { + Run(function(self) + return self:listen("varargs", function() + insert(arg_list, "...") + insert(fn.args, { + "..." + }) + return self:unlisten("varargs") + end) + end), + unpack(body) + } + })) + return build.chain({ + base = { + "parens", + fn + }, + { + "call", + arg_list + } + }) + end +}) diff --git a/moonscript/transform/value.moon b/moonscript/transform/value.moon new file mode 100644 index 0000000..2b3491e --- /dev/null +++ b/moonscript/transform/value.moon @@ -0,0 +1,162 @@ +import Transformer from require "moonscript.transform.transformer" +import build, ntype, smart_node from require "moonscript.types" + +import NameProxy from require "moonscript.transform.names" +import Accumulator, default_accumulator from require "moonscript.transform.accumulator" +import lua_keywords from require "moonscript.data" + +import Run, transform_last_stm, implicitly_return, chain_is_stub, + construct_comprehension from require "moonscript.transform.statements" + +import insert from table + +Transformer { + for: default_accumulator + while: default_accumulator + foreach: default_accumulator + + do: (node) => + build.block_exp node[2] + + decorated: (node) => + @transform.statement node + + class: (node) => + build.block_exp { node } + + string: (node) => + delim = node[2] + + convert_part = (part) -> + if type(part) == "string" or part == nil + {"string", delim, part or ""} + else + build.chain { base: "tostring", {"call", {part[2]}} } + + -- reduced to single item + if #node <= 3 + return if type(node[3]) == "string" + node + else + convert_part node[3] + + e = {"exp", convert_part node[3]} + + for i=4, #node + insert e, ".." + insert e, convert_part node[i] + e + + comprehension: (node) => + a = Accumulator! + node = @transform.statement node, (exp) -> + a\mutate_body {exp} + a\wrap node + + tblcomprehension: (node) => + _, explist, clauses = unpack node + key_exp, value_exp = unpack explist + + accum = NameProxy "tbl" + + inner = if value_exp + dest = build.chain { base: accum, {"index", key_exp} } + { build.assign_one dest, value_exp } + else + -- If we only have single expression then + -- unpack the result into key and value + key_name, val_name = NameProxy"key", NameProxy"val" + dest = build.chain { base: accum, {"index", key_name} } + { + build.assign names: {key_name, val_name}, values: {key_exp} + build.assign_one dest, val_name + } + + build.block_exp { + build.assign_one accum, build.table! + construct_comprehension inner, clauses + accum + } + + fndef: (node) => + smart_node node + node.body = transform_last_stm node.body, implicitly_return self + node.body = { + Run => @listen "varargs", -> -- capture event + unpack node.body + } + + node + + if: (node) => + build.block_exp { node } + + unless: (node) => + build.block_exp { node } + + with: (node) => + build.block_exp { node } + + switch: (node) => + build.block_exp { node } + + -- pull out colon chain + chain: (node) => + -- escape lua keywords used in dot accessors + for i=2,#node + part = node[i] + if ntype(part) == "dot" and lua_keywords[part[2]] + node[i] = { "index", {"string", '"', part[2]} } + + if ntype(node[2]) == "string" + -- add parens if callee is raw string + node[2] = {"parens", node[2] } + + if chain_is_stub node + base_name = NameProxy "base" + fn_name = NameProxy "fn" + colon = table.remove node + + is_super = ntype(node[2]) == "ref" and node[2][2] == "super" + build.block_exp { + build.assign { + names: {base_name} + values: {node} + } + + build.assign { + names: {fn_name} + values: { + build.chain { base: base_name, {"dot", colon[2]} } + } + } + + build.fndef { + args: {{"..."}} + body: { + build.chain { + base: fn_name, {"call", {is_super and "self" or base_name, "..."}} + } + } + } + } + + block_exp: (node) => + _, body = unpack node + + fn = nil + arg_list = {} + + fn = smart_node build.fndef body: { + Run => + @listen "varargs", -> + insert arg_list, "..." + insert fn.args, {"..."} + @unlisten "varargs" + + unpack body + } + + build.chain { base: {"parens", fn}, {"call", arg_list} } +} + diff --git a/moonscript/types.lua b/moonscript/types.lua index a8bd6c7..64ea365 100644 --- a/moonscript/types.lua +++ b/moonscript/types.lua @@ -308,6 +308,9 @@ local smart_node smart_node = function(node) return setmetatable(node, smart_node_mt[ntype(node)]) end +local NOOP = { + "noop" +} return { ntype = ntype, smart_node = smart_node, @@ -320,5 +323,6 @@ return { comprehension_has_value = comprehension_has_value, value_can_be_statement = value_can_be_statement, mtype = mtype, - terminating = terminating + terminating = terminating, + NOOP = NOOP } diff --git a/moonscript/types.moon b/moonscript/types.moon index abac311..a236033 100644 --- a/moonscript/types.moon +++ b/moonscript/types.moon @@ -189,9 +189,12 @@ smart_node_mt = setmetatable {}, { smart_node = (node) -> setmetatable node, smart_node_mt[ntype node] +NOOP = {"noop"} + { :ntype, :smart_node, :build, :is_value, :is_slice, :manual_return, :cascading, :value_is_singular, :comprehension_has_value, :value_can_be_statement, :mtype, :terminating + :NOOP }