1-- transform.moon
2-- Leaf Corcoran (leafot@gmail.com) 2011
3--
4-- This is part of the MoonScript compiler. See <http://moonscript.org>
5-- MoonScript is licensed under the MIT License
6--
7
8module "moonscript.transform", package.seeall
9
10types = require "moonscript.types"
11util = require "moonscript.util"
12data = require "moonscript.data"
13
14import reversed from util
15import ntype, build, smart_node, is_slice from types
16import insert from table
17
18export Statement, Value, NameProxy, LocalName, Run
19
20-- always declares as local
21class LocalName
22  new: (@name) => self[1] = "temp_name"
23  get_name: => @name
24
25class NameProxy
26  new: (@prefix) =>
27    self[1] = "temp_name"
28
29  get_name: (scope) =>
30    if not @name
31      @name = scope\free_name @prefix, true
32    @name
33
34  chain: (...) =>
35    items = {...} -- todo: fix ... propagation
36    items = for i in *items
37      if type(i) == "string"
38        {"dot", i}
39      else
40        i
41
42    build.chain {
43      base: self
44      unpack items
45    }
46
47  index: (key) =>
48    build.chain {
49      base: self, {"index", key}
50    }
51
52  __tostring: =>
53    if @name
54      ("name<%s>")\format @name
55    else
56      ("name<prefix(%s)>")\format @prefix
57
58class Run
59  new: (@fn) =>
60    self[1] = "run"
61
62  call: (state) =>
63    self.fn state
64
65-- transform the last stm is a list of stms
66-- will puke on group
67apply_to_last = (stms, fn) ->
68  -- find last (real) exp
69  last_exp_id = 0
70  for i = #stms, 1, -1
71    stm = stms[i]
72    if stm and util.moon.type(stm) != Run
73      last_exp_id = i
74      break
75
76  return for i, stm in ipairs stms
77    if i == last_exp_id
78      fn stm
79    else
80      stm
81
82-- is a body a sindle expression/statement
83is_singular = (body) ->
84  return false if #body != 1
85  if "group" == ntype body
86    is_singular body[2]
87  else
88    true
89
90constructor_name = "new"
91
92class Transformer
93  new: (@transformers, @scope) =>
94    @seen_nodes = {}
95
96  transform: (scope, node, ...) =>
97    -- print scope, node, ...
98    return node if @seen_nodes[node]
99    @seen_nodes[node] = true
100    while true
101      transformer = @transformers[ntype node]
102      res = if transformer
103        transformer(scope, node, ...) or node
104      else
105        node
106      return node if res == node
107      node = res
108
109  __call: (node, ...) =>
110    @transform @scope, node, ...
111
112  instance: (scope) =>
113    Transformer @transformers, scope
114
115  can_transform: (node) =>
116    @transformers[ntype node] != nil
117
118construct_comprehension = (inner, clauses) ->
119  current_stms = inner
120  for _, clause in reversed clauses
121    t = clause[1]
122    current_stms = if t == "for"
123      _, names, iter = unpack clause
124      {"foreach", names, iter, current_stms}
125    elseif t == "when"
126      _, cond = unpack clause
127      {"if", cond, current_stms}
128    else
129      error "Unknown comprehension clause: "..t
130    current_stms = {current_stms}
131
132  current_stms[1]
133
134Statement = Transformer {
135  assign: (node) =>
136    _, names, values = unpack node
137    -- bubble cascading assigns
138    if #values == 1 and types.cascading[ntype values[1]]
139      values[1] = @transform.statement values[1], (stm) ->
140        t = ntype stm
141        if types.is_value stm
142          {"assign", names, {stm}}
143        else
144          stm
145
146      build.group {
147        {"declare", names}
148        values[1]
149      }
150    else
151      node
152
153  export: (node) =>
154    -- assign values if they are included
155    if #node > 2
156      if node[2] == "class"
157        cls = smart_node node[3]
158        build.group {
159          {"export", {cls.name}}
160          cls
161        }
162      else
163        build.group {
164          node
165          build.assign {
166            names: node[2]
167            values: node[3]
168          }
169        }
170    else
171      nil
172
173  update: (node) =>
174    _, name, op, exp = unpack node
175    op_final = op\match "^(.+)=$"
176    error "Unknown op: "..op if not op_final
177    build.assign_one name, {"exp", name, op_final, exp}
178
179  import: (node) =>
180    _, names, source = unpack node
181
182    stubs = for name in *names
183      if type(name) == "table"
184        name
185      else
186        {"dot", name}
187
188    real_names = for name in *names
189      type(name) == "table" and name[2] or name
190
191    if type(source) == "string"
192      build.assign {
193        names: real_names
194        values: [build.chain { base: source, stub} for stub in *stubs]
195      }
196    else
197      source_name = NameProxy "table"
198      build.group {
199        {"declare", real_names}
200        build["do"] {
201          build.assign_one source_name, source
202          build.assign {
203            names: real_names
204            values: [build.chain { base: source_name, stub} for stub in *stubs]
205          }
206        }
207      }
208
209  comprehension: (node, action) =>
210    _, exp, clauses = unpack node
211
212    action = action or (exp) -> {exp}
213    construct_comprehension action(exp), clauses
214
215  -- handle cascading return decorator
216  if: (node, ret) =>
217    if ret
218      smart_node node
219      -- mutate all the bodies
220      node['then'] = apply_to_last node['then'], ret
221      for i = 4, #node
222        case = node[i]
223        body_idx = #node[i]
224        case[body_idx] = apply_to_last case[body_idx], ret
225    node
226
227  with: (node, ret) =>
228    _, exp, block = unpack node
229    scope_name = NameProxy "with"
230    build["do"] {
231      build.assign_one scope_name, exp
232      Run => @set "scope_var", scope_name
233      build.group block
234      if ret
235        ret scope_name
236    }
237
238  foreach: (node) =>
239    smart_node node
240    if ntype(node.iter) == "unpack"
241      list = node.iter[2]
242
243      index_name = NameProxy "index"
244      list_name = NameProxy "list"
245
246      slice_var = nil
247      bounds = if is_slice list
248        slice = list[#list]
249        table.remove list
250        table.remove slice, 1
251
252        slice[2] = if slice[2] and slice[2] != ""
253          max_tmp_name = NameProxy "max"
254          slice_var = build.assign_one max_tmp_name, slice[2]
255          {"exp", max_tmp_name, "<", 0
256            "and", {"length", list_name}, "+", max_tmp_name
257            "or", max_tmp_name }
258        else
259          {"length", list_name}
260
261        slice
262      else
263        {1, {"length", list_name}}
264
265      build.group {
266        build.assign_one list_name, list
267        slice_var
268        build["for"] {
269          name: index_name
270          bounds: bounds
271          body: {
272            {"assign", node.names, {list_name\index index_name}}
273            build.group node.body
274          }
275        }
276      }
277
278  switch: (node, ret) =>
279    _, exp, conds = unpack node
280    exp_name = NameProxy "exp"
281
282    -- convert switch conds into if statment conds
283    convert_cond = (cond) ->
284      t, case_exp, body = unpack cond
285      out = {}
286      insert out, t == "case" and "elseif" or "else"
287      if  t != "else"
288        insert out, {"exp", case_exp, "==", exp_name} if t != "else"
289      else
290        body = case_exp
291
292      if ret
293        body = apply_to_last body, ret
294
295      insert out, body
296
297      out
298
299    first = true
300    if_stm = {"if"}
301    for cond in *conds
302      if_cond = convert_cond cond
303      if first
304        first = false
305        insert if_stm, if_cond[2]
306        insert if_stm, if_cond[3]
307      else
308        insert if_stm, if_cond
309
310    build.group {
311      build.assign_one exp_name, exp
312      if_stm
313    }
314
315  class: (node) =>
316    _, name, parent_val, body = unpack node
317
318    -- split apart properties and statements
319    statements = {}
320    properties = {}
321    for item in *body
322      switch item[1]
323        when "stm"
324          insert statements, item[2]
325        when "props"
326          for tuple in *item[2,]
327            insert properties, tuple
328
329    -- find constructor
330    constructor = nil
331    properties = for tuple in *properties
332      if tuple[1] == constructor_name
333        constructor = tuple[2]
334        nil
335      else
336        tuple
337
338    parent_cls_name = NameProxy "parent"
339    base_name = NameProxy "base"
340    self_name = NameProxy "self"
341    cls_name = NameProxy "class"
342
343    if not constructor
344      constructor = build.fndef {
345        args: {{"..."}}
346        arrow: "fat"
347        body: {
348          build["if"] {
349            cond: parent_cls_name
350            then: {
351              build.chain { base: "super", {"call", {"..."}} }
352            }
353          }
354        }
355      }
356    else
357      smart_node constructor
358      constructor.arrow = "fat"
359
360    cls = build.table {
361      {"__init", constructor}
362      {"__base", base_name}
363      {"__name", {"string", '"', name}} -- "quote the string"
364      {"__parent", parent_cls_name}
365    }
366
367    -- look up a name in the class object
368    class_lookup = build["if"] {
369      cond: {"exp", "val", "==", "nil", "and", parent_cls_name}
370      then: {
371        parent_cls_name\index"name"
372      }
373    }
374    insert class_lookup, {"else", {"val"}}
375
376    cls_mt = build.table {
377      {"__index", build.fndef {
378        args: {{"cls"}, {"name"}}
379        body: {
380          build.assign_one LocalName"val", build.chain {
381            base: "rawget", {"call", {base_name, "name"}}
382          }
383          class_lookup
384        }
385      }}
386      {"__call", build.fndef {
387        args: {{"cls"}, {"..."}}
388        body: {
389          build.assign_one self_name, build.chain {
390            base: "setmetatable"
391            {"call", {"{}", base_name}}
392          }
393          build.chain {
394            base: "cls.__init"
395            {"call", {self_name, "..."}}
396          }
397          self_name
398        }
399      }}
400    }
401
402    cls = build.chain {
403      base: "setmetatable"
404      {"call", {cls, cls_mt}}
405    }
406
407    value = nil
408    with build
409      value = .block_exp {
410        Run =>
411          @set "super", (block, chain) ->
412            if chain
413              slice = [item for item in *chain[3,]]
414              new_chain = {"chain", parent_cls_name}
415
416              head = slice[1]
417
418              if head == nil
419                return parent_cls_name
420
421              switch head[1]
422                -- calling super, inject calling name and self into chain
423                when "call"
424                  calling_name = block\get"current_block"
425                  slice[1] = {"call", {"self", unpack head[2]}}
426                  act = if ntype(calling_name) != "value" then "index" else "dot"
427                  insert new_chain, {act, calling_name}
428
429                -- colon call on super, replace class with self as first arg
430                when "colon"
431                  call = head[3]
432                  insert new_chain, {"dot", head[2]}
433                  slice[1] = { "call", { "self", unpack call[2] } }
434
435              insert new_chain, item for item in *slice
436
437              new_chain
438            else
439              parent_cls_name
440
441        .assign_one parent_cls_name, parent_val == "" and "nil" or parent_val
442        .assign_one base_name, {"table", properties}
443        .assign_one base_name\chain"__index", base_name
444
445        build["if"] {
446          cond: parent_cls_name
447          then: {
448            .chain {
449              base: "setmetatable"
450              {"call", {
451                base_name,
452                .chain { base: parent_cls_name,  {"dot", "__base"}}
453              }}
454            }
455          }
456        }
457
458        .assign_one cls_name, cls
459        .assign_one base_name\chain"__class", cls_name
460
461        .group if #statements > 0 {
462          .assign_one LocalName"self", cls_name
463          .group statements
464        } else {}
465
466        cls_name
467      }
468
469      value = .group {
470        .declare names: {name}
471        .assign {
472          names: {name}
473          values: {value}
474        }
475      }
476
477    value
478}
479
480class Accumulator
481  body_idx: { for: 4, while: 3, foreach: 4 }
482
483  new: =>
484    @accum_name = NameProxy "accum"
485    @value_name = NameProxy "value"
486    @len_name = NameProxy "len"
487
488  -- wraps node and mutates body
489  convert: (node) =>
490    index = @body_idx[ntype node]
491    node[index] = @mutate_body node[index]
492    @wrap node
493
494  -- wrap the node into a block_exp
495  wrap: (node) =>
496    build.block_exp {
497      build.assign_one @accum_name, build.table!
498      build.assign_one @len_name, 0
499      node
500      @accum_name
501    }
502
503  -- mutates the body of a loop construct to save last value into accumulator
504  -- can optionally skip nil results
505  mutate_body: (body, skip_nil=true) =>
506    val = if not skip_nil and is_singular body
507      with body[1]
508        body = {}
509    else
510      body = apply_to_last body, (n) ->
511        build.assign_one @value_name, n
512      @value_name
513
514    update = {
515      {"update", @len_name, "+=", 1}
516      build.assign_one @accum_name\index(@len_name), val
517    }
518
519    if skip_nil
520      table.insert body, build["if"] {
521        cond: {"exp", @value_name, "!=", "nil"}
522        then: update
523      }
524    else
525      table.insert body, build.group update
526
527    body
528
529default_accumulator = (node) =>
530  Accumulator!\convert node
531
532
533implicitly_return = (scope) ->
534  fn = (stm) ->
535    t = ntype stm
536    if types.manual_return[t] or not types.is_value stm
537      stm
538    elseif types.cascading[t]
539      scope.transform.statement stm, fn
540    else
541      if t == "comprehension" and not types.comprehension_has_value stm
542        stm
543      else
544        {"return", stm}
545
546  fn
547
548Value = Transformer {
549  for: default_accumulator
550  while: default_accumulator
551  foreach: default_accumulator
552
553  comprehension: (node) =>
554    a = Accumulator!
555    node = @transform.statement node, (exp) ->
556      a\mutate_body {exp}, false
557    a\wrap node
558
559  tblcomprehension: (node) =>
560    _, key_exp, value_exp, clauses = unpack node
561
562    accum = NameProxy "tbl"
563    dest = build.chain { base: accum, {"index", key_exp} }
564    inner = build.assign_one dest, value_exp
565
566    build.block_exp {
567      build.assign_one accum, build.table!
568      construct_comprehension {inner}, clauses
569      accum
570    }
571
572  fndef: (node) =>
573    smart_node node
574    node.body = apply_to_last node.body, implicitly_return self
575    node
576
577  if: (node) => build.block_exp { node }
578  with: (node) => build.block_exp { node }
579  switch: (node) =>
580    build.block_exp { node }
581
582  -- pull out colon chain
583  chain: (node) =>
584    stub = node[#node]
585    if type(stub) == "table" and stub[1] == "colon_stub"
586      table.remove node, #node
587
588      base_name = NameProxy "base"
589      fn_name = NameProxy "fn"
590
591      is_super = node[2] == "super"
592      @transform.value build.block_exp {
593        build.assign {
594          names: {base_name}
595          values: {node}
596        }
597
598        build.assign {
599          names: {fn_name}
600          values: {
601            build.chain { base: base_name, {"dot", stub[2]} }
602          }
603        }
604
605        build.fndef {
606          args: {{"..."}}
607          body: {
608            build.chain {
609              base: fn_name, {"call", {is_super and "self" or base_name, "..."}}
610            }
611          }
612        }
613      }
614
615  block_exp: (node) =>
616    _, body = unpack node
617
618    fn = nil
619    arg_list = {}
620
621    insert body, Run =>
622      if @has_varargs
623        insert arg_list, "..."
624        insert fn.args, {"..."}
625
626    fn = smart_node build.fndef body: body
627    build.chain { base: {"parens", fn}, {"call", arg_list} }
628}
629
630