mirror of
				https://github.com/KevinMidboe/linguist.git
				synced 2025-10-29 17:50:22 +00:00 
			
		
		
		
	add MoonScript sample
This commit is contained in:
		
							
								
								
									
										903
									
								
								samples/MoonScript/transform.moon
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										903
									
								
								samples/MoonScript/transform.moon
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,903 @@ | ||||
|  | ||||
| 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" | ||||
|  | ||||
| destructure = require "moonscript.transform.destructure" | ||||
|  | ||||
| local implicitly_return | ||||
|  | ||||
| class Run | ||||
|   new: (@fn) => | ||||
|     self[1] = "run" | ||||
|  | ||||
|   call: (state) => | ||||
|     self.fn state | ||||
|  | ||||
| -- transform the last stm is a list of stms | ||||
| -- will puke on group | ||||
| apply_to_last = (stms, fn) -> | ||||
|   -- find last (real) exp | ||||
|   last_exp_id = 0 | ||||
|   for i = #stms, 1, -1 | ||||
|     stm = stms[i] | ||||
|     if stm and mtype(stm) != Run | ||||
|       last_exp_id = i | ||||
|       break | ||||
|  | ||||
|   return for i, stm in ipairs stms | ||||
|     if i == last_exp_id | ||||
|       fn stm | ||||
|     else | ||||
|       stm | ||||
|  | ||||
| -- is a body a sindle expression/statement | ||||
| is_singular = (body) -> | ||||
|   return false if #body != 1 | ||||
|   if "group" == ntype body | ||||
|     is_singular body[2] | ||||
|   else | ||||
|     true | ||||
|  | ||||
| find_assigns = (body, out={}) -> | ||||
|   for thing in *body | ||||
|     switch thing[1] | ||||
|       when "group" | ||||
|         find_assigns thing[2], out | ||||
|       when "assign" | ||||
|         table.insert out, thing[2] -- extract names | ||||
|   out | ||||
|  | ||||
| hoist_declarations = (body) -> | ||||
|   assigns = {} | ||||
|  | ||||
|   -- hoist the plain old assigns | ||||
|   for names in *find_assigns body | ||||
|     for name in *names | ||||
|       table.insert assigns, name if type(name) == "string" | ||||
|  | ||||
|   -- insert after runs | ||||
|   idx = 1 | ||||
|   while mtype(body[idx]) == Run do idx += 1 | ||||
|  | ||||
|   table.insert body, idx, {"declare", assigns} | ||||
|  | ||||
| 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 | ||||
|       @put_name continue_name, nil | ||||
|       @splice (lines) -> { | ||||
|         {"assign", {continue_name}, {"false"}} | ||||
|         {"repeat", "true", { | ||||
|           lines | ||||
|           {"assign", {continue_name}, {"true"}} | ||||
|         }} | ||||
|         {"if", {"not", continue_name}, { | ||||
|           {"break"} | ||||
|         }} | ||||
|       } | ||||
|   } | ||||
|  | ||||
|  | ||||
| class Transformer | ||||
|   new: (@transformers) => | ||||
|     @seen_nodes = setmetatable {}, __mode: "k" | ||||
|  | ||||
|   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 | ||||
|  | ||||
| construct_comprehension = (inner, clauses) -> | ||||
|   current_stms = inner | ||||
|   for _, clause in reversed clauses | ||||
|     t = clause[1] | ||||
|     current_stms = if t == "for" | ||||
|       _, names, iter = unpack clause | ||||
|       {"foreach", names, {iter}, current_stms} | ||||
|     elseif t == "when" | ||||
|       _, cond = unpack clause | ||||
|       {"if", cond, current_stms} | ||||
|     else | ||||
|       error "Unknown comprehension clause: "..t | ||||
|     current_stms = {current_stms} | ||||
|  | ||||
|   current_stms[1] | ||||
|  | ||||
| Statement = Transformer { | ||||
|   root_stms: (body) => | ||||
|     apply_to_last body, implicitly_return @ | ||||
|  | ||||
|   assign: (node) => | ||||
|     names, values = unpack node, 2 | ||||
|     -- bubble cascading assigns | ||||
|     transformed = if #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 | ||||
|         build.group { | ||||
|           node | ||||
|           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 | ||||
|  | ||||
|     stubs = for name in *names | ||||
|       if type(name) == "table" | ||||
|         name | ||||
|       else | ||||
|         {"dot", name} | ||||
|  | ||||
|     real_names = for name in *names | ||||
|       type(name) == "table" and name[2] or name | ||||
|  | ||||
|     if type(source) == "string" | ||||
|       build.assign { | ||||
|         names: real_names | ||||
|         values: [build.chain { base: source, stub} for stub in *stubs] | ||||
|       } | ||||
|     else | ||||
|       source_name = NameProxy "table" | ||||
|       build.group { | ||||
|         {"declare", real_names} | ||||
|         build["do"] { | ||||
|           build.assign_one source_name, source | ||||
|           build.assign { | ||||
|             names: real_names | ||||
|             values: [build.chain { base: source_name, stub} for stub in *stubs] | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|  | ||||
|   comprehension: (node, action) => | ||||
|     _, exp, clauses = unpack node | ||||
|  | ||||
|     action = action or (exp) -> {exp} | ||||
|     construct_comprehension action(exp), clauses | ||||
|  | ||||
|   do: (node, ret) => | ||||
|     node[2] = apply_to_last 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 type(name) == "string"] | ||||
|         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'] = apply_to_last node['then'], ret | ||||
|       for i = 4, #node | ||||
|         case = node[i] | ||||
|         body_idx = #node[i] | ||||
|         case[body_idx] = apply_to_last case[body_idx], ret | ||||
|  | ||||
|     node | ||||
|  | ||||
|   with: (node, ret) => | ||||
|     _, exp, block = unpack node | ||||
|  | ||||
|     scope_name = NameProxy "with" | ||||
|  | ||||
|     named_assign = if ntype(exp) == "assign" | ||||
|       names, values = unpack exp, 2 | ||||
|       assign_name = names[1] | ||||
|       exp = values[1] | ||||
|       values[1] = scope_name | ||||
|       {"assign", names, values} | ||||
|  | ||||
|     build.do { | ||||
|       Run => @set "scope_var", scope_name | ||||
|       build.assign_one scope_name, exp | ||||
|       build.group { named_assign } | ||||
|       build.group block | ||||
|  | ||||
|       if ret | ||||
|         ret scope_name | ||||
|     } | ||||
|  | ||||
|   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 = NameProxy "list" | ||||
|  | ||||
|       slice_var = nil | ||||
|       bounds = if is_slice list | ||||
|         slice = list[#list] | ||||
|         table.remove list | ||||
|         table.remove slice, 1 | ||||
|  | ||||
|         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 { | ||||
|         build.assign_one list_name, list | ||||
|         slice_var | ||||
|         build["for"] { | ||||
|           name: index_name | ||||
|           bounds: bounds | ||||
|           body: { | ||||
|             {"assign", node.names, {list_name\index 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 = apply_to_last 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 | ||||
|  | ||||
|     -- 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 | ||||
|     constructor = nil | ||||
|     properties = for tuple in *properties | ||||
|       key = tuple[1] | ||||
|       if key[1] == "key_literal" and key[2] == constructor_name | ||||
|         constructor = tuple[2] | ||||
|         nil | ||||
|       else | ||||
|         tuple | ||||
|  | ||||
|     parent_cls_name = NameProxy "parent" | ||||
|     base_name = NameProxy "base" | ||||
|     self_name = NameProxy "self" | ||||
|     cls_name = NameProxy "class" | ||||
|  | ||||
|     if not constructor | ||||
|       constructor = build.fndef { | ||||
|         args: {{"..."}} | ||||
|         arrow: "fat" | ||||
|         body: { | ||||
|           build["if"] { | ||||
|             cond: parent_cls_name | ||||
|             then: { | ||||
|               build.chain { base: "super", {"call", {"..."}} } | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     else | ||||
|       smart_node constructor | ||||
|       constructor.arrow = "fat" | ||||
|  | ||||
|     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 | ||||
|         {"string", '"', real_name} | ||||
|  | ||||
|     cls = build.table { | ||||
|       {"__init", constructor} | ||||
|       {"__base", base_name} | ||||
|       {"__name", real_name} -- "quote the string" | ||||
|       {"__parent", parent_cls_name} | ||||
|     } | ||||
|  | ||||
|     -- look up a name in the class object | ||||
|     class_lookup = build["if"] { | ||||
|       cond: {"exp", "val", "==", "nil", "and", parent_cls_name} | ||||
|       then: { | ||||
|         parent_cls_name\index"name" | ||||
|       } | ||||
|     } | ||||
|     insert class_lookup, {"else", {"val"}} | ||||
|  | ||||
|     cls_mt = build.table { | ||||
|       {"__index", build.fndef { | ||||
|         args: {{"cls"}, {"name"}} | ||||
|         body: { | ||||
|           build.assign_one LocalName"val", build.chain { | ||||
|             base: "rawget", {"call", {base_name, "name"}} | ||||
|           } | ||||
|           class_lookup | ||||
|         } | ||||
|       }} | ||||
|       {"__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) -> | ||||
|             if chain | ||||
|               slice = [item for item in *chain[3,]] | ||||
|               new_chain = {"chain", parent_cls_name} | ||||
|  | ||||
|               head = slice[1] | ||||
|  | ||||
|               if head == nil | ||||
|                 return parent_cls_name | ||||
|  | ||||
|               switch head[1] | ||||
|                 -- calling super, inject calling name and self into chain | ||||
|                 when "call" | ||||
|                   calling_name = block\get"current_block" | ||||
|                   slice[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 = head[3] | ||||
|                   insert new_chain, {"dot", head[2]} | ||||
|                   slice[1] = { "call", { "self", unpack call[2] } } | ||||
|  | ||||
|               insert new_chain, item for item in *slice | ||||
|  | ||||
|               new_chain | ||||
|             else | ||||
|               parent_cls_name | ||||
|  | ||||
|         .assign_one parent_cls_name, parent_val == "" and "nil" or parent_val | ||||
|         .assign_one base_name, {"table", properties} | ||||
|         .assign_one base_name\chain"__index", base_name | ||||
|  | ||||
|         .if { | ||||
|           cond: parent_cls_name | ||||
|           then: { | ||||
|             .chain { | ||||
|               base: "setmetatable" | ||||
|               {"call", { | ||||
|                 base_name, | ||||
|                 .chain { base: parent_cls_name,  {"dot", "__base"}} | ||||
|               }} | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|  | ||||
|         .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 | ||||
|         .if { | ||||
|           cond: {"exp", | ||||
|             parent_cls_name, "and", parent_cls_name\chain "__inherited" | ||||
|           } | ||||
|           then: { | ||||
|             parent_cls_name\chain "__inherited", {"call", { | ||||
|               parent_cls_name, cls_name | ||||
|             }} | ||||
|           } | ||||
|         } | ||||
|  | ||||
|         .group if name then { | ||||
|           .assign_one name, cls_name | ||||
|         } | ||||
|  | ||||
|         if ret | ||||
|           ret cls_name | ||||
|       } | ||||
|  | ||||
|       hoist_declarations out_body | ||||
|  | ||||
|       value = .group { | ||||
|         .group if ntype(name) == "value" then { | ||||
|           .declare names: {name} | ||||
|         } | ||||
|  | ||||
|         .do out_body | ||||
|       } | ||||
|  | ||||
|     value | ||||
| } | ||||
|  | ||||
| class Accumulator | ||||
|   body_idx: { for: 4, while: 3, foreach: 4 } | ||||
|  | ||||
|   new: => | ||||
|     @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) => | ||||
|     build.block_exp { | ||||
|       build.assign_one @accum_name, build.table! | ||||
|       build.assign_one @len_name, 0 | ||||
|       node | ||||
|       @accum_name | ||||
|     } | ||||
|  | ||||
|   -- mutates the body of a loop construct to save last value into accumulator | ||||
|   -- can optionally skip nil results | ||||
|   mutate_body: (body, skip_nil=true) => | ||||
|     val = if not skip_nil and is_singular body | ||||
|       with body[1] | ||||
|         body = {} | ||||
|     else | ||||
|       body = apply_to_last 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 = { | ||||
|       {"update", @len_name, "+=", 1} | ||||
|       build.assign_one @accum_name\index(@len_name), val | ||||
|     } | ||||
|  | ||||
|     if skip_nil | ||||
|       table.insert body, build["if"] { | ||||
|         cond: {"exp", @value_name, "!=", "nil"} | ||||
|         then: update | ||||
|       } | ||||
|     else | ||||
|       table.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] == "" | ||||
|         nil | ||||
|       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}, false | ||||
|     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 = apply_to_last 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) => | ||||
|     stub = node[#node] | ||||
|  | ||||
|     -- escape lua keywords used in dot accessors | ||||
|     for i=3,#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] } | ||||
|     elseif type(stub) == "table" and stub[1] == "colon_stub" | ||||
|       -- convert colon stub into code | ||||
|       table.remove node, #node | ||||
|  | ||||
|       base_name = NameProxy "base" | ||||
|       fn_name = NameProxy "fn" | ||||
|  | ||||
|       is_super = node[2] == "super" | ||||
|       @transform.value build.block_exp { | ||||
|         build.assign { | ||||
|           names: {base_name} | ||||
|           values: {node} | ||||
|         } | ||||
|  | ||||
|         build.assign { | ||||
|           names: {fn_name} | ||||
|           values: { | ||||
|             build.chain { base: base_name, {"dot", stub[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 } | ||||
		Reference in New Issue
	
	Block a user