mirror of
				https://github.com/KevinMidboe/linguist.git
				synced 2025-10-29 17:50:22 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			904 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			904 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| 
 | |
| 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 }
 |