From 541ab8334d0ea6700a97ed2af440c0113ea39efc Mon Sep 17 00:00:00 2001 From: Jason Walton Date: Tue, 26 Feb 2013 13:34:27 -0500 Subject: [PATCH] Compile to an array of CodeFragments instead of to a giant string. --- lib/coffee-script/nodes.js | 768 ++++++++++++++++++++++--------------- src/nodes.coffee | 614 ++++++++++++++++++----------- 2 files changed, 848 insertions(+), 534 deletions(-) diff --git a/lib/coffee-script/nodes.js b/lib/coffee-script/nodes.js index f554aae8..f3ee39f1 100644 --- a/lib/coffee-script/nodes.js +++ b/lib/coffee-script/nodes.js @@ -1,8 +1,11 @@ (function() { - var Access, Arr, Assign, Base, Block, Call, Class, Closure, Code, Comment, Existence, Extends, For, IDENTIFIER, IDENTIFIER_STR, IS_STRING, If, In, Index, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, METHOD_DEF, NEGATE, NO, Obj, Op, Param, Parens, RESERVED, Range, Return, SIMPLENUM, STRICT_PROSCRIBED, Scope, Slice, Splat, Switch, TAB, THIS, Throw, Try, UTILITIES, Value, While, YES, addLocationDataFn, compact, del, ends, extend, flatten, last, locationDataToString, merge, multident, some, starts, unfoldSoak, utility, _ref, _ref1, + var Access, Arr, Assign, Base, Block, Call, Class, Closure, Code, CodeFragment, Comment, Existence, Extends, For, IDENTIFIER, IDENTIFIER_STR, IS_STRING, If, In, Index, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, METHOD_DEF, NEGATE, NO, Obj, Op, PARANOID, Param, Parens, RESERVED, Range, Return, SIMPLENUM, STRICT_PROSCRIBED, Scope, Slice, Splat, Switch, TAB, THIS, Throw, Try, UTILITIES, Value, While, YES, addLocationDataFn, checkFragments, compact, del, ends, extend, flatten, fragmentsToText, last, locationDataToString, merge, multident, some, starts, unfoldSoak, utility, _ref, _ref1, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, - __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, + __slice = [].slice; + + Error.stackTraceLimit = Infinity; Scope = require('./scope').Scope; @@ -31,12 +34,75 @@ return this; }; + PARANOID = false; + + exports.CodeFragment = CodeFragment = (function() { + + function CodeFragment(parent, code) { + var _ref2; + this.code = code; + this.locationData = parent != null ? parent.locationData : void 0; + this.type = (parent != null ? (_ref2 = parent.constructor) != null ? _ref2.name : void 0 : void 0) || 'unknown'; + } + + CodeFragment.prototype.toString = function() { + return "" + this.code + [this.locationData ? ": " + locationDataToString(this.locationData) : void 0]; + }; + + return CodeFragment; + + })(); + + fragmentsToText = function(fragments) { + var fragment; + checkFragments(fragments); + return ((function() { + var _i, _len, _results; + _results = []; + for (_i = 0, _len = fragments.length; _i < _len; _i++) { + fragment = fragments[_i]; + _results.push(fragment.code); + } + return _results; + })()).join(''); + }; + + checkFragments = function(fragments, node) { + var fragment, i, inspected, nodeName, nodeStr, _i, _len; + if (node == null) { + node = null; + } + if (!PARANOID) { + return fragments; + } + nodeName = node ? " from " + node.constructor.name : ""; + if (!fragments) { + throw new Error("Fragments is null" + nodeName + ": " + fragments + "\n"); + } + if (fragments instanceof CodeFragment) { + throw new Error("Expected array of fragments but found fragment" + nodeName + ": " + fragments + "\n"); + } + for (i = _i = 0, _len = fragments.length; _i < _len; i = ++_i) { + fragment = fragments[i]; + if (!(fragment instanceof CodeFragment)) { + inspected = (require('util')).inspect(fragments); + nodeStr = node ? "node: " + (node.toString()) + "\n" : ""; + throw new Error("Expected fragment: " + i + " of " + fragments.length + nodeName + ": " + fragment + "\nFragments: " + inspected + "\n" + nodeStr); + } + } + return fragments; + }; + exports.Base = Base = (function() { function Base() {} Base.prototype.compile = function(o, lvl) { - var node; + return fragmentsToText(this.compileToFragments(o, lvl)); + }; + + Base.prototype.compileToFragments = function(o, lvl) { + var fragments, node; o = extend({}, o); if (lvl) { o.level = lvl; @@ -44,10 +110,12 @@ node = this.unfoldSoak(o) || this; node.tab = o.indent; if (o.level === LEVEL_TOP || !node.isStatement(o)) { - return node.compileNode(o); + fragments = node.compileNode(o); } else { - return node.compileClosure(o); + fragments = node.compileClosure(o); } + checkFragments(fragments, node); + return fragments; }; Base.prototype.compileClosure = function(o) { @@ -61,26 +129,21 @@ Base.prototype.cache = function(o, level, reused) { var ref, sub; if (!this.isComplex()) { - ref = level ? this.compile(o, level) : this; + ref = level ? this.compileToFragments(o, level) : this; return [ref, ref]; } else { ref = new Literal(reused || o.scope.freeVariable('ref')); sub = new Assign(ref, this); if (level) { - return [sub.compile(o, level), ref.value]; + return [sub.compileToFragments(o, level), [this.makeCode(ref.value)]]; } else { return [sub, ref]; } } }; - Base.prototype.compileLoopReference = function(o, name) { - var src, tmp; - src = tmp = this.compile(o, LEVEL_LIST); - if (!((-Infinity < +src && +src < Infinity) || IDENTIFIER.test(src) && o.scope.check(src, true))) { - src = "" + (tmp = o.scope.freeVariable(name)) + " = " + src; - } - return [src, tmp]; + Base.prototype.cacheToCodeFragments = function(cacheValues) { + return [fragmentsToText(cacheValues[0]), fragmentsToText(cacheValues[1])]; }; Base.prototype.makeReturn = function(res) { @@ -212,6 +275,28 @@ }); }; + Base.prototype.makeCode = function(code) { + return new CodeFragment(this, code); + }; + + Base.prototype.wrapInBraces = function(fragments) { + return [].concat(this.makeCode('('), fragments, this.makeCode(')')); + }; + + Base.prototype.joinFragmentArrays = function(fragmentsList, joinStr) { + var answer, fragments, i, _i, _len; + answer = []; + for (i = _i = 0, _len = fragmentsList.length; _i < _len; i = ++_i) { + fragments = fragmentsList[i]; + checkFragments(fragments, this); + if (i) { + answer.push(this.makeCode(joinStr)); + } + answer = answer.concat(fragments); + } + return answer; + }; + return Base; })(); @@ -291,62 +376,67 @@ return this; }; - Block.prototype.compile = function(o, level) { + Block.prototype.compileToFragments = function(o, level) { if (o == null) { o = {}; } if (o.scope) { - return Block.__super__.compile.call(this, o, level); + return Block.__super__.compileToFragments.call(this, o, level); } else { return this.compileRoot(o); } }; Block.prototype.compileNode = function(o) { - var code, codes, node, top, _i, _len, _ref2; + var answer, compiledNodes, fragments, index, node, top, _i, _len, _ref2; this.tab = o.indent; top = o.level === LEVEL_TOP; - codes = []; + compiledNodes = []; _ref2 = this.expressions; - for (_i = 0, _len = _ref2.length; _i < _len; _i++) { - node = _ref2[_i]; + for (index = _i = 0, _len = _ref2.length; _i < _len; index = ++_i) { + node = _ref2[index]; node = node.unwrapAll(); node = node.unfoldSoak(o) || node; if (node instanceof Block) { - codes.push(node.compileNode(o)); + compiledNodes.push(node.compileNode(o)); } else if (top) { node.front = true; - code = node.compile(o); + fragments = node.compileToFragments(o); if (!node.isStatement(o)) { - code = "" + this.tab + code + ";"; + fragments.unshift(this.makeCode("" + this.tab)); + fragments.push(this.makeCode(";")); } - codes.push(code); + compiledNodes.push(fragments); } else { - codes.push(node.compile(o, LEVEL_LIST)); + compiledNodes.push(node.compileToFragments(o, LEVEL_LIST)); } } if (top) { if (this.spaced) { - return "\n" + (codes.join('\n\n')) + "\n"; + return [].concat(this.makeCode("\n"), this.joinFragmentArrays(compiledNodes, '\n\n'), this.makeCode("\n")); } else { - return codes.join('\n'); + return this.joinFragmentArrays(compiledNodes, '\n'); } } - code = codes.join(', ') || 'void 0'; - if (codes.length > 1 && o.level >= LEVEL_LIST) { - return "(" + code + ")"; + if (compiledNodes.length) { + answer = this.joinFragmentArrays(compiledNodes, ', '); } else { - return code; + answer = [this.makeCode("void 0")]; + } + if (compiledNodes.length > 1 && o.level >= LEVEL_LIST) { + return this.wrapInBraces(answer); + } else { + return answer; } }; Block.prototype.compileRoot = function(o) { - var code, exp, i, prelude, preludeExps, rest; + var exp, fragments, i, prelude, preludeExps, rest; o.indent = o.bare ? '' : TAB; o.scope = new Scope(null, this, null); o.level = LEVEL_TOP; this.spaced = true; - prelude = ""; + prelude = []; if (!o.bare) { preludeExps = (function() { var _i, _len, _ref2, _results; @@ -364,22 +454,24 @@ rest = this.expressions.slice(preludeExps.length); this.expressions = preludeExps; if (preludeExps.length) { - prelude = "" + (this.compileNode(merge(o, { + prelude = this.compileNode(merge(o, { indent: '' - }))) + "\n"; + })); + prelude.push(this.makeCode("\n")); } this.expressions = rest; } - code = this.compileWithDeclarations(o); + fragments = this.compileWithDeclarations(o); if (o.bare) { - return code; + return fragments; } - return "" + prelude + "(function() {\n" + code + "\n}).call(this);\n"; + return [].concat(prelude, this.makeCode("(function() {\n"), fragments, this.makeCode("\n}).call(this);\n")); }; Block.prototype.compileWithDeclarations = function(o) { - var assigns, code, declars, exp, i, post, rest, scope, spaced, _i, _len, _ref2, _ref3, _ref4; - code = post = ''; + var assigns, declars, exp, fragments, i, post, rest, scope, spaced, _i, _len, _ref2, _ref3, _ref4; + fragments = []; + post = []; _ref2 = this.expressions; for (i = _i = 0, _len = _ref2.length; _i < _len; i = ++_i) { exp = _ref2[i]; @@ -394,7 +486,7 @@ if (i) { rest = this.expressions.splice(i, 9e9); _ref3 = [this.spaced, false], spaced = _ref3[0], this.spaced = _ref3[1]; - _ref4 = [this.compileNode(o), spaced], code = _ref4[0], this.spaced = _ref4[1]; + _ref4 = [this.compileNode(o), spaced], fragments = _ref4[0], this.spaced = _ref4[1]; this.expressions = rest; } post = this.compileNode(o); @@ -404,22 +496,22 @@ assigns = scope.hasAssignments; if (declars || assigns) { if (i) { - code += '\n'; + fragments.push(this.makeCode('\n')); } - code += "" + this.tab + "var "; + fragments.push(this.makeCode("" + this.tab + "var ")); if (declars) { - code += scope.declaredVariables().join(', '); + fragments.push(this.makeCode(scope.declaredVariables().join(', '))); } if (assigns) { if (declars) { - code += ",\n" + (this.tab + TAB); + fragments.push(this.makeCode(",\n" + (this.tab + TAB))); } - code += scope.assignedVariables().join(",\n" + (this.tab + TAB)); + fragments.push(this.makeCode(scope.assignedVariables().join(",\n" + (this.tab + TAB)))); } - code += ';\n'; + fragments.push(this.makeCode(';\n')); } } - return code + post; + return fragments.concat(post); }; Block.wrap = function(nodes) { @@ -474,13 +566,10 @@ }; Literal.prototype.compileNode = function(o) { - var code, _ref2; + var answer, code, _ref2; code = this.value === 'this' ? ((_ref2 = o.scope.method) != null ? _ref2.bound : void 0) ? o.scope.method.context : this.value : this.value.reserved ? "\"" + this.value + "\"" : this.value; - if (this.isStatement()) { - return "" + this.tab + code + ";"; - } else { - return code; - } + answer = this.isStatement() ? "" + this.tab + code + ";" : code; + return [this.makeCode(answer)]; }; Literal.prototype.toString = function() { @@ -504,11 +593,7 @@ Undefined.prototype.isComplex = NO; Undefined.prototype.compileNode = function(o) { - if (o.level >= LEVEL_ACCESS) { - return '(void 0)'; - } else { - return 'void 0'; - } + return [this.makeCode(o.level >= LEVEL_ACCESS ? '(void 0)' : 'void 0')]; }; return Undefined; @@ -528,7 +613,7 @@ Null.prototype.isComplex = NO; Null.prototype.compileNode = function() { - return "null"; + return [this.makeCode("null")]; }; return Null; @@ -544,7 +629,7 @@ Bool.prototype.isComplex = NO; Bool.prototype.compileNode = function() { - return this.val; + return [this.makeCode(this.val)]; }; function Bool(val) { @@ -573,18 +658,25 @@ Return.prototype.jumps = THIS; - Return.prototype.compile = function(o, level) { + Return.prototype.compileToFragments = function(o, level) { var expr, _ref2; expr = (_ref2 = this.expression) != null ? _ref2.makeReturn() : void 0; if (expr && !(expr instanceof Return)) { - return expr.compile(o, level); + return expr.compileToFragments(o, level); } else { - return Return.__super__.compile.call(this, o, level); + return Return.__super__.compileToFragments.call(this, o, level); } }; Return.prototype.compileNode = function(o) { - return this.tab + ("return" + [this.expression ? " " + (this.expression.compile(o, LEVEL_PAREN)) : void 0] + ";"); + var answer; + answer = []; + answer.push(this.makeCode(this.tab + ("return" + [this.expression ? " " : void 0]))); + if (this.expression) { + answer = answer.concat(this.expression.compileToFragments(o, LEVEL_PAREN)); + } + answer.push(this.makeCode(";")); + return answer; }; return Return; @@ -709,18 +801,18 @@ }; Value.prototype.compileNode = function(o) { - var code, prop, props, _i, _len; + var fragments, prop, props, _i, _len; this.base.front = this.front; props = this.properties; - code = this.base.compile(o, props.length ? LEVEL_ACCESS : null); - if ((this.base instanceof Parens || props.length) && SIMPLENUM.test(code)) { - code = "" + code + "."; + fragments = this.base.compileToFragments(o, (props.length ? LEVEL_ACCESS : null)); + if ((this.base instanceof Parens || props.length) && SIMPLENUM.test(fragmentsToText(fragments))) { + fragments.push(this.makeCode('.')); } for (_i = 0, _len = props.length; _i < _len; _i++) { prop = props[_i]; - code += prop.compile(o); + fragments.push.apply(fragments, prop.compileToFragments(o)); } - return code; + return fragments; }; Value.prototype.unfoldSoak = function(o) { @@ -780,7 +872,7 @@ if ((level || o.level) === LEVEL_TOP) { code = o.indent + code; } - return code; + return [this.makeCode(code)]; }; return Comment; @@ -918,60 +1010,70 @@ }; Call.prototype.compileNode = function(o) { - var arg, args, code, _ref2; + var arg, argIndex, args, compiledArgs, compiledArray, fragments, preface, _i, _len, _ref2; if ((_ref2 = this.variable) != null) { _ref2.front = this.front; } - if (code = Splat.compileSplattedArray(o, this.args, true)) { - return this.compileSplat(o, code); + compiledArray = Splat.compileSplattedArray(o, this.args, true); + if (compiledArray.length) { + return this.compileSplat(o, compiledArray); } args = this.filterImplicitObjects(this.args); - args = ((function() { - var _i, _len, _results; - _results = []; - for (_i = 0, _len = args.length; _i < _len; _i++) { - arg = args[_i]; - _results.push(arg.compile(o, LEVEL_LIST)); + compiledArgs = []; + for (argIndex = _i = 0, _len = args.length; _i < _len; argIndex = ++_i) { + arg = args[argIndex]; + if (argIndex) { + compiledArgs.push(this.makeCode(", ")); } - return _results; - })()).join(', '); - if (this.isSuper) { - return this.superReference(o) + (".call(" + (this.superThis(o)) + (args && ', ' + args) + ")"); - } else { - return (this.isNew ? 'new ' : '') + this.variable.compile(o, LEVEL_ACCESS) + ("(" + args + ")"); + compiledArgs.push.apply(compiledArgs, arg.compileToFragments(o, LEVEL_LIST)); } - }; - - Call.prototype.compileSuper = function(args, o) { - return "" + (this.superReference(o)) + ".call(" + (this.superThis(o)) + (args.length ? ', ' : '') + args + ")"; + fragments = []; + if (this.isSuper) { + preface = this.superReference(o) + (".call(" + (this.superThis(o))); + if (compiledArgs.length) { + preface += ", "; + } + fragments.push(this.makeCode(preface)); + } else { + if (this.isNew) { + fragments.push(this.makeCode('new ')); + } + fragments.push.apply(fragments, this.variable.compileToFragments(o, LEVEL_ACCESS)); + fragments.push(this.makeCode("(")); + } + fragments.push.apply(fragments, compiledArgs); + fragments.push(this.makeCode(")")); + return fragments; }; Call.prototype.compileSplat = function(o, splatArgs) { - var base, fun, idt, name, ref; + var answer, base, fun, idt, name, ref; if (this.isSuper) { - return "" + (this.superReference(o)) + ".apply(" + (this.superThis(o)) + ", " + splatArgs + ")"; + return [].concat(this.makeCode("" + (this.superReference(o)) + ".apply(" + (this.superThis(o)) + ", "), splatArgs, this.makeCode(")")); } if (this.isNew) { idt = this.tab + TAB; - return "(function(func, args, ctor) {\n" + idt + "ctor.prototype = func.prototype;\n" + idt + "var child = new ctor, result = func.apply(child, args);\n" + idt + "return Object(result) === result ? result : child;\n" + this.tab + "})(" + (this.variable.compile(o, LEVEL_LIST)) + ", " + splatArgs + ", function(){})"; + return [].concat(this.makeCode("(function(func, args, ctor) {\n" + idt + "ctor.prototype = func.prototype;\n" + idt + "var child = new ctor, result = func.apply(child, args);\n" + idt + "return Object(result) === result ? result : child;\n" + this.tab + "})("), this.variable.compileToFragments(o, LEVEL_LIST), this.makeCode(", "), splatArgs, this.makeCode(", function(){})")); } + answer = []; base = Value.wrap(this.variable); if ((name = base.properties.pop()) && base.isComplex()) { ref = o.scope.freeVariable('ref'); - fun = "(" + ref + " = " + (base.compile(o, LEVEL_LIST)) + ")" + (name.compile(o)); + answer = answer.concat(this.makeCode("(" + ref + " = "), base.compileToFragments(o, LEVEL_LIST), this.makeCode(")"), name.compileToFragments(o)); } else { - fun = base.compile(o, LEVEL_ACCESS); - if (SIMPLENUM.test(fun)) { - fun = "(" + fun + ")"; + fun = base.compileToFragments(o, LEVEL_ACCESS); + if (SIMPLENUM.test(fragmentsToText(fun))) { + fun = this.wrapInBraces(fun); } if (name) { - ref = fun; - fun += name.compile(o); + ref = fragmentsToText(fun); + fun.push.apply(fun, name.compileToFragments(o)); } else { ref = 'null'; } + answer = answer.concat(fun); } - return "" + fun + ".apply(" + ref + ", " + splatArgs + ")"; + return answer = answer.concat(this.makeCode(".apply(" + ref + ", "), splatArgs, this.makeCode(")")); }; return Call; @@ -989,8 +1091,8 @@ Extends.prototype.children = ['child', 'parent']; - Extends.prototype.compile = function(o) { - return new Call(Value.wrap(new Literal(utility('extends'))), [this.child, this.parent]).compile(o); + Extends.prototype.compileToFragments = function(o) { + return new Call(Value.wrap(new Literal(utility('extends'))), [this.child, this.parent]).compileToFragments(o); }; return Extends; @@ -1009,14 +1111,16 @@ Access.prototype.children = ['name']; - Access.prototype.compile = function(o) { + Access.prototype.compileToFragments = function(o) { var name; - name = this.name.compile(o); - if (IDENTIFIER.test(name)) { - return "." + name; + name = this.name.compileToFragments(o); + if (IDENTIFIER.test(fragmentsToText(name))) { + name.unshift(this.makeCode(".")); } else { - return "[" + name + "]"; + name.unshift(this.makeCode("[")); + name.push(this.makeCode("]")); } + return name; }; Access.prototype.isComplex = NO; @@ -1035,8 +1139,8 @@ Index.prototype.children = ['index']; - Index.prototype.compile = function(o) { - return "[" + (this.index.compile(o, LEVEL_PAREN)) + "]"; + Index.prototype.compileToFragments = function(o) { + return [].concat(this.makeCode("["), this.index.compileToFragments(o, LEVEL_PAREN), this.makeCode("]")); }; Index.prototype.isComplex = function() { @@ -1065,10 +1169,10 @@ o = merge(o, { top: true }); - _ref2 = this.from.cache(o, LEVEL_LIST), this.fromC = _ref2[0], this.fromVar = _ref2[1]; - _ref3 = this.to.cache(o, LEVEL_LIST), this.toC = _ref3[0], this.toVar = _ref3[1]; + _ref2 = this.cacheToCodeFragments(this.from.cache(o, LEVEL_LIST)), this.fromC = _ref2[0], this.fromVar = _ref2[1]; + _ref3 = this.cacheToCodeFragments(this.to.cache(o, LEVEL_LIST)), this.toC = _ref3[0], this.toVar = _ref3[1]; if (step = del(o, 'step')) { - _ref4 = step.cache(o, LEVEL_LIST), this.step = _ref4[0], this.stepVar = _ref4[1]; + _ref4 = this.cacheToCodeFragments(step.cache(o, LEVEL_LIST)), this.step = _ref4[0], this.stepVar = _ref4[1]; } _ref5 = [this.fromVar.match(SIMPLENUM), this.toVar.match(SIMPLENUM)], this.fromNum = _ref5[0], this.toNum = _ref5[1]; if (this.stepVar) { @@ -1104,7 +1208,7 @@ if (namedIndex) { stepPart = "" + idxName + " = " + stepPart; } - return "" + varPart + "; " + condPart + "; " + stepPart; + return [this.makeCode("" + varPart + "; " + condPart + "; " + stepPart)]; }; Range.prototype.compileArray = function(o) { @@ -1118,7 +1222,7 @@ if (this.exclusive) { range.pop(); } - return "[" + (range.join(', ')) + "]"; + return [this.makeCode("[" + (range.join(', ')) + "]")]; } idt = this.tab + TAB; i = o.scope.freeVariable('i'); @@ -1126,7 +1230,7 @@ pre = "\n" + idt + result + " = [];"; if (this.fromNum && this.toNum) { o.index = i; - body = this.compileNode(o); + body = fragmentsToText(this.compileNode(o)); } else { vars = ("" + i + " = " + this.fromC) + (this.toC !== this.toVar ? ", " + this.toC : ''); cond = "" + this.fromVar + " <= " + this.toVar; @@ -1141,7 +1245,7 @@ if (hasArgs(this.from) || hasArgs(this.to)) { args = ', arguments'; } - return "(function() {" + pre + "\n" + idt + "for (" + body + ")" + post + "}).apply(this" + (args != null ? args : '') + ")"; + return [this.makeCode("(function() {" + pre + "\n" + idt + "for (" + body + ")" + post + "}).apply(this" + (args != null ? args : '') + ")")]; }; return Range; @@ -1160,14 +1264,17 @@ } Slice.prototype.compileNode = function(o) { - var compiled, from, fromStr, to, toStr, _ref2; + var compiled, compiledText, from, fromCompiled, to, toStr, _ref2; _ref2 = this.range, to = _ref2.to, from = _ref2.from; - fromStr = from && from.compile(o, LEVEL_PAREN) || '0'; - compiled = to && to.compile(o, LEVEL_PAREN); - if (to && !(!this.range.exclusive && +compiled === -1)) { - toStr = ', ' + (this.range.exclusive ? compiled : SIMPLENUM.test(compiled) ? "" + (+compiled + 1) : (compiled = to.compile(o, LEVEL_ACCESS), "+" + compiled + " + 1 || 9e9")); + fromCompiled = from && from.compileToFragments(o, LEVEL_PAREN) || [this.makeCode('0')]; + if (to) { + compiled = to.compileToFragments(o, LEVEL_PAREN); + compiledText = fragmentsToText(compiled); + if (!(!this.range.exclusive && +compiledText === -1)) { + toStr = ', ' + (this.range.exclusive ? compiledText : SIMPLENUM.test(compiledText) ? "" + (+compiledText + 1) : (compiled = to.compileToFragments(o, LEVEL_ACCESS), "+" + (fragmentsToText(compiled)) + " + 1 || 9e9")); + } } - return ".slice(" + fromStr + (toStr || '') + ")"; + return [this.makeCode(".slice(" + (fragmentsToText(fromCompiled)) + (toStr || '') + ")")]; }; return Slice; @@ -1186,10 +1293,10 @@ Obj.prototype.children = ['properties']; Obj.prototype.compileNode = function(o) { - var i, idt, indent, join, lastNoncom, node, obj, prop, props, _i, _len; + var answer, i, idt, indent, join, lastNoncom, node, prop, props, _i, _j, _len, _len1; props = this.properties; if (!props.length) { - return (this.front ? '({})' : '{}'); + return [this.makeCode(this.front ? '({})' : '{}')]; } if (this.generated) { for (_i = 0, _len = props.length; _i < _len; _i++) { @@ -1201,32 +1308,34 @@ } idt = o.indent += TAB; lastNoncom = this.lastNonComment(this.properties); - props = (function() { - var _j, _len1, _results; - _results = []; - for (i = _j = 0, _len1 = props.length; _j < _len1; i = ++_j) { - prop = props[i]; - join = i === props.length - 1 ? '' : prop === lastNoncom || prop instanceof Comment ? '\n' : ',\n'; - indent = prop instanceof Comment ? '' : idt; - if (prop instanceof Value && prop["this"]) { - prop = new Assign(prop.properties[0].name, prop, 'object'); - } - if (!(prop instanceof Comment)) { - if (!(prop instanceof Assign)) { - prop = new Assign(prop, prop, 'object'); - } - (prop.variable.base || prop.variable).asKey = true; - } - _results.push(indent + prop.compile(o, LEVEL_TOP) + join); + answer = []; + for (i = _j = 0, _len1 = props.length; _j < _len1; i = ++_j) { + prop = props[i]; + join = i === props.length - 1 ? '' : prop === lastNoncom || prop instanceof Comment ? '\n' : ',\n'; + indent = prop instanceof Comment ? '' : idt; + if (prop instanceof Value && prop["this"]) { + prop = new Assign(prop.properties[0].name, prop, 'object'); } - return _results; - })(); - props = props.join(''); - obj = "{" + (props && '\n' + props + '\n' + this.tab) + "}"; + if (!(prop instanceof Comment)) { + if (!(prop instanceof Assign)) { + prop = new Assign(prop, prop, 'object'); + } + (prop.variable.base || prop.variable).asKey = true; + } + if (indent) { + answer.push(this.makeCode(indent)); + } + answer.push.apply(answer, prop.compileToFragments(o, LEVEL_TOP)); + if (join) { + answer.push(this.makeCode(join)); + } + } + answer.unshift(this.makeCode("{" + (props.length && '\n'))); + answer.push(this.makeCode("" + (props.length && '\n' + this.tab) + "}")); if (this.front) { - return "(" + obj + ")"; + return this.wrapInBraces(answer); } else { - return obj; + return answer; } }; @@ -1259,29 +1368,41 @@ Arr.prototype.filterImplicitObjects = Call.prototype.filterImplicitObjects; Arr.prototype.compileNode = function(o) { - var code, obj, objs; + var answer, compiledObjs, fragments, index, obj, objs, _i, _len; if (!this.objects.length) { - return '[]'; + return [this.makeCode('[]')]; } o.indent += TAB; objs = this.filterImplicitObjects(this.objects); - if (code = Splat.compileSplattedArray(o, objs)) { - return code; + answer = Splat.compileSplattedArray(o, objs); + if (answer.length) { + return answer; } - code = ((function() { + answer = []; + compiledObjs = (function() { var _i, _len, _results; _results = []; for (_i = 0, _len = objs.length; _i < _len; _i++) { obj = objs[_i]; - _results.push(obj.compile(o, LEVEL_LIST)); + _results.push(obj.compileToFragments(o, LEVEL_LIST)); } return _results; - })()).join(', '); - if (code.indexOf('\n') >= 0) { - return "[\n" + o.indent + code + "\n" + this.tab + "]"; - } else { - return "[" + code + "]"; + })(); + for (index = _i = 0, _len = compiledObjs.length; _i < _len; index = ++_i) { + fragments = compiledObjs[index]; + if (index) { + answer.push(this.makeCode(", ")); + } + answer.push.apply(answer, fragments); } + if ((fragmentsToText(answer)).indexOf('\n') >= 0) { + answer.unshift(this.makeCode("[\n" + o.indent)); + answer.push(this.makeCode("\n" + this.tab + "]")); + } else { + answer.unshift(this.makeCode("[")); + answer.push(this.makeCode("]")); + } + return answer; }; Arr.prototype.assigns = function(name) { @@ -1489,7 +1610,7 @@ if (this.variable) { klass = new Assign(this.variable, klass); } - return klass.compile(o); + return klass.compileToFragments(o); }; return Class; @@ -1528,7 +1649,7 @@ }; Assign.prototype.compileNode = function(o) { - var isValue, match, name, val, varBase, _ref2, _ref3, _ref4, _ref5; + var answer, compiledName, isValue, match, name, val, varBase, _ref2, _ref3, _ref4, _ref5; if (isValue = this.variable instanceof Value) { if (this.variable.isArray() || this.variable.isObject()) { return this.compilePatternMatch(o); @@ -1540,7 +1661,8 @@ return this.compileConditional(o); } } - name = this.variable.compile(o, LEVEL_LIST); + compiledName = this.variable.compileToFragments(o, LEVEL_LIST); + name = fragmentsToText(compiledName); if (!this.context) { if (!(varBase = this.variable.unwrapAll()).isAssignable()) { throw SyntaxError("\"" + (this.variable.compile(o)) + "\" cannot be assigned."); @@ -1559,27 +1681,27 @@ } this.value.name = (_ref3 = (_ref4 = (_ref5 = match[2]) != null ? _ref5 : match[3]) != null ? _ref4 : match[4]) != null ? _ref3 : match[5]; } - val = this.value.compile(o, LEVEL_LIST); + val = this.value.compileToFragments(o, LEVEL_LIST); if (this.context === 'object') { - return "" + name + ": " + val; + return compiledName.concat(this.makeCode(": "), val); } - val = name + (" " + (this.context || '=') + " ") + val; + answer = compiledName.concat(this.makeCode(" " + (this.context || '=') + " "), val); if (o.level <= LEVEL_LIST) { - return val; + return answer; } else { - return "(" + val + ")"; + return this.wrapInBraces(answer); } }; Assign.prototype.compilePatternMatch = function(o) { - var acc, assigns, code, i, idx, isObject, ivar, name, obj, objects, olen, ref, rest, splat, top, val, value, vvar, _i, _len, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7; + var acc, assigns, code, fragments, i, idx, isObject, ivar, name, obj, objects, olen, ref, rest, splat, top, val, value, vvar, vvarText, _i, _len, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7; top = o.level === LEVEL_TOP; value = this.value; objects = this.variable.base.objects; if (!(olen = objects.length)) { - code = value.compile(o); + code = value.compileToFragments(o); if (o.level >= LEVEL_OP) { - return "(" + code + ")"; + return this.wrapInBraces(code); } else { return code; } @@ -1599,14 +1721,16 @@ } return new Assign(obj, value, null, { param: this.param - }).compile(o, LEVEL_TOP); + }).compileToFragments(o, LEVEL_TOP); } - vvar = value.compile(o, LEVEL_LIST); + vvar = value.compileToFragments(o, LEVEL_LIST); + vvarText = fragmentsToText(vvar); assigns = []; splat = false; - if (!IDENTIFIER.test(vvar) || this.variable.assigns(vvar)) { - assigns.push("" + (ref = o.scope.freeVariable('ref')) + " = " + vvar); - vvar = ref; + if (!IDENTIFIER.test(vvarText) || this.variable.assigns(vvarText)) { + assigns.push([this.makeCode("" + (ref = o.scope.freeVariable('ref')) + " = ")].concat(__slice.call(vvar))); + vvar = [this.makeCode(ref)]; + vvarText = ref; } for (i = _i = 0, _len = objects.length; _i < _len; i = ++_i) { obj = objects[i]; @@ -1625,10 +1749,10 @@ if (!splat && obj instanceof Splat) { name = obj.name.unwrap().value; obj = obj.unwrap(); - val = "" + olen + " <= " + vvar + ".length ? " + (utility('slice')) + ".call(" + vvar + ", " + i; + val = "" + olen + " <= " + vvarText + ".length ? " + (utility('slice')) + ".call(" + vvarText + ", " + i; if (rest = olen - i - 1) { ivar = o.scope.freeVariable('i'); - val += ", " + ivar + " = " + vvar + ".length - " + rest + ") : (" + ivar + " = " + i + ", [])"; + val += ", " + ivar + " = " + vvarText + ".length - " + rest + ") : (" + ivar + " = " + i + ", [])"; } else { val += ") : []"; } @@ -1637,7 +1761,7 @@ } else { name = obj.unwrap().value; if (obj instanceof Splat) { - obj = obj.name.compile(o); + obj = obj.name.compileToFragments(o); throw new SyntaxError("multiple splats are disallowed in an assignment: " + obj + "..."); } if (typeof idx === 'number') { @@ -1646,7 +1770,7 @@ } else { acc = isObject && IDENTIFIER.test(idx.unwrap().value || 0); } - val = Value.wrap(new Literal(vvar), [new (acc ? Access : Index)(idx)]); + val = Value.wrap(new Literal(vvarText), [new (acc ? Access : Index)(idx)]); } if ((name != null) && __indexOf.call(RESERVED, name) >= 0) { throw new SyntaxError("assignment to a reserved word: " + (obj.compile(o)) + " = " + (val.compile(o))); @@ -1654,16 +1778,16 @@ assigns.push(new Assign(obj, val, null, { param: this.param, subpattern: true - }).compile(o, LEVEL_LIST)); + }).compileToFragments(o, LEVEL_LIST)); } if (!(top || this.subpattern)) { assigns.push(vvar); } - code = assigns.join(', '); + fragments = this.joinFragmentArrays(assigns, ', '); if (o.level < LEVEL_LIST) { - return code; + return fragments; } else { - return "(" + code + ")"; + return this.wrapInBraces(fragments); } }; @@ -1676,14 +1800,18 @@ if (__indexOf.call(this.context, "?") >= 0) { o.isExistentialEquals = true; } - return Op.create(this.context.slice(0, -1), left, new Assign(right, this.value, '=')).compile(o); + return Op.create(this.context.slice(0, -1), left, new Assign(right, this.value, '=')).compileToFragments(o); }; Assign.prototype.compileSplice = function(o) { - var code, exclusive, from, fromDecl, fromRef, name, to, valDef, valRef, _ref2, _ref3, _ref4; + var answer, exclusive, from, fromDecl, fromRef, name, to, valDef, valRef, _ref2, _ref3, _ref4; _ref2 = this.variable.properties.pop().range, from = _ref2.from, to = _ref2.to, exclusive = _ref2.exclusive; name = this.variable.compile(o); - _ref3 = (from != null ? from.cache(o, LEVEL_OP) : void 0) || ['0', '0'], fromDecl = _ref3[0], fromRef = _ref3[1]; + if (from) { + _ref3 = this.cacheToCodeFragments(from.cache(o, LEVEL_OP)), fromDecl = _ref3[0], fromRef = _ref3[1]; + } else { + fromDecl = fromRef = '0'; + } if (to) { if ((from != null ? from.isSimpleNumber() : void 0) && to.isSimpleNumber()) { to = +to.compile(o) - +fromRef; @@ -1700,11 +1828,11 @@ to = "9e9"; } _ref4 = this.value.cache(o, LEVEL_LIST), valDef = _ref4[0], valRef = _ref4[1]; - code = "[].splice.apply(" + name + ", [" + fromDecl + ", " + to + "].concat(" + valDef + ")), " + valRef; + answer = [].concat(this.makeCode("[].splice.apply(" + name + ", [" + fromDecl + ", " + to + "].concat("), valDef, this.makeCode(")), "), valRef); if (o.level > LEVEL_TOP) { - return "(" + code + ")"; + return this.wrapInBraces(answer); } else { - return code; + return answer; } }; @@ -1734,7 +1862,7 @@ Code.prototype.jumps = NO; Code.prototype.compileNode = function(o) { - var code, exprs, i, idt, lit, name, p, param, params, ref, splats, uniqs, val, wasEmpty, _i, _j, _k, _l, _len, _len1, _len2, _len3, _len4, _len5, _m, _n, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8; + var answer, code, exprs, i, idt, lit, name, p, param, params, ref, splats, uniqs, val, wasEmpty, _i, _j, _k, _l, _len, _len1, _len2, _len3, _len4, _len5, _len6, _m, _n, _o, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7, _ref8; o.scope = new Scope(o.scope, this.body, this); o.scope.shared = del(o, 'sharedScope'); o.indent += TAB; @@ -1809,7 +1937,8 @@ } for (i = _m = 0, _len4 = params.length; _m < _len4; i = ++_m) { p = params[i]; - o.scope.parameter(params[i] = p.compile(o)); + params[i] = p.compileToFragments(o); + o.scope.parameter(fragmentsToText(params[i])); } uniqs = []; _ref7 = this.paramNames(); @@ -1835,18 +1964,27 @@ if (this.ctor) { code += ' ' + this.name; } - code += '(' + params.join(', ') + ') {'; - if (!this.body.isEmpty()) { - code += "\n" + (this.body.compileWithDeclarations(o)) + "\n" + this.tab; + code += '('; + answer = [this.makeCode(code)]; + for (i = _o = 0, _len6 = params.length; _o < _len6; i = ++_o) { + p = params[i]; + if (i) { + answer.push(this.makeCode(", ")); + } + answer.push.apply(answer, p); } - code += '}'; + answer.push(this.makeCode(') {')); + if (!this.body.isEmpty()) { + answer = answer.concat(this.makeCode("\n"), this.body.compileWithDeclarations(o), this.makeCode("\n" + this.tab)); + } + answer.push(this.makeCode('}')); if (this.ctor) { - return this.tab + code; + return [this.makeCode(this.tab)].concat(__slice.call(answer)); } if (this.front || (o.level >= LEVEL_ACCESS)) { - return "(" + code + ")"; + return this.wrapInBraces(answer); } else { - return code; + return answer; } }; @@ -1887,8 +2025,8 @@ Param.prototype.children = ['name', 'value']; - Param.prototype.compile = function(o) { - return this.name.compile(o, LEVEL_LIST); + Param.prototype.compileToFragments = function(o) { + return this.name.compileToFragments(o, LEVEL_LIST); }; Param.prototype.asReference = function(o) { @@ -1979,11 +2117,11 @@ return this.name.assigns(name); }; - Splat.prototype.compile = function(o) { + Splat.prototype.compileToFragments = function(o) { if (this.index != null) { return this.compileParam(o); } else { - return this.name.compile(o); + return this.name.compileToFragments(o); } }; @@ -1992,29 +2130,32 @@ }; Splat.compileSplattedArray = function(o, list, apply) { - var args, base, code, i, index, node, _i, _len; + var args, base, compiledNode, concatPart, fragments, i, index, node, _i, _len; index = -1; while ((node = list[++index]) && !(node instanceof Splat)) { continue; } if (index >= list.length) { - return ''; + return []; } if (list.length === 1) { - code = list[0].compile(o, LEVEL_LIST); + node = list[0]; + fragments = node.compileToFragments(o, LEVEL_LIST); if (apply) { - return code; + return fragments; } - return "" + (utility('slice')) + ".call(" + code + ")"; + return [].concat(node.makeCode("" + (utility('slice')) + ".call("), fragments, node.makeCode(")")); } args = list.slice(index); for (i = _i = 0, _len = args.length; _i < _len; i = ++_i) { node = args[i]; - code = node.compile(o, LEVEL_LIST); - args[i] = node instanceof Splat ? "" + (utility('slice')) + ".call(" + code + ")" : "[" + code + "]"; + compiledNode = node.compileToFragments(o, LEVEL_LIST); + args[i] = node instanceof Splat ? [].concat(node.makeCode("" + (utility('slice')) + ".call("), compiledNode, node.makeCode(")")) : [].concat(node.makeCode("["), compiledNode, node.makeCode("]")); } if (index === 0) { - return args[0] + (".concat(" + (args.slice(1).join(', ')) + ")"); + node = list[0]; + concatPart = node.joinFragmentArrays(args.slice(1), ', '); + return args[0].concat(node.makeCode(".concat("), concatPart, node.makeCode(")")); } base = (function() { var _j, _len1, _ref2, _results; @@ -2022,11 +2163,13 @@ _results = []; for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) { node = _ref2[_j]; - _results.push(node.compile(o, LEVEL_LIST)); + _results.push(node.compileToFragments(o, LEVEL_LIST)); } return _results; })(); - return "[" + (base.join(', ')) + "].concat(" + (args.join(', ')) + ")"; + base = list[0].joinFragmentArrays(base, ', '); + concatPart = list[index].joinFragmentArrays(args, ', '); + return [].concat(list[0].makeCode("["), base, list[index].makeCode("].concat("), concatPart, (last(list)).makeCode(")")); }; return Splat; @@ -2080,7 +2223,7 @@ }; While.prototype.compileNode = function(o) { - var body, code, rvar, set; + var answer, body, rvar, set; o.indent += TAB; set = ''; body = this.body; @@ -2100,13 +2243,13 @@ } } } - body = "\n" + (body.compile(o, LEVEL_TOP)) + "\n" + this.tab; + body = [].concat(this.makeCode("\n"), body.compileToFragments(o, LEVEL_TOP), this.makeCode("\n" + this.tab)); } - code = set + this.tab + ("while (" + (this.condition.compile(o, LEVEL_PAREN)) + ") {" + body + "}"); + answer = [].concat(this.makeCode(set + this.tab + "while ("), this.condition.compileToFragments(o, LEVEL_PAREN), this.makeCode(") {"), body, this.makeCode("}")); if (this.returns) { - code += "\n" + this.tab + "return " + rvar + ";"; + answer.push(this.makeCode("\n" + this.tab + "return " + rvar + ";")); } - return code; + return answer; }; return While; @@ -2231,7 +2374,7 @@ }; Op.prototype.compileNode = function(o) { - var code, isChain, _ref2, _ref3; + var answer, isChain, _ref2, _ref3; isChain = this.isChainable() && this.first.isChainable(); if (!isChain) { this.first.front = this.front; @@ -2251,20 +2394,20 @@ if (this.operator === '?') { return this.compileExistence(o); } - code = this.first.compile(o, LEVEL_OP) + ' ' + this.operator + ' ' + this.second.compile(o, LEVEL_OP); + answer = [].concat(this.first.compileToFragments(o, LEVEL_OP), this.makeCode(' ' + this.operator + ' '), this.second.compileToFragments(o, LEVEL_OP)); if (o.level <= LEVEL_OP) { - return code; + return answer; } else { - return "(" + code + ")"; + return this.wrapInBraces(answer); } }; Op.prototype.compileChain = function(o) { - var code, fst, shared, _ref2; + var fragments, fst, shared, _ref2; _ref2 = this.first.second.cache(o), this.first.second = _ref2[0], shared = _ref2[1]; - fst = this.first.compile(o, LEVEL_OP); - code = "" + fst + " " + (this.invert ? '&&' : '||') + " " + (shared.compile(o)) + " " + this.operator + " " + (this.second.compile(o, LEVEL_OP)); - return "(" + code + ")"; + fst = this.first.compileToFragments(o, LEVEL_OP); + fragments = fst.concat(this.makeCode(" " + (this.invert ? '&&' : '||') + " "), shared.compileToFragments(o), this.makeCode(" " + this.operator + " "), this.second.compileToFragments(o, LEVEL_OP)); + return this.wrapInBraces(fragments); }; Op.prototype.compileExistence = function(o) { @@ -2278,31 +2421,33 @@ } return new If(new Existence(fst), ref, { type: 'if' - }).addElse(this.second).compile(o); + }).addElse(this.second).compileToFragments(o); }; Op.prototype.compileUnary = function(o) { var op, parts, plusMinus; - parts = [op = this.operator]; + parts = []; + op = this.operator; + parts.push([this.makeCode(op)]); if (op === '!' && this.first instanceof Existence) { this.first.negated = !this.first.negated; - return this.first.compile(o); + return this.first.compileToFragments(o); } if (o.level >= LEVEL_ACCESS) { - return (new Parens(this)).compile(o); + return (new Parens(this)).compileToFragments(o); } plusMinus = op === '+' || op === '-'; if ((op === 'new' || op === 'typeof' || op === 'delete') || plusMinus && this.first instanceof Op && this.first.operator === op) { - parts.push(' '); + parts.push([this.makeCode(' ')]); } if ((plusMinus && this.first instanceof Op) || (op === 'new' && this.first.isStatement(o))) { this.first = new Parens(this.first); } - parts.push(this.first.compile(o, LEVEL_OP)); + parts.push(this.first.compileToFragments(o, LEVEL_OP)); if (this.flip) { parts.reverse(); } - return parts.join(''); + return this.joinFragmentArrays(parts, ''); }; Op.prototype.toString = function(idt) { @@ -2346,42 +2491,40 @@ }; In.prototype.compileOrTest = function(o) { - var cmp, cnj, i, item, ref, sub, tests, _ref2, _ref3; + var cmp, cnj, i, item, ref, sub, tests, _i, _len, _ref2, _ref3, _ref4; if (this.array.base.objects.length === 0) { - return "" + (!!this.negated); + return [this.makeCode("" + (!!this.negated))]; } _ref2 = this.object.cache(o, LEVEL_OP), sub = _ref2[0], ref = _ref2[1]; _ref3 = this.negated ? [' !== ', ' && '] : [' === ', ' || '], cmp = _ref3[0], cnj = _ref3[1]; - tests = (function() { - var _i, _len, _ref4, _results; - _ref4 = this.array.base.objects; - _results = []; - for (i = _i = 0, _len = _ref4.length; _i < _len; i = ++_i) { - item = _ref4[i]; - _results.push((i ? ref : sub) + cmp + item.compile(o, LEVEL_ACCESS)); + tests = []; + _ref4 = this.array.base.objects; + for (i = _i = 0, _len = _ref4.length; _i < _len; i = ++_i) { + item = _ref4[i]; + if (i) { + tests.push(this.makeCode(cnj)); } - return _results; - }).call(this); - tests = tests.join(cnj); + tests = tests.concat((i ? ref : sub), this.makeCode(cmp), item.compileToFragments(o, LEVEL_ACCESS)); + } if (o.level < LEVEL_OP) { return tests; } else { - return "(" + tests + ")"; + return this.wrapInBraces(tests); } }; In.prototype.compileLoopTest = function(o) { - var code, ref, sub, _ref2; + var fragments, ref, sub, _ref2; _ref2 = this.object.cache(o, LEVEL_LIST), sub = _ref2[0], ref = _ref2[1]; - code = utility('indexOf') + (".call(" + (this.array.compile(o, LEVEL_LIST)) + ", " + ref + ") ") + (this.negated ? '< 0' : '>= 0'); - if (sub === ref) { - return code; + fragments = [].concat(this.makeCode(utility('indexOf') + ".call("), this.array.compileToFragments(o, LEVEL_LIST), this.makeCode(", "), ref, this.makeCode(") " + (this.negated ? '< 0' : '>= 0'))); + if ((fragmentsToText(sub)) === (fragmentsToText(ref))) { + return fragments; } - code = sub + ', ' + code; + fragments = sub.concat(this.makeCode(', '), fragments); if (o.level < LEVEL_LIST) { - return code; + return fragments; } else { - return "(" + code + ")"; + return this.wrapInBraces(fragments); } }; @@ -2426,7 +2569,7 @@ Try.prototype.compileNode = function(o) { var catchPart, ensurePart, placeholder, tryPart; o.indent += TAB; - tryPart = this.attempt.compile(o, LEVEL_TOP); + tryPart = this.attempt.compileToFragments(o, LEVEL_TOP); catchPart = (function() { var _base, _ref2; if (this.recovery) { @@ -2441,13 +2584,15 @@ if (!o.scope.check(this.error.value)) { o.scope.add(this.error.value, 'param'); } - return " catch (" + (this.error.compile(o)) + ") {\n" + (this.recovery.compile(o, LEVEL_TOP)) + "\n" + this.tab + "}"; + return [].concat(this.makeCode(" catch ("), this.error.compileToFragments(o), this.makeCode(") {\n"), this.recovery.compileToFragments(o, LEVEL_TOP), this.makeCode("\n" + this.tab + "}")); } else if (!(this.ensure || this.recovery)) { - return ' catch (_error) {}'; + return [this.makeCode(' catch (_error) {}')]; + } else { + return []; } }).call(this); - ensurePart = this.ensure ? " finally {\n" + (this.ensure.compile(o, LEVEL_TOP)) + "\n" + this.tab + "}" : ''; - return "" + this.tab + "try {\n" + tryPart + "\n" + this.tab + "}" + (catchPart || '') + ensurePart; + ensurePart = this.ensure ? [].concat(this.makeCode(" finally {\n"), this.ensure.compileToFragments(o, LEVEL_TOP), this.makeCode("\n" + this.tab + "}")) : []; + return [].concat(this.makeCode("" + this.tab + "try {\n"), tryPart, this.makeCode("\n" + this.tab + "}"), catchPart, ensurePart); }; return Try; @@ -2471,7 +2616,7 @@ Throw.prototype.makeReturn = THIS; Throw.prototype.compileNode = function(o) { - return this.tab + ("throw " + (this.expression.compile(o)) + ";"); + return [].concat(this.makeCode(this.tab + "throw "), this.expression.compileToFragments(o), this.makeCode(";")); }; return Throw; @@ -2500,11 +2645,7 @@ } else { code = "" + code + " " + (this.negated ? '==' : '!=') + " null"; } - if (o.level <= LEVEL_COND) { - return code; - } else { - return "(" + code + ")"; - } + return [this.makeCode(o.level <= LEVEL_COND ? code : "(" + code + ")")]; }; return Existence; @@ -2530,18 +2671,18 @@ }; Parens.prototype.compileNode = function(o) { - var bare, code, expr; + var bare, expr, fragments; expr = this.body.unwrap(); if (expr instanceof Value && expr.isAtomic()) { expr.front = this.front; - return expr.compile(o); + return expr.compileToFragments(o); } - code = expr.compile(o, LEVEL_PAREN); + fragments = expr.compileToFragments(o, LEVEL_PAREN); bare = o.level < LEVEL_OP && (expr instanceof Op || expr instanceof Call || (expr instanceof For && expr.returns)); if (bare) { - return code; + return fragments; } else { - return "(" + code + ")"; + return this.wrapInBraces(fragments); } }; @@ -2579,7 +2720,7 @@ For.prototype.children = ['body', 'source', 'guard', 'step']; For.prototype.compileNode = function(o) { - var body, compare, compareDown, declare, declareDown, defPart, down, forPart, guardPart, idt1, increment, index, ivar, kvar, kvarAssign, lastJumps, lvar, name, namePart, ref, resultPart, returnResult, rvar, scope, source, step, stepNum, stepVar, svar, varPart, _ref2, _ref3; + var body, bodyFragments, compare, compareDown, declare, declareDown, defPart, defPartFragments, down, forPartFragments, guardPart, idt1, increment, index, ivar, kvar, kvarAssign, lastJumps, lvar, name, namePart, ref, resultPart, returnResult, rvar, scope, source, step, stepNum, stepVar, svar, varPart, _ref2, _ref3; body = Block.wrap([this.body]); lastJumps = (_ref2 = last(body.expressions)) != null ? _ref2.jumps() : void 0; if (lastJumps && lastJumps instanceof Return) { @@ -2587,8 +2728,8 @@ } source = this.range ? this.source.base : this.source; scope = o.scope; - name = this.name && this.name.compile(o, LEVEL_LIST); - index = this.index && this.index.compile(o, LEVEL_LIST); + name = this.name && (this.name.compile(o, LEVEL_LIST)); + index = this.index && (this.index.compile(o, LEVEL_LIST)); if (name && !this.pattern) { scope.find(name); } @@ -2602,7 +2743,7 @@ kvar = (this.range && name) || index || ivar; kvarAssign = kvar !== ivar ? "" + kvar + " = " : ""; if (this.step && !this.range) { - _ref3 = this.step.cache(o, LEVEL_LIST), step = _ref3[0], stepVar = _ref3[1]; + _ref3 = this.cacheToCodeFragments(this.step.cache(o, LEVEL_LIST)), step = _ref3[0], stepVar = _ref3[1]; stepNum = stepVar.match(SIMPLENUM); } if (this.pattern) { @@ -2613,7 +2754,7 @@ defPart = ''; idt1 = this.tab + TAB; if (this.range) { - forPart = source.compile(merge(o, { + forPartFragments = source.compileToFragments(merge(o, { index: ivar, name: name, step: this.step @@ -2652,7 +2793,7 @@ } else { increment = "" + (kvar !== ivar ? "++" + ivar : "" + ivar + "++"); } - forPart = "" + declare + "; " + compare + "; " + kvarAssign + increment; + forPartFragments = [this.makeCode("" + declare + "; " + compare + "; " + kvarAssign + increment)]; } } if (this.returns) { @@ -2672,28 +2813,28 @@ if (this.pattern) { body.expressions.unshift(new Assign(this.name, new Literal("" + svar + "[" + kvar + "]"))); } - defPart += this.pluckDirectCall(o, body); + defPartFragments = [].concat(this.makeCode(defPart), this.pluckDirectCall(o, body)); if (namePart) { varPart = "\n" + idt1 + namePart + ";"; } if (this.object) { - forPart = "" + kvar + " in " + svar; + forPartFragments = [this.makeCode("" + kvar + " in " + svar)]; if (this.own) { guardPart = "\n" + idt1 + "if (!" + (utility('hasProp')) + ".call(" + svar + ", " + kvar + ")) continue;"; } } - body = body.compile(merge(o, { + bodyFragments = body.compileToFragments(merge(o, { indent: idt1 }), LEVEL_TOP); - if (body) { - body = '\n' + body + '\n'; + if (bodyFragments && (bodyFragments.length > 0)) { + bodyFragments = [].concat(this.makeCode("\n"), bodyFragments, this.makeCode("\n")); } - return "" + defPart + (resultPart || '') + this.tab + "for (" + forPart + ") {" + guardPart + varPart + body + this.tab + "}" + (returnResult || ''); + return [].concat(defPartFragments, this.makeCode("" + (resultPart || '') + this.tab + "for ("), forPartFragments, this.makeCode(") {" + guardPart + varPart), bodyFragments, this.makeCode("" + this.tab + "}" + (returnResult || ''))); }; For.prototype.pluckDirectCall = function(o, body) { var base, defs, expr, fn, idx, ref, val, _i, _len, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7; - defs = ''; + defs = []; _ref2 = body.expressions; for (idx = _i = 0, _len = _ref2.length; _i < _len; idx = ++_i) { expr = _ref2[idx]; @@ -2712,7 +2853,7 @@ _ref7 = [base, val], val.base = _ref7[0], base = _ref7[1]; } body.expressions[idx] = new Call(base, expr.args); - defs += this.tab + new Assign(ref, fn).compile(o, LEVEL_TOP) + ';\n'; + defs = defs.concat(this.makeCode(this.tab), new Assign(ref, fn).compileToFragments(o, LEVEL_TOP), this.makeCode(';\n')); } return defs; }; @@ -2769,23 +2910,23 @@ }; Switch.prototype.compileNode = function(o) { - var block, body, code, cond, conditions, expr, i, idt1, idt2, _i, _j, _len, _len1, _ref2, _ref3, _ref4, _ref5; + var block, body, cond, conditions, expr, fragments, i, idt1, idt2, _i, _j, _len, _len1, _ref2, _ref3, _ref4; idt1 = o.indent + TAB; idt2 = o.indent = idt1 + TAB; - code = this.tab + ("switch (" + (((_ref2 = this.subject) != null ? _ref2.compile(o, LEVEL_PAREN) : void 0) || false) + ") {\n"); - _ref3 = this.cases; - for (i = _i = 0, _len = _ref3.length; _i < _len; i = ++_i) { - _ref4 = _ref3[i], conditions = _ref4[0], block = _ref4[1]; - _ref5 = flatten([conditions]); - for (_j = 0, _len1 = _ref5.length; _j < _len1; _j++) { - cond = _ref5[_j]; + fragments = [].concat(this.makeCode(this.tab + "switch ("), (this.subject ? this.subject.compileToFragments(o, LEVEL_PAREN) : this.makeCode("false")), this.makeCode(") {\n")); + _ref2 = this.cases; + for (i = _i = 0, _len = _ref2.length; _i < _len; i = ++_i) { + _ref3 = _ref2[i], conditions = _ref3[0], block = _ref3[1]; + _ref4 = flatten([conditions]); + for (_j = 0, _len1 = _ref4.length; _j < _len1; _j++) { + cond = _ref4[_j]; if (!this.subject) { cond = cond.invert(); } - code += idt1 + ("case " + (cond.compile(o, LEVEL_PAREN)) + ":\n"); + fragments = fragments.concat(this.makeCode(idt1 + "case "), cond.compileToFragments(o, LEVEL_PAREN), this.makeCode(":\n")); } - if (body = block.compile(o, LEVEL_TOP)) { - code += body + '\n'; + if ((body = block.compileToFragments(o, LEVEL_TOP)).length > 0) { + fragments = fragments.concat(body, this.makeCode('\n')); } if (i === this.cases.length - 1 && !this.otherwise) { break; @@ -2794,12 +2935,13 @@ if (expr instanceof Return || (expr instanceof Literal && expr.jumps() && expr.value !== 'debugger')) { continue; } - code += idt2 + 'break;\n'; + fragments.push(cond.makeCode(idt2 + 'break;\n')); } if (this.otherwise && this.otherwise.expressions.length) { - code += idt1 + ("default:\n" + (this.otherwise.compile(o, LEVEL_TOP)) + "\n"); + fragments.push.apply(fragments, [this.makeCode(idt1 + "default:\n")].concat(__slice.call(this.otherwise.compileToFragments(o, LEVEL_TOP)), [this.makeCode("\n")])); } - return code + this.tab + '}'; + fragments.push(this.makeCode(this.tab + '}')); + return fragments; }; return Switch; @@ -2879,37 +3021,45 @@ }; If.prototype.compileStatement = function(o) { - var body, child, cond, exeq, ifPart; + var answer, body, child, cond, exeq, ifPart; child = del(o, 'chainChild'); exeq = del(o, 'isExistentialEquals'); if (exeq) { return new If(this.condition.invert(), this.elseBodyNode(), { type: 'if' - }).compile(o); + }).compileToFragments(o); } - cond = this.condition.compile(o, LEVEL_PAREN); + cond = this.condition.compileToFragments(o, LEVEL_PAREN); o.indent += TAB; body = this.ensureBlock(this.body); - ifPart = "if (" + cond + ") {\n" + (body.compile(o)) + "\n" + this.tab + "}"; + ifPart = [].concat(this.makeCode("if ("), cond, this.makeCode(") {\n"), body.compileToFragments(o), this.makeCode("\n" + this.tab + "}")); if (!child) { - ifPart = this.tab + ifPart; + ifPart.unshift(this.makeCode(this.tab)); } if (!this.elseBody) { return ifPart; } - return ifPart + ' else ' + (this.isChain ? (o.indent = this.tab, o.chainChild = true, this.elseBody.unwrap().compile(o, LEVEL_TOP)) : "{\n" + (this.elseBody.compile(o, LEVEL_TOP)) + "\n" + this.tab + "}"); + answer = ifPart.concat(this.makeCode(' else ')); + if (this.isChain) { + o.indent = this.tab; + o.chainChild = true; + answer = answer.concat(this.elseBody.unwrap().compileToFragments(o, LEVEL_TOP)); + } else { + answer = answer.concat(this.makeCode("{\n"), this.elseBody.compileToFragments(o, LEVEL_TOP), this.makeCode("\n" + this.tab + "}")); + } + return answer; }; If.prototype.compileExpression = function(o) { - var alt, body, code, cond; - cond = this.condition.compile(o, LEVEL_COND); - body = this.bodyNode().compile(o, LEVEL_LIST); - alt = this.elseBodyNode() ? this.elseBodyNode().compile(o, LEVEL_LIST) : 'void 0'; - code = "" + cond + " ? " + body + " : " + alt; + var alt, body, cond, fragments; + cond = this.condition.compileToFragments(o, LEVEL_COND); + body = this.bodyNode().compileToFragments(o, LEVEL_LIST); + alt = this.elseBodyNode() ? this.elseBodyNode().compileToFragments(o, LEVEL_LIST) : [this.makeCode('void 0')]; + fragments = cond.concat(this.makeCode(" ? "), body, this.makeCode(" : "), alt); if (o.level >= LEVEL_COND) { - return "(" + code + ")"; + return this.wrapInBraces(fragments); } else { - return code; + return fragments; } }; diff --git a/src/nodes.coffee b/src/nodes.coffee index 32ed95ec..e77c836f 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -3,6 +3,8 @@ # but some are created by other nodes as a method of code generation. To convert # the syntax tree into a string of JavaScript code, call `compile()` on the root. +Error.stackTraceLimit = Infinity + {Scope} = require './scope' {RESERVED, STRICT_PROSCRIBED} = require './lexer' @@ -19,6 +21,49 @@ NO = -> no THIS = -> this NEGATE = -> @negated = not @negated; this +# If PARANOID is true, then certain extra internal consistency checks are done during the compile. +# This is used for checks that would slow down the compiler. This is really only useful if you're +# doing compiler work. +PARANOID = false + +#### CodeFragment + +# The various nodes defined below all compile to a collection of **CodeFragment** objects. +# A CodeFragments is a block of generated code, and the location in the source file where the code +# came from. CodeFragments can be assembled together into working code just by catting together +# all the CodeFragments' `code` snippets, in order. +exports.CodeFragment = class CodeFragment + constructor: (parent, @code) -> + @locationData = parent?.locationData + @type = parent?.constructor?.name or 'unknown' + + toString: () -> + "#{@code}#{[if @locationData then ": " + locationDataToString(@locationData)]}" + +# Convert an array of CodeFragments into a string. +fragmentsToText = (fragments) -> + checkFragments fragments + (fragment.code for fragment in fragments).join('') + +# Validates that `fragments` is an array of CodeFragment objects. +# If PARANOID is false, then this does nothing. +checkFragments = (fragments, node=null) -> + if !PARANOID then return fragments + + nodeName = if node then " from #{node.constructor.name}" else "" + if !fragments + throw new Error("Fragments is null#{nodeName}: #{fragments}\n" ) + if fragments instanceof CodeFragment + throw new Error("Expected array of fragments but found fragment#{nodeName}: #{fragments}\n" ) + for fragment, i in fragments + if !(fragment instanceof CodeFragment) + # This is not a fragment. + inspected = (require 'util').inspect fragments + nodeStr = if node then "node: #{node.toString()}\n" else "" + throw new Error("Expected fragment: #{i} of #{fragments.length}#{nodeName}: #{fragment}\nFragments: #{inspected}\n#{nodeStr}") + + return fragments + #### Base # The **Base** is the abstract base class for all nodes in the syntax tree. @@ -32,21 +77,26 @@ NEGATE = -> @negated = not @negated; this # scope, and indentation level. exports.Base = class Base + compile: (o, lvl) -> + fragmentsToText @compileToFragments o, lvl + # Common logic for determining whether to wrap this node in a closure before # compiling it, or to compile directly. We need to wrap if this node is a # *statement*, and it's not a *pureStatement*, and we're not at # the top level of a block (which would be unnecessary), and we haven't # already been asked to return the result (because statements know how to # return results). - compile: (o, lvl) -> + compileToFragments: (o, lvl) -> o = extend {}, o o.level = lvl if lvl node = @unfoldSoak(o) or this node.tab = o.indent if o.level is LEVEL_TOP or not node.isStatement(o) - node.compileNode o + fragments = node.compileNode o else - node.compileClosure o + fragments = node.compileClosure o + checkFragments fragments, node + fragments # Statements converted into expressions via closure-wrapping share a scope # object with their parent closure, to preserve the expected lexical scope. @@ -59,21 +109,21 @@ exports.Base = class Base # If the code generation wishes to use the result of a complex expression # in multiple places, ensure that the expression is only ever evaluated once, # by assigning it to a temporary variable. Pass a level to precompile. + # + # If `level` is passed, then returns `[val, ref]`, where `val` is the compiled value, and `ref` + # is the compiled reference. If `level` is not passed, this returns `[val, ref]` where + # the two values are raw nodes which have not been compiled. cache: (o, level, reused) -> unless @isComplex() - ref = if level then @compile o, level else this + ref = if level then @compileToFragments o, level else this [ref, ref] else ref = new Literal reused or o.scope.freeVariable 'ref' sub = new Assign ref, this - if level then [sub.compile(o, level), ref.value] else [sub, ref] + if level then [sub.compileToFragments(o, level), [@makeCode(ref.value)]] else [sub, ref] - # Compile to a source/variable pair suitable for looping. - compileLoopReference: (o, name) -> - src = tmp = @compile o, LEVEL_LIST - unless -Infinity < +src < Infinity or IDENTIFIER.test(src) and o.scope.check(src, yes) - src = "#{ tmp = o.scope.freeVariable name } = #{src}" - [src, tmp] + cacheToCodeFragments: (cacheValues) -> + [fragmentsToText(cacheValues[0]), fragmentsToText(cacheValues[1])] # Construct a node that returns the current node's result. # Note that this is overridden for smarter behavior for @@ -163,6 +213,23 @@ exports.Base = class Base @eachChild (child) -> child.updateLocationDataIfMissing locationData + makeCode: (code) -> + new CodeFragment this, code + + wrapInBraces: (fragments) -> + [].concat @makeCode('('), fragments, @makeCode(')') + + # `fragmentsList` is an array of arrays of fragments. Each array in fragmentsList will be + # concatonated together, with `joinStr` added in between each, to produce a final flat array + # of fragments. + joinFragmentArrays: (fragmentsList, joinStr) -> + answer = [] + for fragments,i in fragmentsList + checkFragments fragments, @ + if i then answer.push @makeCode joinStr + answer = answer.concat fragments + answer + #### Block # The block is the list of expressions that forms the body of an @@ -219,7 +286,7 @@ exports.Block = class Block extends Base this # A **Block** is the only node that can serve as the root. - compile: (o = {}, level) -> + compileToFragments: (o = {}, level) -> if o.scope then super o, level else @compileRoot o # Compile all expressions within the **Block** body. If we need to @@ -228,30 +295,36 @@ exports.Block = class Block extends Base compileNode: (o) -> @tab = o.indent top = o.level is LEVEL_TOP - codes = [] - for node in @expressions + compiledNodes = [] + + for node, index in @expressions + node = node.unwrapAll() node = (node.unfoldSoak(o) or node) if node instanceof Block # This is a nested block. We don't do anything special here like enclose # it in a new scope; we just compile the statements in this block along with # our own - codes.push node.compileNode o + compiledNodes.push node.compileNode o else if top node.front = true - code = node.compile o + fragments = node.compileToFragments o unless node.isStatement o - code = "#{@tab}#{code};" - codes.push code + fragments.unshift @makeCode "#{@tab}" + fragments.push @makeCode ";" + compiledNodes.push fragments else - codes.push node.compile o, LEVEL_LIST + compiledNodes.push node.compileToFragments o, LEVEL_LIST if top if @spaced - return "\n#{codes.join '\n\n'}\n" + return [].concat @makeCode("\n"), @joinFragmentArrays(compiledNodes, '\n\n'), @makeCode("\n") else - return codes.join '\n' - code = codes.join(', ') or 'void 0' - if codes.length > 1 and o.level >= LEVEL_LIST then "(#{code})" else code + return @joinFragmentArrays(compiledNodes, '\n') + if compiledNodes.length + answer = @joinFragmentArrays(compiledNodes, ', ') + else + answer = [@makeCode "void 0"] + if compiledNodes.length > 1 and o.level >= LEVEL_LIST then @wrapInBraces answer else answer # If we happen to be the top-level **Block**, wrap everything in # a safety closure, unless requested not to. @@ -262,31 +335,34 @@ exports.Block = class Block extends Base o.scope = new Scope null, this, null o.level = LEVEL_TOP @spaced = yes - prelude = "" + prelude = [] unless o.bare preludeExps = for exp, i in @expressions break unless exp.unwrap() instanceof Comment exp rest = @expressions[preludeExps.length...] @expressions = preludeExps - prelude = "#{@compileNode merge(o, indent: '')}\n" if preludeExps.length + if preludeExps.length + prelude = @compileNode merge(o, indent: '') + prelude.push @makeCode "\n" @expressions = rest - code = @compileWithDeclarations o - return code if o.bare - "#{prelude}(function() {\n#{code}\n}).call(this);\n" + fragments = @compileWithDeclarations o + return fragments if o.bare + [].concat prelude, @makeCode("(function() {\n"), fragments, @makeCode("\n}).call(this);\n") # Compile the expressions body for the contents of a function, with # declarations of all inner variables pushed up to the top. compileWithDeclarations: (o) -> - code = post = '' + fragments = [] + post = [] for exp, i in @expressions exp = exp.unwrap() break unless exp instanceof Comment or exp instanceof Literal o = merge(o, level: LEVEL_TOP) if i rest = @expressions.splice i, 9e9 - [spaced, @spaced] = [@spaced, no] - [code , @spaced] = [(@compileNode o), spaced] + [spaced, @spaced] = [@spaced, no] + [fragments, @spaced] = [(@compileNode o), spaced] @expressions = rest post = @compileNode o {scope} = o @@ -294,15 +370,15 @@ exports.Block = class Block extends Base declars = o.scope.hasDeclarations() assigns = scope.hasAssignments if declars or assigns - code += '\n' if i - code += "#{@tab}var " + fragments.push @makeCode '\n' if i + fragments.push @makeCode "#{@tab}var " if declars - code += scope.declaredVariables().join ', ' + fragments.push @makeCode(scope.declaredVariables().join ', ') if assigns - code += ",\n#{@tab + TAB}" if declars - code += scope.assignedVariables().join ",\n#{@tab + TAB}" - code += ';\n' - code + post + fragments.push @makeCode ",\n#{@tab + TAB}" if declars + fragments.push @makeCode (scope.assignedVariables().join ",\n#{@tab + TAB}") + fragments.push @makeCode ';\n' + fragments.concat post # Wrap up the given nodes as a **Block**, unless it already happens # to be one. @@ -343,7 +419,8 @@ exports.Literal = class Literal extends Base "\"#{@value}\"" else @value - if @isStatement() then "#{@tab}#{code};" else code + answer = if @isStatement() then "#{@tab}#{code};" else code + [@makeCode answer] toString: -> ' "' + @value + '"' @@ -352,17 +429,17 @@ class exports.Undefined extends Base isAssignable: NO isComplex: NO compileNode: (o) -> - if o.level >= LEVEL_ACCESS then '(void 0)' else 'void 0' + [@makeCode if o.level >= LEVEL_ACCESS then '(void 0)' else 'void 0'] class exports.Null extends Base isAssignable: NO isComplex: NO - compileNode: -> "null" + compileNode: -> [@makeCode "null"] class exports.Bool extends Base isAssignable: NO isComplex: NO - compileNode: -> @val + compileNode: -> [@makeCode @val] constructor: (@val) -> #### Return @@ -379,12 +456,19 @@ exports.Return = class Return extends Base makeReturn: THIS jumps: THIS - compile: (o, level) -> + compileToFragments: (o, level) -> expr = @expression?.makeReturn() - if expr and expr not instanceof Return then expr.compile o, level else super o, level + if expr and expr not instanceof Return then expr.compileToFragments o, level else super o, level compileNode: (o) -> - @tab + "return#{[" #{@expression.compile o, LEVEL_PAREN}" if @expression]};" + answer = [] + # TODO: If we call expression.compile() here twice, we'll sometimes get back different results! + answer.push @makeCode(@tab + "return#{[" " if @expression]}") + if @expression + answer = answer.concat @expression.compileToFragments(o, LEVEL_PAREN) + answer.push @makeCode ";" + return answer + #### Value @@ -463,10 +547,12 @@ exports.Value = class Value extends Base compileNode: (o) -> @base.front = @front props = @properties - code = @base.compile o, if props.length then LEVEL_ACCESS else null - code = "#{code}." if (@base instanceof Parens or props.length) and SIMPLENUM.test code - code += prop.compile o for prop in props - code + fragments = @base.compileToFragments o, (if props.length then LEVEL_ACCESS else null) + if (@base instanceof Parens or props.length) and SIMPLENUM.test fragmentsToText fragments + fragments.push @makeCode '.' + for prop in props + fragments.push (prop.compileToFragments o)... + fragments # Unfold a soak into an `If`: `a?.b` -> `a.b if a?` unfoldSoak: (o) -> @@ -500,7 +586,7 @@ exports.Comment = class Comment extends Base compileNode: (o, level) -> code = '/*' + multident(@comment, @tab) + "\n#{@tab}*/\n" code = o.indent + code if (level or o.level) is LEVEL_TOP - code + [@makeCode code] #### Call @@ -596,49 +682,68 @@ exports.Call = class Call extends Base # Compile a vanilla function call. compileNode: (o) -> @variable?.front = @front - if code = Splat.compileSplattedArray o, @args, true - return @compileSplat o, code + compiledArray = Splat.compileSplattedArray o, @args, true + if compiledArray.length + return @compileSplat o, compiledArray args = @filterImplicitObjects @args - args = (arg.compile o, LEVEL_LIST for arg in args).join ', ' - if @isSuper - @superReference(o) + ".call(#{@superThis(o)}#{ args and ', ' + args })" - else - (if @isNew then 'new ' else '') + @variable.compile(o, LEVEL_ACCESS) + "(#{args})" + compiledArgs = [] + for arg, argIndex in args + if argIndex then compiledArgs.push @makeCode ", " + compiledArgs.push (arg.compileToFragments o, LEVEL_LIST)... - # `super()` is converted into a call against the superclass's implementation - # of the current function. - # TODO: This looks like it is never called? - compileSuper: (args, o) -> - "#{@superReference(o)}.call(#{@superThis(o)}#{ if args.length then ', ' else '' }#{args})" + fragments = [] + if @isSuper + preface = @superReference(o) + ".call(#{@superThis(o)}" + if compiledArgs.length then preface += ", " + fragments.push @makeCode preface + else + if @isNew then fragments.push @makeCode 'new ' + fragments.push (@variable.compileToFragments(o, LEVEL_ACCESS))... + fragments.push @makeCode "(" + fragments.push compiledArgs... + fragments.push @makeCode ")" + fragments # If you call a function with a splat, it's converted into a JavaScript # `.apply()` call to allow an array of arguments to be passed. # If it's a constructor, then things get real tricky. We have to inject an # inner constructor in order to be able to pass the varargs. + # + # splatArgs is an array of CodeFragments to put into the 'apply'. compileSplat: (o, splatArgs) -> - return "#{ @superReference o }.apply(#{@superThis(o)}, #{splatArgs})" if @isSuper + if @isSuper + return [].concat @makeCode("#{ @superReference o }.apply(#{@superThis(o)}, "), + splatArgs, @makeCode(")") + if @isNew idt = @tab + TAB - return """ + return [].concat @makeCode(""" (function(func, args, ctor) { #{idt}ctor.prototype = func.prototype; #{idt}var child = new ctor, result = func.apply(child, args); #{idt}return Object(result) === result ? result : child; - #{@tab}})(#{ @variable.compile o, LEVEL_LIST }, #{splatArgs}, function(){}) - """ + #{@tab}})("""), + (@variable.compileToFragments o, LEVEL_LIST), + @makeCode(", "), splatArgs, @makeCode(", function(){})") + + answer = [] base = Value.wrap @variable if (name = base.properties.pop()) and base.isComplex() ref = o.scope.freeVariable 'ref' - fun = "(#{ref} = #{ base.compile o, LEVEL_LIST })#{ name.compile o }" + answer = answer.concat @makeCode("(#{ref} = "), + (base.compileToFragments o, LEVEL_LIST), + @makeCode(")"), + name.compileToFragments(o) else - fun = base.compile o, LEVEL_ACCESS - fun = "(#{fun})" if SIMPLENUM.test fun + fun = base.compileToFragments o, LEVEL_ACCESS + fun = @wrapInBraces fun if SIMPLENUM.test fragmentsToText fun if name - ref = fun - fun += name.compile o + ref = fragmentsToText fun + fun.push (name.compileToFragments o)... else ref = 'null' - "#{fun}.apply(#{ref}, #{splatArgs})" + answer = answer.concat fun + answer = answer.concat @makeCode(".apply(#{ref}, "), splatArgs, @makeCode(")") #### Extends @@ -651,8 +756,8 @@ exports.Extends = class Extends extends Base children: ['child', 'parent'] # Hooks one constructor into another's prototype chain. - compile: (o) -> - new Call(Value.wrap(new Literal utility 'extends'), [@child, @parent]).compile o + compileToFragments: (o) -> + new Call(Value.wrap(new Literal utility 'extends'), [@child, @parent]).compileToFragments o #### Access @@ -665,9 +770,14 @@ exports.Access = class Access extends Base children: ['name'] - compile: (o) -> - name = @name.compile o - if IDENTIFIER.test name then ".#{name}" else "[#{name}]" + compileToFragments: (o) -> + name = @name.compileToFragments o + if IDENTIFIER.test fragmentsToText name + name.unshift @makeCode "." + else + name.unshift @makeCode "[" + name.push @makeCode "]" + name isComplex: NO @@ -679,8 +789,8 @@ exports.Index = class Index extends Base children: ['index'] - compile: (o) -> - "[#{ @index.compile o, LEVEL_PAREN }]" + compileToFragments: (o) -> + [].concat @makeCode("["), (@index.compileToFragments o, LEVEL_PAREN), @makeCode("]") isComplex: -> @index.isComplex() @@ -698,13 +808,15 @@ exports.Range = class Range extends Base @exclusive = tag is 'exclusive' @equals = if @exclusive then '' else '=' + + # Compiles the range's source variables -- where it starts and where it ends. # But only if they need to be cached to avoid double evaluation. compileVariables: (o) -> o = merge o, top: true - [@fromC, @fromVar] = @from.cache o, LEVEL_LIST - [@toC, @toVar] = @to.cache o, LEVEL_LIST - [@step, @stepVar] = step.cache o, LEVEL_LIST if step = del o, 'step' + [@fromC, @fromVar] = @cacheToCodeFragments @from.cache o, LEVEL_LIST + [@toC, @toVar] = @cacheToCodeFragments @to.cache o, LEVEL_LIST + [@step, @stepVar] = @cacheToCodeFragments step.cache o, LEVEL_LIST if step = del o, 'step' [@fromNum, @toNum] = [@fromVar.match(SIMPLENUM), @toVar.match(SIMPLENUM)] @stepNum = @stepVar.match(SIMPLENUM) if @stepVar @@ -752,7 +864,7 @@ exports.Range = class Range extends Base stepPart = "#{idxName} = #{stepPart}" if namedIndex # The final loop body. - "#{varPart}; #{condPart}; #{stepPart}" + [@makeCode "#{varPart}; #{condPart}; #{stepPart}"] # When used as a value, expand the range into the equivalent array. @@ -760,14 +872,14 @@ exports.Range = class Range extends Base if @fromNum and @toNum and Math.abs(@fromNum - @toNum) <= 20 range = [+@fromNum..+@toNum] range.pop() if @exclusive - return "[#{ range.join(', ') }]" + return [@makeCode "[#{ range.join(', ') }]"] idt = @tab + TAB i = o.scope.freeVariable 'i' result = o.scope.freeVariable 'results' pre = "\n#{idt}#{result} = [];" if @fromNum and @toNum o.index = i - body = @compileNode o + body = fragmentsToText @compileNode o else vars = "#{i} = #{@fromC}" + if @toC isnt @toVar then ", #{@toC}" else '' cond = "#{@fromVar} <= #{@toVar}" @@ -775,7 +887,7 @@ exports.Range = class Range extends Base post = "{ #{result}.push(#{i}); }\n#{idt}return #{result};\n#{o.indent}" hasArgs = (node) -> node?.contains (n) -> n instanceof Literal and n.value is 'arguments' and not n.asKey args = ', arguments' if hasArgs(@from) or hasArgs(@to) - "(function() {#{pre}\n#{idt}for (#{body})#{post}}).apply(this#{args ? ''})" + [@makeCode "(function() {#{pre}\n#{idt}for (#{body})#{post}}).apply(this#{args ? ''})"] #### Slice @@ -794,17 +906,20 @@ exports.Slice = class Slice extends Base # `9e9` should be safe because `9e9` > `2**32`, the max array length. compileNode: (o) -> {to, from} = @range - fromStr = from and from.compile(o, LEVEL_PAREN) or '0' - compiled = to and to.compile o, LEVEL_PAREN - if to and not (not @range.exclusive and +compiled is -1) - toStr = ', ' + if @range.exclusive - compiled - else if SIMPLENUM.test compiled - "#{+compiled + 1}" - else - compiled = to.compile o, LEVEL_ACCESS - "+#{compiled} + 1 || 9e9" - ".slice(#{ fromStr }#{ toStr or '' })" + fromCompiled = from and from.compileToFragments(o, LEVEL_PAREN) or [@makeCode '0'] + # TODO: jwalton - move this into the 'if'? + if to + compiled = to.compileToFragments o, LEVEL_PAREN + compiledText = fragmentsToText compiled + if not (not @range.exclusive and +compiledText is -1) + toStr = ', ' + if @range.exclusive + compiledText + else if SIMPLENUM.test compiledText + "#{+compiledText + 1}" + else + compiled = to.compileToFragments o, LEVEL_ACCESS + "+#{fragmentsToText compiled} + 1 || 9e9" + [@makeCode ".slice(#{ fragmentsToText fromCompiled }#{ toStr or '' })"] #### Obj @@ -817,13 +932,14 @@ exports.Obj = class Obj extends Base compileNode: (o) -> props = @properties - return (if @front then '({})' else '{}') unless props.length + return [@makeCode(if @front then '({})' else '{}')] unless props.length if @generated for node in props when node instanceof Value throw new Error 'cannot have an implicit value in an implicit object' idt = o.indent += TAB lastNoncom = @lastNonComment @properties - props = for prop, i in props + answer = [] + for prop, i in props join = if i is props.length - 1 '' else if prop is lastNoncom or prop instanceof Comment @@ -837,10 +953,12 @@ exports.Obj = class Obj extends Base if prop not instanceof Assign prop = new Assign prop, prop, 'object' (prop.variable.base or prop.variable).asKey = yes - indent + prop.compile(o, LEVEL_TOP) + join - props = props.join '' - obj = "{#{ props and '\n' + props + '\n' + @tab }}" - if @front then "(#{obj})" else obj + if indent then answer.push @makeCode indent + answer.push prop.compileToFragments(o, LEVEL_TOP)... + if join then answer.push @makeCode join + answer.unshift @makeCode "{#{ props.length and '\n' }" + answer.push @makeCode "#{ props.length and '\n' + @tab }}" + if @front then @wrapInBraces answer else answer assigns: (name) -> for prop in @properties when prop.assigns name then return yes @@ -858,15 +976,25 @@ exports.Arr = class Arr extends Base filterImplicitObjects: Call::filterImplicitObjects compileNode: (o) -> - return '[]' unless @objects.length + return [@makeCode '[]'] unless @objects.length o.indent += TAB objs = @filterImplicitObjects @objects - return code if code = Splat.compileSplattedArray o, objs - code = (obj.compile o, LEVEL_LIST for obj in objs).join ', ' - if code.indexOf('\n') >= 0 - "[\n#{o.indent}#{code}\n#{@tab}]" + answer = Splat.compileSplattedArray o, objs + return answer if answer.length + + answer = [] + compiledObjs = (obj.compileToFragments o, LEVEL_LIST for obj in objs) + for fragments, index in compiledObjs + if index + answer.push @makeCode ", " + answer.push fragments... + if (fragmentsToText answer).indexOf('\n') >= 0 + answer.unshift @makeCode "[\n#{o.indent}" + answer.push @makeCode "\n#{@tab}]" else - "[#{code}]" + answer.unshift @makeCode "[" + answer.push @makeCode "]" + answer assigns: (name) -> for obj in @objects when obj.assigns name then return yes @@ -1015,7 +1143,7 @@ exports.Class = class Class extends Base klass = new Parens call, yes klass = new Assign @variable, klass if @variable - klass.compile o + klass.compileToFragments o #### Assign @@ -1049,7 +1177,8 @@ exports.Assign = class Assign extends Base return @compilePatternMatch o if @variable.isArray() or @variable.isObject() return @compileSplice o if @variable.isSplice() return @compileConditional o if @context in ['||=', '&&=', '?='] - name = @variable.compile o, LEVEL_LIST + compiledName = @variable.compileToFragments o, LEVEL_LIST + name = fragmentsToText compiledName unless @context unless (varBase = @variable.unwrapAll()).isAssignable() throw SyntaxError "\"#{ @variable.compile o }\" cannot be assigned." @@ -1061,10 +1190,10 @@ exports.Assign = class Assign extends Base if @value instanceof Code and match = METHOD_DEF.exec name @value.klass = match[1] if match[1] @value.name = match[2] ? match[3] ? match[4] ? match[5] - val = @value.compile o, LEVEL_LIST - return "#{name}: #{val}" if @context is 'object' - val = name + " #{ @context or '=' } " + val - if o.level <= LEVEL_LIST then val else "(#{val})" + val = @value.compileToFragments o, LEVEL_LIST + return (compiledName.concat @makeCode(": "), val) if @context is 'object' + answer = compiledName.concat @makeCode(" #{ @context or '=' } "), val + if o.level <= LEVEL_LIST then answer else @wrapInBraces answer # Brief implementation of recursive pattern matching, when assigning array or # object literals to a value. Peeks at their properties to assign inner names. @@ -1075,8 +1204,8 @@ exports.Assign = class Assign extends Base {value} = this {objects} = @variable.base unless olen = objects.length - code = value.compile o - return if o.level >= LEVEL_OP then "(#{code})" else code + code = value.compileToFragments o + return if o.level >= LEVEL_OP then @wrapInBraces code else code isObject = @variable.isObject() if top and olen is 1 and (obj = objects[0]) not instanceof Splat # Unroll simplest cases: `{v} = x` -> `v = x.v` @@ -1092,13 +1221,16 @@ exports.Assign = class Assign extends Base value.properties.push new (if acc then Access else Index) idx if obj.unwrap().value in RESERVED throw new SyntaxError "assignment to a reserved word: #{obj.compile o} = #{value.compile o}" - return new Assign(obj, value, null, param: @param).compile o, LEVEL_TOP - vvar = value.compile o, LEVEL_LIST + return new Assign(obj, value, null, param: @param).compileToFragments o, LEVEL_TOP + vvar = value.compileToFragments o, LEVEL_LIST + vvarText = fragmentsToText vvar assigns = [] splat = false - if not IDENTIFIER.test(vvar) or @variable.assigns(vvar) - assigns.push "#{ ref = o.scope.freeVariable 'ref' } = #{vvar}" - vvar = ref + # Make vvar into a simple variable if it isn't already. + if not IDENTIFIER.test(vvarText) or @variable.assigns(vvarText) + assigns.push [@makeCode("#{ ref = o.scope.freeVariable 'ref' } = "), vvar...] + vvar = [@makeCode ref] + vvarText = ref for obj, i in objects # A regular array pattern-match. idx = i @@ -1115,10 +1247,10 @@ exports.Assign = class Assign extends Base if not splat and obj instanceof Splat name = obj.name.unwrap().value obj = obj.unwrap() - val = "#{olen} <= #{vvar}.length ? #{ utility 'slice' }.call(#{vvar}, #{i}" + val = "#{olen} <= #{vvarText}.length ? #{ utility 'slice' }.call(#{vvarText}, #{i}" if rest = olen - i - 1 ivar = o.scope.freeVariable 'i' - val += ", #{ivar} = #{vvar}.length - #{rest}) : (#{ivar} = #{i}, [])" + val += ", #{ivar} = #{vvarText}.length - #{rest}) : (#{ivar} = #{i}, [])" else val += ") : []" val = new Literal val @@ -1126,7 +1258,7 @@ exports.Assign = class Assign extends Base else name = obj.unwrap().value if obj instanceof Splat - obj = obj.name.compile o + obj = obj.name.compileToFragments o throw new SyntaxError \ "multiple splats are disallowed in an assignment: #{obj}..." if typeof idx is 'number' @@ -1134,13 +1266,13 @@ exports.Assign = class Assign extends Base acc = no else acc = isObject and IDENTIFIER.test idx.unwrap().value or 0 - val = Value.wrap new Literal(vvar), [new (if acc then Access else Index) idx] + val = Value.wrap new Literal(vvarText), [new (if acc then Access else Index) idx] if name? and name in RESERVED throw new SyntaxError "assignment to a reserved word: #{obj.compile o} = #{val.compile o}" - assigns.push new Assign(obj, val, null, param: @param, subpattern: yes).compile o, LEVEL_LIST + assigns.push new Assign(obj, val, null, param: @param, subpattern: yes).compileToFragments o, LEVEL_LIST assigns.push vvar unless top or @subpattern - code = assigns.join ', ' - if o.level < LEVEL_LIST then code else "(#{code})" + fragments = @joinFragmentArrays assigns, ', ' + if o.level < LEVEL_LIST then fragments else @wrapInBraces fragments # When compiling a conditional assignment, take care to ensure that the # operands are only evaluated once, even though we have to reference them @@ -1152,14 +1284,17 @@ exports.Assign = class Assign extends Base left.base.value != "this" and not o.scope.check left.base.value throw new Error "the variable \"#{left.base.value}\" can't be assigned with #{@context} because it has not been defined." if "?" in @context then o.isExistentialEquals = true - Op.create(@context[...-1], left, new Assign(right, @value, '=') ).compile o + Op.create(@context[...-1], left, new Assign(right, @value, '=') ).compileToFragments o # Compile the assignment from an array splice literal, using JavaScript's # `Array#splice` method. compileSplice: (o) -> {range: {from, to, exclusive}} = @variable.properties.pop() name = @variable.compile o - [fromDecl, fromRef] = from?.cache(o, LEVEL_OP) or ['0', '0'] + if from + [fromDecl, fromRef] = @cacheToCodeFragments from.cache o, LEVEL_OP + else + fromDecl = fromRef = '0' if to if from?.isSimpleNumber() and to.isSimpleNumber() to = +to.compile(o) - +fromRef @@ -1170,8 +1305,8 @@ exports.Assign = class Assign extends Base else to = "9e9" [valDef, valRef] = @value.cache o, LEVEL_LIST - code = "[].splice.apply(#{name}, [#{fromDecl}, #{to}].concat(#{valDef})), #{valRef}" - if o.level > LEVEL_TOP then "(#{code})" else code + answer = [].concat @makeCode("[].splice.apply(#{name}, [#{fromDecl}, #{to}].concat("), valDef, @makeCode(")), "), valRef + if o.level > LEVEL_TOP then @wrapInBraces answer else answer #### Code @@ -1228,7 +1363,9 @@ exports.Code = class Code extends Base wasEmpty = @body.isEmpty() exprs.unshift splats if splats @body.expressions.unshift exprs... if exprs.length - o.scope.parameter params[i] = p.compile o for p, i in params + for p, i in params + params[i] = p.compileToFragments o + o.scope.parameter fragmentsToText params[i] uniqs = [] for name in @paramNames() throw SyntaxError "multiple parameters named '#{name}'" if name in uniqs @@ -1242,11 +1379,17 @@ exports.Code = class Code extends Base idt = o.indent code = 'function' code += ' ' + @name if @ctor - code += '(' + params.join(', ') + ') {' - code += "\n#{ @body.compileWithDeclarations o }\n#{@tab}" unless @body.isEmpty() - code += '}' - return @tab + code if @ctor - if @front or (o.level >= LEVEL_ACCESS) then "(#{code})" else code + code += '(' + answer = [@makeCode(code)] + for p, i in params + if i then answer.push @makeCode ", " + answer.push p... + answer.push @makeCode ') {' + answer = answer.concat(@makeCode("\n"), @body.compileWithDeclarations(o), @makeCode("\n#{@tab}")) unless @body.isEmpty() + answer.push @makeCode '}' + + return [@makeCode(@tab), answer...] if @ctor + if @front or (o.level >= LEVEL_ACCESS) then @wrapInBraces answer else answer # A list of parameter names, excluding those generated by the compiler. paramNames: -> @@ -1271,8 +1414,8 @@ exports.Param = class Param extends Base children: ['name', 'value'] - compile: (o) -> - @name.compile o, LEVEL_LIST + compileToFragments: (o) -> + @name.compileToFragments o, LEVEL_LIST asReference: (o) -> return @reference if @reference @@ -1342,8 +1485,8 @@ exports.Splat = class Splat extends Base assigns: (name) -> @name.assigns name - compile: (o) -> - if @index? then @compileParam o else @name.compile o + compileToFragments: (o) -> + if @index? then @compileParam o else @name.compileToFragments o unwrap: -> @name @@ -1352,20 +1495,26 @@ exports.Splat = class Splat extends Base @compileSplattedArray: (o, list, apply) -> index = -1 continue while (node = list[++index]) and node not instanceof Splat - return '' if index >= list.length + return [] if index >= list.length if list.length is 1 - code = list[0].compile o, LEVEL_LIST - return code if apply - return "#{ utility 'slice' }.call(#{code})" + node = list[0] + fragments = node.compileToFragments o, LEVEL_LIST + return fragments if apply + return [].concat node.makeCode("#{ utility 'slice' }.call("), fragments, node.makeCode(")") args = list[index..] for node, i in args - code = node.compile o, LEVEL_LIST + compiledNode = node.compileToFragments o, LEVEL_LIST args[i] = if node instanceof Splat - then "#{ utility 'slice' }.call(#{code})" - else "[#{code}]" - return args[0] + ".concat(#{ args[1..].join ', ' })" if index is 0 - base = (node.compile o, LEVEL_LIST for node in list[...index]) - "[#{ base.join ', ' }].concat(#{ args.join ', ' })" + then [].concat node.makeCode("#{ utility 'slice' }.call("), compiledNode, node.makeCode(")") + else [].concat node.makeCode("["), compiledNode, node.makeCode("]") + if index is 0 + node = list[0] + concatPart = (node.joinFragmentArrays args[1..], ', ') + return args[0].concat node.makeCode(".concat("), concatPart, node.makeCode(")") + base = (node.compileToFragments o, LEVEL_LIST for node in list[...index]) + base = list[0].joinFragmentArrays base, ', ' + concatPart = list[index].joinFragmentArrays args, ', ' + [].concat list[0].makeCode("["), base, list[index].makeCode("].concat("), concatPart, (last list).makeCode(")") #### While @@ -1416,11 +1565,12 @@ exports.While = class While extends Base body.expressions.unshift new If (new Parens @guard).invert(), new Literal "continue" else body = Block.wrap [new If @guard, body] if @guard - body = "\n#{ body.compile o, LEVEL_TOP }\n#{@tab}" - code = set + @tab + "while (#{ @condition.compile o, LEVEL_PAREN }) {#{body}}" + body = [].concat @makeCode("\n"), (body.compileToFragments o, LEVEL_TOP), @makeCode("\n#{@tab}") + answer = [].concat @makeCode(set + @tab + "while ("), @condition.compileToFragments(o, LEVEL_PAREN), + @makeCode(") {"), body, @makeCode("}") if @returns - code += "\n#{@tab}return #{rvar};" - code + answer.push @makeCode "\n#{@tab}return #{rvar};" + answer #### Op @@ -1525,9 +1675,9 @@ exports.Op = class Op extends Base return @compileUnary o if @isUnary() return @compileChain o if isChain return @compileExistence o if @operator is '?' - code = @first.compile(o, LEVEL_OP) + ' ' + @operator + ' ' + - @second.compile(o, LEVEL_OP) - if o.level <= LEVEL_OP then code else "(#{code})" + answer = [].concat @first.compileToFragments(o, LEVEL_OP), @makeCode(' ' + @operator + ' '), + @second.compileToFragments(o, LEVEL_OP) + if o.level <= LEVEL_OP then answer else @wrapInBraces answer # Mimic Python's chained comparisons when multiple comparison operators are # used sequentially. For example: @@ -1536,9 +1686,10 @@ exports.Op = class Op extends Base # true compileChain: (o) -> [@first.second, shared] = @first.second.cache o - fst = @first.compile o, LEVEL_OP - code = "#{fst} #{if @invert then '&&' else '||'} #{ shared.compile o } #{@operator} #{ @second.compile o, LEVEL_OP }" - "(#{code})" + fst = @first.compileToFragments o, LEVEL_OP + fragments = fst.concat @makeCode(" #{if @invert then '&&' else '||'} "), + (shared.compileToFragments o), @makeCode(" #{@operator} "), (@second.compileToFragments o, LEVEL_OP) + @wrapInBraces fragments compileExistence: (o) -> if @first.isComplex() @@ -1547,24 +1698,26 @@ exports.Op = class Op extends Base else fst = @first ref = fst - new If(new Existence(fst), ref, type: 'if').addElse(@second).compile o + new If(new Existence(fst), ref, type: 'if').addElse(@second).compileToFragments o # Compile a unary **Op**. compileUnary: (o) -> - parts = [op = @operator] + parts = [] + op = @operator + parts.push [@makeCode op] if op is '!' and @first instanceof Existence @first.negated = not @first.negated - return @first.compile o + return @first.compileToFragments o if o.level >= LEVEL_ACCESS - return (new Parens this).compile o + return (new Parens this).compileToFragments o plusMinus = op in ['+', '-'] - parts.push ' ' if op in ['new', 'typeof', 'delete'] or + parts.push [@makeCode(' ')] if op in ['new', 'typeof', 'delete'] or plusMinus and @first instanceof Op and @first.operator is op if (plusMinus && @first instanceof Op) or (op is 'new' and @first.isStatement o) @first = new Parens @first - parts.push @first.compile o, LEVEL_OP + parts.push @first.compileToFragments o, LEVEL_OP parts.reverse() if @flip - parts.join '' + @joinFragmentArrays parts, '' toString: (idt) -> super idt, @constructor.name + ' ' + @operator @@ -1587,21 +1740,22 @@ exports.In = class In extends Base @compileLoopTest o compileOrTest: (o) -> - return "#{!!@negated}" if @array.base.objects.length is 0 + return [@makeCode("#{!!@negated}")] if @array.base.objects.length is 0 [sub, ref] = @object.cache o, LEVEL_OP [cmp, cnj] = if @negated then [' !== ', ' && '] else [' === ', ' || '] - tests = for item, i in @array.base.objects - (if i then ref else sub) + cmp + item.compile o, LEVEL_ACCESS - tests = tests.join cnj - if o.level < LEVEL_OP then tests else "(#{tests})" + tests = [] + for item, i in @array.base.objects + if i then tests.push @makeCode cnj + tests = tests.concat (if i then ref else sub), @makeCode(cmp), item.compileToFragments(o, LEVEL_ACCESS) + if o.level < LEVEL_OP then tests else @wrapInBraces tests compileLoopTest: (o) -> [sub, ref] = @object.cache o, LEVEL_LIST - code = utility('indexOf') + ".call(#{ @array.compile o, LEVEL_LIST }, #{ref}) " + - if @negated then '< 0' else '>= 0' - return code if sub is ref - code = sub + ', ' + code - if o.level < LEVEL_LIST then code else "(#{code})" + fragments = [].concat @makeCode(utility('indexOf') + ".call("), @array.compileToFragments(o, LEVEL_LIST), + @makeCode(", "), ref, @makeCode(") " + if @negated then '< 0' else '>= 0') + return fragments if (fragmentsToText sub) is (fragmentsToText ref) + fragments = sub.concat @makeCode(', '), fragments + if o.level < LEVEL_LIST then fragments else @wrapInBraces fragments toString: (idt) -> super idt, @constructor.name + if @negated then '!' else '' @@ -1627,7 +1781,7 @@ exports.Try = class Try extends Base # is optional, the *catch* is not. compileNode: (o) -> o.indent += TAB - tryPart = @attempt.compile o, LEVEL_TOP + tryPart = @attempt.compileToFragments o, LEVEL_TOP catchPart = if @recovery if @error.isObject?() @@ -1637,15 +1791,18 @@ exports.Try = class Try extends Base if @error.value in STRICT_PROSCRIBED throw SyntaxError "catch variable may not be \"#{@error.value}\"" o.scope.add @error.value, 'param' unless o.scope.check @error.value - " catch (#{ @error.compile o }) {\n#{ @recovery.compile o, LEVEL_TOP }\n#{@tab}}" + [].concat @makeCode(" catch ("), @error.compileToFragments(o), @makeCode(") {\n"), + @recovery.compileToFragments(o, LEVEL_TOP), @makeCode("\n#{@tab}}") else unless @ensure or @recovery - ' catch (_error) {}' + [@makeCode(' catch (_error) {}')] + else + [] - ensurePart = if @ensure then " finally {\n#{ @ensure.compile o, LEVEL_TOP }\n#{@tab}}" else '' + ensurePart = if @ensure then ([].concat @makeCode(" finally {\n"), (@ensure.compileToFragments o, LEVEL_TOP), @makeCode("\n#{@tab}}")) else [] - """#{@tab}try { - #{tryPart} - #{@tab}}#{ catchPart or '' }#{ensurePart}""" + [].concat @makeCode("#{@tab}try {\n"), + tryPart, + @makeCode("\n#{@tab}}"), catchPart, ensurePart #### Throw @@ -1662,7 +1819,7 @@ exports.Throw = class Throw extends Base makeReturn: THIS compileNode: (o) -> - @tab + "throw #{ @expression.compile o };" + [].concat @makeCode(@tab + "throw "), (@expression.compileToFragments o), @makeCode(";") #### Existence @@ -1685,7 +1842,7 @@ exports.Existence = class Existence extends Base else # do not use strict equality here; it will break existing code code = "#{code} #{if @negated then '==' else '!='} null" - if o.level <= LEVEL_COND then code else "(#{code})" + [@makeCode(if o.level <= LEVEL_COND then code else "(#{code})")] #### Parens @@ -1706,11 +1863,11 @@ exports.Parens = class Parens extends Base expr = @body.unwrap() if expr instanceof Value and expr.isAtomic() expr.front = @front - return expr.compile o - code = expr.compile o, LEVEL_PAREN + return expr.compileToFragments o + fragments = expr.compileToFragments o, LEVEL_PAREN bare = o.level < LEVEL_OP and (expr instanceof Op or expr instanceof Call or (expr instanceof For and expr.returns)) - if bare then code else "(#{code})" + if bare then fragments else @wrapInBraces fragments #### For @@ -1747,8 +1904,8 @@ exports.For = class For extends While @returns = no if lastJumps and lastJumps instanceof Return source = if @range then @source.base else @source scope = o.scope - name = @name and @name.compile o, LEVEL_LIST - index = @index and @index.compile o, LEVEL_LIST + name = @name and (@name.compile o, LEVEL_LIST) + index = @index and (@index.compile o, LEVEL_LIST) scope.find(name) if name and not @pattern scope.find(index) if index rvar = scope.freeVariable 'results' if @returns @@ -1756,7 +1913,7 @@ exports.For = class For extends While kvar = (@range and name) or index or ivar kvarAssign = if kvar isnt ivar then "#{kvar} = " else "" if @step and not @range - [step, stepVar] = @step.cache o, LEVEL_LIST + [step, stepVar] = @cacheToCodeFragments @step.cache o, LEVEL_LIST stepNum = stepVar.match SIMPLENUM name = ivar if @pattern varPart = '' @@ -1764,7 +1921,7 @@ exports.For = class For extends While defPart = '' idt1 = @tab + TAB if @range - forPart = source.compile merge(o, {index: ivar, name, @step}) + forPartFragments = source.compileToFragments merge(o, {index: ivar, name, @step}) else svar = @source.compile o, LEVEL_LIST if (name or @own) and not IDENTIFIER.test svar @@ -1790,7 +1947,7 @@ exports.For = class For extends While increment = "#{ivar} += #{stepVar}" else increment = "#{if kvar isnt ivar then "++#{ivar}" else "#{ivar}++"}" - forPart = "#{declare}; #{compare}; #{kvarAssign}#{increment}" + forPartFragments = [@makeCode("#{declare}; #{compare}; #{kvarAssign}#{increment}")] if @returns resultPart = "#{@tab}#{rvar} = [];\n" returnResult = "\n#{@tab}return #{rvar};" @@ -1802,19 +1959,20 @@ exports.For = class For extends While body = Block.wrap [new If @guard, body] if @guard if @pattern body.expressions.unshift new Assign @name, new Literal "#{svar}[#{kvar}]" - defPart += @pluckDirectCall o, body - varPart = "\n#{idt1}#{namePart};" if namePart + defPartFragments = [].concat @makeCode(defPart), @pluckDirectCall(o, body) + varPart = "\n#{idt1}#{namePart};" if namePart if @object - forPart = "#{kvar} in #{svar}" + forPartFragments = [@makeCode("#{kvar} in #{svar}")] guardPart = "\n#{idt1}if (!#{utility 'hasProp'}.call(#{svar}, #{kvar})) continue;" if @own - body = body.compile merge(o, indent: idt1), LEVEL_TOP - body = '\n' + body + '\n' if body - """ - #{defPart}#{resultPart or ''}#{@tab}for (#{forPart}) {#{guardPart}#{varPart}#{body}#{@tab}}#{returnResult or ''} - """ + bodyFragments = body.compileToFragments merge(o, indent: idt1), LEVEL_TOP + if bodyFragments and (bodyFragments.length > 0) + bodyFragments = [].concat @makeCode("\n"), bodyFragments, @makeCode("\n") + [].concat defPartFragments, @makeCode("#{resultPart or ''}#{@tab}for ("), + forPartFragments, @makeCode(") {#{guardPart}#{varPart}"), bodyFragments, + @makeCode("#{@tab}}#{returnResult or ''}") pluckDirectCall: (o, body) -> - defs = '' + defs = [] for expr, idx in body.expressions expr = expr.unwrapAll() continue unless expr instanceof Call @@ -1830,7 +1988,7 @@ exports.For = class For extends While if val.base [val.base, base] = [base, val] body.expressions[idx] = new Call base, expr.args - defs += @tab + new Assign(ref, fn).compile(o, LEVEL_TOP) + ';\n' + defs = defs.concat @makeCode(@tab), (new Assign(ref, fn).compileToFragments(o, LEVEL_TOP)), @makeCode(';\n') defs #### Switch @@ -1857,18 +2015,22 @@ exports.Switch = class Switch extends Base compileNode: (o) -> idt1 = o.indent + TAB idt2 = o.indent = idt1 + TAB - code = @tab + "switch (#{ @subject?.compile(o, LEVEL_PAREN) or false }) {\n" + fragments = [].concat @makeCode(@tab + "switch ("), + (if @subject then @subject.compileToFragments(o, LEVEL_PAREN) else @makeCode("false")), + @makeCode(") {\n") for [conditions, block], i in @cases for cond in flatten [conditions] cond = cond.invert() unless @subject - code += idt1 + "case #{ cond.compile o, LEVEL_PAREN }:\n" - code += body + '\n' if body = block.compile o, LEVEL_TOP + fragments = fragments.concat @makeCode(idt1 + "case "), cond.compileToFragments(o, LEVEL_PAREN), @makeCode(":\n") + fragments = fragments.concat body, @makeCode('\n') if (body = block.compileToFragments o, LEVEL_TOP).length > 0 break if i is @cases.length - 1 and not @otherwise expr = @lastNonComment block.expressions continue if expr instanceof Return or (expr instanceof Literal and expr.jumps() and expr.value isnt 'debugger') - code += idt2 + 'break;\n' - code += idt1 + "default:\n#{ @otherwise.compile o, LEVEL_TOP }\n" if @otherwise and @otherwise.expressions.length - code + @tab + '}' + fragments.push cond.makeCode(idt2 + 'break;\n') + if @otherwise and @otherwise.expressions.length + fragments.push @makeCode(idt1 + "default:\n"), (@otherwise.compileToFragments o, LEVEL_TOP)..., @makeCode("\n") + fragments.push @makeCode @tab + '}' + fragments #### If @@ -1925,28 +2087,30 @@ exports.If = class If extends Base exeq = del o, 'isExistentialEquals' if exeq - return new If(@condition.invert(), @elseBodyNode(), type: 'if').compile o + return new If(@condition.invert(), @elseBodyNode(), type: 'if').compileToFragments o - cond = @condition.compile o, LEVEL_PAREN + cond = @condition.compileToFragments o, LEVEL_PAREN o.indent += TAB body = @ensureBlock(@body) - ifPart = "if (#{cond}) {\n#{body.compile(o)}\n#{@tab}}" - ifPart = @tab + ifPart unless child + ifPart = [].concat @makeCode("if ("), cond, @makeCode(") {\n"), body.compileToFragments(o), @makeCode("\n#{@tab}}") + ifPart.unshift @makeCode @tab unless child return ifPart unless @elseBody - ifPart + ' else ' + if @isChain + answer = ifPart.concat @makeCode(' else ') + if @isChain o.indent = @tab o.chainChild = yes - @elseBody.unwrap().compile o, LEVEL_TOP + answer = answer.concat @elseBody.unwrap().compileToFragments o, LEVEL_TOP else - "{\n#{ @elseBody.compile o, LEVEL_TOP }\n#{@tab}}" + answer = answer.concat @makeCode("{\n"), @elseBody.compileToFragments(o, LEVEL_TOP), @makeCode("\n#{@tab}}") + answer # Compile the `If` as a conditional operator. compileExpression: (o) -> - cond = @condition.compile o, LEVEL_COND - body = @bodyNode().compile o, LEVEL_LIST - alt = if @elseBodyNode() then @elseBodyNode().compile(o, LEVEL_LIST) else 'void 0' - code = "#{cond} ? #{body} : #{alt}" - if o.level >= LEVEL_COND then "(#{code})" else code + cond = @condition.compileToFragments o, LEVEL_COND + body = @bodyNode().compileToFragments o, LEVEL_LIST + alt = if @elseBodyNode() then @elseBodyNode().compileToFragments(o, LEVEL_LIST) else [@makeCode('void 0')] + fragments = cond.concat @makeCode(" ? "), body, @makeCode(" : "), alt + if o.level >= LEVEL_COND then @wrapInBraces fragments else fragments unfoldSoak: -> @soak and this