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 }
 |