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