diff --git a/lib/browser.js b/lib/browser.js index 15df3576..075f6b43 100644 --- a/lib/browser.js +++ b/lib/browser.js @@ -6,7 +6,9 @@ return eval(CoffeeScript.compile(code, options)); }; CoffeeScript.run = function(code, options) { - options != null ? options.bare = true : undefined; + if (options != null) { + options.bare = true; + } return Function(CoffeeScript.compile(code, options))(); }; if (!(typeof window !== "undefined" && window !== null)) { diff --git a/lib/index.js b/lib/index.js index dc5ab13c..34791ee5 100644 --- a/lib/index.js +++ b/lib/index.js @@ -4,6 +4,6 @@ for (key in _ref = require('./coffee-script')) { if (!__hasProp.call(_ref, key)) continue; val = _ref[key]; - (exports[key] = val); + exports[key] = val; } }).call(this); diff --git a/lib/lexer.js b/lib/lexer.js index 24c3fdeb..ed018ad6 100644 --- a/lib/lexer.js +++ b/lib/lexer.js @@ -382,7 +382,6 @@ break; case '::': prev[0] = 'INDEX_PROTO'; - break; } } } diff --git a/lib/nodes.js b/lib/nodes.js index cb7e9718..93f1a690 100644 --- a/lib/nodes.js +++ b/lib/nodes.js @@ -1,5 +1,5 @@ (function() { - var Accessor, ArrayLiteral, Assign, Base, Call, Class, Closure, Code, Comment, Existence, Expressions, Extends, For, IDENTIFIER, IS_STRING, If, In, Index, LVL_ACCESS, LVL_ASSIGN, LVL_COND, LVL_LIST, LVL_OP, LVL_PAREN, LVL_TOP, Literal, NO, NUMBER, ObjectLiteral, Op, Param, Parens, Push, Return, SIMPLENUM, Scope, Splat, Switch, TAB, THIS, TRAILING_WHITESPACE, Throw, Try, UTILITIES, Value, While, YES, _ref, compact, del, ends, extend, flatten, last, merge, starts, utility; + var Accessor, ArrayLiteral, Assign, Base, Call, Class, Closure, Code, Comment, Existence, Expressions, Extends, For, IDENTIFIER, IS_STRING, If, In, Index, LVL_ACCESS, LVL_COND, LVL_LIST, LVL_OP, LVL_PAREN, LVL_TOP, Literal, NO, NUMBER, ObjectLiteral, Op, Param, Parens, Push, Return, SIMPLENUM, Scope, Splat, Switch, TAB, THIS, TRAILING_WHITESPACE, Throw, Try, UTILITIES, Value, While, YES, _ref, compact, del, ends, extend, flatten, last, merge, multident, starts, utility; var __extends = function(child, parent) { function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; @@ -31,14 +31,14 @@ return Base; })(); Base.prototype.compile = function(o, lvl) { - var top; + var node; o = o ? extend({}, o) : {}; - if (lvl) { + if (lvl != null) { o.level = lvl; } - top = this.topSensitive ? o.top : del(o, 'top'); - this.tab = o.indent; - return top || o.asStatement || this instanceof Comment || this.isPureStatement() || !this.isStatement(o) ? this.compileNode(o) : this.compileClosure(o); + node = this.unfoldSoak(o) || this; + node.tab = o.indent; + return o.level === LVL_TOP || node.isPureStatement() || !node.isStatement(o) ? node.compileNode(o) : node.compileClosure(o); }; Base.prototype.compileClosure = function(o) { if (this.containsPureStatement()) { @@ -60,7 +60,7 @@ }; Base.prototype.compileLoopReference = function(o, name) { var src, tmp; - src = tmp = this.compile(o, LVL_ASSIGN); + src = tmp = this.compile(o, LVL_LIST); if (!(NUMBER.test(src) || IDENTIFIER.test(src) && o.scope.check(src, { immediate: true }))) { @@ -98,7 +98,7 @@ Base.prototype.toString = function(idt, override) { var _i, _len, _ref2, _result, child, children, klass; idt || (idt = ''); - children = (function() { + children = ((function() { _ref2 = this.collectChildren(); _result = []; for (_i = 0, _len = _ref2.length; _i < _len; _i++) { @@ -106,7 +106,7 @@ _result.push(child.toString(idt + TAB)); } return _result; - }).call(this).join(''); + }).call(this)).join(''); klass = override || this.constructor.name + (this.soakNode ? '?' : ''); return '\n' + idt + klass + children; }; @@ -155,7 +155,6 @@ Base.prototype.isPureStatement = NO; Base.prototype.isComplex = YES; Base.prototype.isChainable = NO; - Base.prototype.topSensitive = false; Base.prototype.unfoldSoak = NO; Base.prototype.assigns = NO; return Base; @@ -204,15 +203,16 @@ }; Expressions.prototype.compileNode = function(o) { var _i, _len, _ref2, _result, node; - return (function() { + this.tab = o.indent; + return ((function() { _ref2 = this.expressions; _result = []; for (_i = 0, _len = _ref2.length; _i < _len; _i++) { node = _ref2[_i]; - _result.push(this.compileExpression(node, merge(o))); + _result.push(this.compileExpression(node, o)); } return _result; - }).call(this).join('\n'); + }).call(this)).join('\n'); }; Expressions.prototype.compileRoot = function(o) { var code; @@ -224,20 +224,25 @@ return o.bare ? code : "(function() {\n" + code + "\n}).call(this);\n"; }; Expressions.prototype.compileWithDeclarations = function(o) { - var code; + var code, scope; code = this.compileNode(o); - if (o.scope.hasAssignments(this)) { - code = "" + this.tab + "var " + (o.scope.compiledAssignments().replace(/\n/g, '$&' + this.tab)) + ";\n" + code; + scope = o.scope; + if (scope.hasAssignments(this)) { + code = "" + this.tab + "var " + (multident(scope.compiledAssignments(), this.tab)) + ";\n" + code; } if (!o.globals && o.scope.hasDeclarations(this)) { - code = "" + this.tab + "var " + (o.scope.compiledDeclarations()) + ";\n" + code; + code = "" + this.tab + "var " + (scope.compiledDeclarations()) + ";\n" + code; } return code; }; Expressions.prototype.compileExpression = function(node, o) { var code; - o.top = node.tags.front = true; - this.tab = o.indent; + while (node !== (node = node.unwrap())) { + + } + node = node.unfoldSoak(o) || node; + node.tags.front = true; + o.level = LVL_TOP; code = node.compile(o); return node.isStatement(o) ? code : this.tab + code + ';'; }; @@ -262,19 +267,16 @@ Literal.prototype.makeReturn = function() { return this.isStatement() ? this : Literal.__super__.makeReturn.call(this); }; - Literal.prototype.isStatement = function() { + Literal.prototype.isPureStatement = function() { var _ref2; return (_ref2 = this.value) === 'break' || _ref2 === 'continue' || _ref2 === 'debugger'; }; - Literal.prototype.isPureStatement = Literal.prototype.isStatement; Literal.prototype.isComplex = NO; Literal.prototype.assigns = function(name) { return name === this.value; }; - Literal.prototype.compileNode = function() { - var val; - val = this.value.reserved ? "\"" + this.value + "\"" : this.value; - return this.isStatement() ? this.tab + val + ';' : val; + Literal.prototype.compile = function() { + return this.value.reserved ? "\"" + this.value + "\"" : this.value; }; Literal.prototype.toString = function() { return ' "' + this.value + '"'; @@ -301,15 +303,8 @@ return expr && !(expr instanceof Return) ? expr.compile(o, lvl) : Return.__super__.compile.call(this, o, lvl); }; Return.prototype.compileNode = function(o) { - var expr; - expr = ''; - if (this.expression) { - if (this.expression.isStatement(o)) { - o.asStatement = true; - } - expr = ' ' + this.expression.compile(o, LVL_PAREN); - } - return this.tab + 'return' + expr + ';'; + o.level = LVL_PAREN; + return this.tab + ("return" + (this.expression ? ' ' + this.expression.compile(o) : '') + ";"); }; return Return; })(); @@ -365,7 +360,7 @@ return this.properties.length ? this : this.base; }; Value.prototype.isStatement = function(o) { - return this.base.isStatement(o) && !this.properties.length; + return !this.properties.length && this.base.isStatement(o); }; Value.prototype.isSimpleNumber = function() { return this.base instanceof Literal && SIMPLENUM.test(this.base.value); @@ -391,23 +386,17 @@ } return [base.push(name), new Value(bref || base.base, [nref || name])]; }; - Value.prototype.compile = function(o, lvl) { - this.base.tags.front = this.tags.front; - return !o.top || this.properties.length ? Value.__super__.compile.call(this, o, lvl) : this.base.compile(o, lvl); - }; Value.prototype.compileNode = function(o) { - var _i, _len, code, ifn, prop, props; - if (ifn = this.unfoldSoak(o)) { - return ifn.compile(o); - } + var _i, _len, code, prop, props; + this.base.tags.front = this.tags.front; props = this.properties; - code = this.base.compile(o, props.length && LVL_ACCESS); + code = this.base.compile(o, props.length ? LVL_ACCESS : null); if (props[0] instanceof Accessor && this.isSimpleNumber()) { code = "(" + code + ")"; } for (_i = 0, _len = props.length; _i < _len; _i++) { prop = props[_i]; - (code += prop.compileNode(o)); + code += prop.compile(o); } return code; }; @@ -451,10 +440,10 @@ return Comment; })(); __extends(Comment, Base); - Comment.prototype.isStatement = YES; + Comment.prototype.isPureStatement = YES; Comment.prototype.makeReturn = THIS; Comment.prototype.compileNode = function(o) { - return this.tab + '/*' + this.comment.replace(/\n/g, '\n' + this.tab) + '*/'; + return this.tab + '/*' + multident(this.comment, this.tab) + '*/'; }; return Comment; })(); @@ -543,11 +532,10 @@ return ifn; }; Call.prototype.compileNode = function(o) { - var _i, _j, _len, _len2, _ref2, _ref3, _ref4, _result, arg, args, ifn; - if (ifn = this.unfoldSoak(o)) { - return ifn.compile(o); + var _i, _j, _len, _len2, _ref2, _ref3, _ref4, _result, arg, args; + if ((_ref2 = this.variable) != null) { + _ref2.tags.front = this.tags.front; } - (_ref2 = this.variable) != null ? _ref2.tags.front = this.tags.front : undefined; _ref3 = this.args; for (_i = 0, _len = _ref3.length; _i < _len; _i++) { arg = _ref3[_i]; @@ -555,7 +543,7 @@ return this.compileSplat(o); } } - args = (function() { + args = ((function() { _ref4 = this.args; _result = []; for (_j = 0, _len2 = _ref4.length; _j < _len2; _j++) { @@ -563,7 +551,7 @@ _result.push(arg.compile(o, LVL_LIST)); } return _result; - }).call(this).join(', '); + }).call(this)).join(', '); return this.isSuper ? this.compileSuper(args, o) : (this.isNew ? 'new ' : '') + this.variable.compile(o, LVL_ACCESS) + ("(" + args + ")"); }; Call.prototype.compileSuper = function(args, o) { @@ -579,11 +567,11 @@ base = Value.wrap(this.variable); if ((name = base.properties.pop()) && base.isComplex()) { ref = o.scope.freeVariable('this'); - fun = "(" + ref + " = " + (base.compile(o, LVL_ASSIGN)) + ")" + (name.compileNode(o)); + fun = "(" + ref + " = " + (base.compile(o, LVL_LIST)) + ")" + (name.compile(o)); } else { fun = ref = base.compile(o, LVL_ACCESS); if (name) { - fun += name.compileNode(o); + fun += name.compile(o); } } return "" + fun + ".apply(" + ref + ", " + splatargs + ")"; @@ -605,7 +593,7 @@ })(); __extends(Extends, Base); Extends.prototype.children = ['child', 'parent']; - Extends.prototype.compileNode = function(o) { + Extends.prototype.compile = function(o) { return new Call(new Value(new Literal(utility('extends'))), [this.child, this.parent]).compile(o); }; return Extends; @@ -623,7 +611,7 @@ })(); __extends(Accessor, Base); Accessor.prototype.children = ['name']; - Accessor.prototype.compileNode = function(o) { + Accessor.prototype.compile = function(o) { var name; name = this.name.compile(o); return this.proto + (IS_STRING.test(name) ? "[" + name + "]" : "." + name); @@ -642,7 +630,7 @@ })(); __extends(Index, Base); Index.prototype.children = ['index']; - Index.prototype.compileNode = function(o) { + Index.prototype.compile = function(o) { return (this.proto ? '.prototype' : '') + ("[" + (this.index.compile(o, LVL_PAREN)) + "]"); }; Index.prototype.isComplex = function() { @@ -662,8 +650,7 @@ __extends(ObjectLiteral, Base); ObjectLiteral.prototype.children = ['properties']; ObjectLiteral.prototype.compileNode = function(o) { - var _i, _len, _ref2, _result, i, indent, join, lastNoncom, nonComments, obj, prop, props, top; - top = del(o, 'top'); + var _i, _len, _ref2, _result, i, indent, join, lastNoncom, nonComments, obj, prop, props; o.indent = this.idt(1); nonComments = (function() { _ref2 = this.properties; @@ -779,14 +766,13 @@ return this; }; Class.prototype.compileNode = function(o) { - var _i, _len, _ref2, _ref3, access, applied, apply, className, constScope, construct, constructor, extension, func, me, pname, prop, props, pvar, ref, returns, val, variable; + var _i, _len, _ref2, _ref3, access, applied, apply, className, constScope, construct, constructor, extension, func, me, pname, prop, props, pvar, ref, val, variable; variable = this.variable; if (variable.value === '__temp__') { variable = new Literal(o.scope.freeVariable('ctor')); } extension = this.parent && new Extends(variable, this.parent); props = new Expressions; - o.top = true; me = null; className = variable.compile(o); constScope = null; @@ -847,13 +833,18 @@ if (me) { constructor.body.unshift(new Literal("" + me + " = this")); } - construct = this.idt() + new Assign(variable, constructor).compile(merge(o, { - sharedScope: constScope - })) + ';'; - props = !props.empty() ? '\n' + props.compile(o) : ''; - extension = extension ? '\n' + this.idt() + extension.compile(o) + ';' : ''; - returns = this.returns ? '\n' + new Return(variable).compile(o) : ''; - return construct + extension + props + returns; + o.sharedScope = constScope; + construct = this.tab + new Assign(variable, constructor).compile(o) + ';'; + if (extension) { + construct += '\n' + this.tab + extension.compile(o) + ';'; + } + if (!props.empty()) { + construct += '\n' + props.compile(o); + } + if (this.returns) { + construct += '\n' + new Return(variable).compile(o); + } + return construct; }; return Class; })(); @@ -872,29 +863,28 @@ Assign.prototype.METHOD_DEF = /^(?:(\S+)\.prototype\.)?([$A-Za-z_][$\w]*)$/; Assign.prototype.CONDITIONAL = ['||=', '&&=', '?=']; Assign.prototype.children = ['variable', 'value']; - Assign.prototype.topSensitive = true; + Assign.prototype.assigns = function(name) { + return this[this.context === 'object' ? 'value' : 'variable'].assigns(name); + }; + Assign.prototype.unfoldSoak = function(o) { + return If.unfoldSoak(o, this, 'variable'); + }; Assign.prototype.compileNode = function(o) { - var _ref2, ifn, isValue, match, name, stmt, top, val; + var _ref2, isValue, match, name, val; if (isValue = this.variable instanceof Value) { if (this.variable.isArray() || this.variable.isObject()) { return this.compilePatternMatch(o); } - if (ifn = If.unfoldSoak(o, this, 'variable')) { - delete o.top; - return ifn.compile(o); - } if (_ref2 = this.context, __indexOf.call(this.CONDITIONAL, _ref2) >= 0) { return this.compileConditional(o); } } - top = del(o, 'top'); - stmt = del(o, 'asStatement'); - name = this.variable.compile(o, LVL_ASSIGN); + name = this.variable.compile(o, LVL_LIST); if (this.value instanceof Code && (match = this.METHOD_DEF.exec(name))) { this.value.name = match[2]; this.value.klass = match[1]; } - val = this.value.compile(o, LVL_ASSIGN); + val = this.value.compile(o, LVL_LIST); if (this.context === 'object') { return "" + name + ": " + val; } @@ -902,17 +892,11 @@ o.scope.find(name); } val = name + (" " + (this.context || '=') + " ") + val; - if (stmt) { - return "" + this.tab + val + ";"; - } - return top || o.level <= LVL_ASSIGN ? val : "(" + val + ")"; + return o.level <= LVL_LIST ? val : "(" + val + ")"; }; Assign.prototype.compilePatternMatch = function(o) { - var _len, _ref2, _ref3, accessClass, assigns, code, i, idx, isObject, obj, objects, olength, otop, ref, splat, top, val, valVar, value; - top = del(o, 'top'); - otop = merge(o, { - top: true - }); + var _len, _ref2, _ref3, _ref4, _ref5, accessClass, assigns, code, i, idx, isObject, obj, objects, olength, ref, splat, top, val, valVar, value; + top = o.level === LVL_TOP; value = this.value; objects = this.variable.base.objects; if (!(olength = objects.length)) { @@ -921,15 +905,15 @@ isObject = this.variable.isObject(); if (top && olength === 1 && !((obj = objects[0]) instanceof Splat)) { if (obj instanceof Assign) { - _ref2 = obj, idx = _ref2.variable.base, obj = _ref2.value; + _ref2 = obj, (_ref3 = _ref2.variable, idx = _ref3.base, _ref3), obj = _ref2.value; } else { idx = isObject ? obj.tags["this"] ? obj.properties[0].name : obj : new Literal(0); } accessClass = IDENTIFIER.test(idx.value) ? Accessor : Index; (value = Value.wrap(value)).properties.push(new accessClass(idx)); - return new Assign(obj, value).compile(otop); + return new Assign(obj, value).compile(o); } - valVar = value.compile(o); + valVar = value.compile(o, LVL_LIST); assigns = []; splat = false; if (!IDENTIFIER.test(valVar) || this.variable.assigns(valVar)) { @@ -941,7 +925,7 @@ idx = i; if (isObject) { if (obj instanceof Assign) { - _ref3 = obj, idx = _ref3.variable.base, obj = _ref3.value; + _ref4 = obj, (_ref5 = _ref4.variable, idx = _ref5.base, _ref5), obj = _ref4.value; } else { idx = obj.tags["this"] ? obj.properties[0].name : obj; } @@ -959,22 +943,19 @@ } val = new Value(new Literal(valVar), [new accessClass(idx)]); } - assigns.push(new Assign(obj, val).compile(otop)); + assigns.push(new Assign(obj, val).compile(o, LVL_LIST)); } if (!top) { assigns.push(valVar); } code = assigns.join(', '); - return top || o.level <= LVL_PAREN ? code : "(" + code + ")"; + return o.level < LVL_LIST ? code : "(" + code + ")"; }; Assign.prototype.compileConditional = function(o) { var _ref2, left, rite; _ref2 = this.variable.cacheReference(o), left = _ref2[0], rite = _ref2[1]; return new Op(this.context.slice(0, -1), left, new Assign(rite, this.value)).compile(o); }; - Assign.prototype.assigns = function(name) { - return this[this.context === 'object' ? 'value' : 'variable'].assigns(name); - }; return Assign; })(); exports.Code = (function() { @@ -996,11 +977,9 @@ __extends(Code, Base); Code.prototype.children = ['params', 'body']; Code.prototype.compileNode = function(o) { - var _i, _len, _len2, _ref2, _ref3, _result, close, code, comm, empty, func, i, open, param, params, sharedScope, splat, top, value; + var _i, _len, _len2, _ref2, _ref3, _result, close, code, comm, empty, func, i, idt, open, param, params, scope, sharedScope, splat, value; sharedScope = del(o, 'sharedScope'); - top = del(o, 'top'); - o.scope = sharedScope || new Scope(o.scope, this.body, this); - o.top = true; + o.scope = scope = sharedScope || new Scope(o.scope, this.body, this); o.indent = this.idt(1); empty = this.body.expressions.length === 0; delete o.bare; @@ -1019,7 +998,7 @@ } else { if (param.attach) { value = param.value; - _ref3 = [new Literal(o.scope.freeVariable('arg')), param.splat], param = _ref3[0], param.splat = _ref3[1]; + _ref3 = [new Literal(scope.freeVariable('arg')), param.splat], param = _ref3[0], param.splat = _ref3[1]; this.body.unshift(new Assign(new Value(new Literal('this'), [new Accessor(value)]), param)); } if (param.splat) { @@ -1033,31 +1012,36 @@ } } } - o.scope.startLevel(); + scope.startLevel(); + if (!(empty || this.noReturn)) { + this.body.makeReturn(); + } params = (function() { _result = []; for (_i = 0, _len2 = params.length; _i < _len2; _i++) { param = params[_i]; - _result.push(param.compile(o)); + _result.push((function() { + scope.parameter(param = param.compile(o)); + return param; + })()); } return _result; })(); - if (!(empty || this.noReturn)) { - this.body.makeReturn(); - } - for (_i = 0, _len2 = params.length; _i < _len2; _i++) { - param = params[_i]; - o.scope.parameter(param); - } comm = this.comment ? this.comment.compile(o) + '\n' : ''; if (this.className) { o.indent = this.idt(2); } + idt = this.idt(1); code = this.body.expressions.length ? "\n" + (this.body.compileWithDeclarations(o)) + "\n" : ''; - open = this.className ? "(function() {\n" + comm + (this.idt(1)) + "function " + this.className + "(" : "function("; - close = this.className ? "" + (code && this.idt(1)) + "};\n" + (this.idt(1)) + "return " + this.className + ";\n" + this.tab + "})()" : "" + (code && this.tab) + "}"; + if (this.className) { + open = "(function() {\n" + comm + idt + "function " + this.className + "("; + close = "" + (code && idt) + "};\n" + idt + "return " + this.className + ";\n" + this.tab + "})()"; + } else { + open = "function("; + close = "" + (code && this.tab) + "}"; + } func = "" + open + (params.join(', ')) + ") {" + code + close; - o.scope.endLevel(); + scope.endLevel(); if (this.bound) { return "" + (utility('bind')) + "(" + func + ", " + this.context + ")"; } @@ -1082,7 +1066,7 @@ })(); __extends(Param, Base); Param.prototype.children = ['name']; - Param.prototype.compileNode = function(o) { + Param.prototype.compile = function(o) { return this.value.compile(o, LVL_LIST); }; Param.prototype.toString = function() { @@ -1112,7 +1096,7 @@ Splat.prototype.assigns = function(name) { return this.name.assigns(name); }; - Splat.prototype.compileNode = function(o) { + Splat.prototype.compile = function(o) { return this.index != null ? this.compileParam(o) : this.name.compile(o); }; Splat.prototype.compileParam = function(o) { @@ -1182,7 +1166,6 @@ })(); __extends(While, Base); While.prototype.children = ['condition', 'guard', 'body']; - While.prototype.topSensitive = true; While.prototype.isStatement = YES; While.prototype.addBody = function(body) { this.body = body; @@ -1193,31 +1176,26 @@ return this; }; While.prototype.compileNode = function(o) { - var cond, post, pre, rvar, set, top; - top = del(o, 'top') && !this.returns; + var body, code, rvar, set; o.indent = this.idt(1); - cond = this.condition.compile(o, LVL_PAREN); - o.top = true; set = ''; - if (!top) { + body = this.body; + if (o.level > LVL_TOP || this.returns) { rvar = o.scope.freeVariable('result'); set = "" + this.tab + rvar + " = [];\n"; - if (this.body) { - this.body = Push.wrap(rvar, this.body); + if (body) { + body = Push.wrap(rvar, body); } } - pre = "" + set + this.tab + "while (" + cond + ")"; if (this.guard) { - this.body = Expressions.wrap([new If(this.guard, this.body)]); + body = Expressions.wrap([new If(this.guard, body)]); } + code = set + this.tab + ("while (" + (this.condition.compile(o, LVL_PAREN)) + ") {\n" + (body.compile(o, LVL_TOP)) + "\n" + this.tab + "}"); if (this.returns) { - post = '\n' + new Return(new Literal(rvar)).compile(merge(o, { - indent: this.idt() - })); - } else { - post = ''; + o.indent = this.tab; + code += '\n' + new Return(new Literal(rvar)).compile(o); } - return "" + pre + " {\n" + (this.body.compile(o)) + "\n" + this.tab + "}" + post; + return code; }; return While; })(); @@ -1278,12 +1256,12 @@ Op.prototype.toString = function(idt) { return Op.__super__.toString.call(this, idt, this.constructor.name + ' ' + this.operator); }; + Op.prototype.unfoldSoak = function(o) { + var _ref2; + return (_ref2 = this.operator, __indexOf.call(this.MUTATORS, _ref2) >= 0) && If.unfoldSoak(o, this, 'first'); + }; Op.prototype.compileNode = function(o) { - var _ref2, ifn; if (this.isUnary()) { - if ((_ref2 = this.operator, __indexOf.call(this.MUTATORS, _ref2) >= 0) && (ifn = If.unfoldSoak(o, this, 'first'))) { - return ifn.compile(o); - } return this.compileUnary(o); } if (this.isChainable() && this.first.unwrap().isChainable()) { @@ -1309,7 +1287,7 @@ fst = this.first; ref = fst.compile(o); } - return new Existence(fst).compile(o) + (" ? " + ref + " : " + (this.second.compile(o, LVL_ASSIGN))); + return new Existence(fst).compile(o) + (" ? " + ref + " : " + (this.second.compile(o, LVL_LIST))); }; Op.prototype.compileUnary = function(o) { var _ref2, parts, space; @@ -1394,14 +1372,11 @@ return this; }; Try.prototype.compileNode = function(o) { - var attemptPart, catchPart, errorPart, finallyPart; + var catchPart, errorPart; o.indent = this.idt(1); - o.top = true; - attemptPart = this.attempt.compile(o); errorPart = this.error ? " (" + (this.error.compile(o)) + ") " : ' '; - catchPart = this.recovery ? " catch" + errorPart + "{\n" + (this.recovery.compile(o)) + "\n" + this.tab + "}" : !(this.ensure || this.recovery) ? ' catch (_e) {}' : ''; - finallyPart = (this.ensure || '') && ' finally {\n' + this.ensure.compile(merge(o)) + ("\n" + this.tab + "}"); - return "" + this.tab + "try {\n" + attemptPart + "\n" + this.tab + "}" + catchPart + finallyPart; + catchPart = this.recovery ? " catch" + errorPart + "{\n" + (this.recovery.compile(o, LVL_TOP)) + "\n" + this.tab + "}" : !(this.ensure || this.recovery) ? ' catch (_e) {}' : undefined; + return ("" + this.tab + "try {\n" + (this.attempt.compile(o, LVL_TOP)) + "\n" + this.tab + "}" + (catchPart || '')) + (this.ensure ? " finally {\n" + (this.ensure.compile(o, LVL_TOP)) + "\n" + this.tab + "}" : ''); }; return Try; })(); @@ -1419,7 +1394,7 @@ Throw.prototype.isStatement = YES; Throw.prototype.makeReturn = THIS; Throw.prototype.compileNode = function(o) { - return "" + this.tab + "throw " + (this.expression.compile(o)) + ";"; + return this.tab + ("throw " + (this.expression.compile(o)) + ";"); }; return Throw; })(); @@ -1453,9 +1428,8 @@ })(); __extends(Parens, Base); Parens.prototype.children = ['expression']; - Parens.prototype.topSensitive = true; - Parens.prototype.isStatement = function(o) { - return this.expression.isStatement(o); + Parens.prototype.unwrap = function() { + return this.expression; }; Parens.prototype.isComplex = function() { return this.expression.isComplex(); @@ -1464,21 +1438,15 @@ return this.expression.makeReturn(); }; Parens.prototype.compileNode = function(o) { - var code, expr, top; - top = del(o, 'top'); + var bare, code, expr; expr = this.expression; if (expr instanceof Value && expr.isAtomic()) { expr.tags.front = this.tags.front; return expr.compile(o); } + bare = o.level < LVL_OP && (expr instanceof Op || expr instanceof Call); code = expr.compile(o, LVL_PAREN); - if (o.level < LVL_OP && (expr instanceof Op || expr instanceof Call)) { - return code; - } - if (expr.isStatement(o)) { - return (top ? this.tab + code + ';' : code); - } - return "(" + code + ")"; + return bare ? code : "(" + code + ")"; }; return Parens; })(); @@ -1505,7 +1473,6 @@ })(); __extends(For, Base); For.prototype.children = ['body', 'source', 'guard', 'step', 'from', 'to']; - For.prototype.topSensitive = true; For.prototype.isStatement = YES; For.prototype.makeReturn = function() { this.returns = true; @@ -1521,9 +1488,8 @@ return ''; }; For.prototype.compileNode = function(o) { - var _ref2, _ref3, _ref4, _ref5, _ref6, body, cond, defPart, forPart, guardPart, idt, index, ivar, lvar, name, namePart, pvar, resultRet, rvar, scope, sourcePart, step, svar, tail, top, tvar, varPart, vars; + var _ref2, _ref3, _ref4, _ref5, _ref6, body, cond, defPart, forPart, guardPart, idt, index, ivar, lvar, name, namePart, pvar, resultRet, rvar, scope, sourcePart, step, svar, tail, tvar, varPart, vars; scope = o.scope; - top = del(o, 'top') && !this.returns; name = !this.pattern && ((_ref2 = this.name) != null ? _ref2.compile(o) : undefined); index = (_ref3 = this.index) != null ? _ref3.compile(o) : undefined; ivar = !index ? scope.freeVariable('i') : index; @@ -1554,11 +1520,9 @@ if (name) { _ref6 = this.source.compileLoopReference(o, 'ref'), sourcePart = _ref6[0], svar = _ref6[1]; } else { - sourcePart = svar = this.source.compile(o); + sourcePart = svar = this.source.compile(o, LVL_PAREN); } - namePart = this.pattern ? new Assign(this.name, new Literal("" + svar + "[" + ivar + "]")).compile(merge(o, { - top: true - })) : name ? "" + name + " = " + svar + "[" + ivar + "]" : undefined; + namePart = this.pattern ? new Assign(this.name, new Literal("" + svar + "[" + ivar + "]")).compile(o, LVL_TOP) : name ? "" + name + " = " + svar + "[" + ivar + "]" : undefined; if (!this.object) { if (0 > pvar && (pvar | 0) === +pvar) { vars = "" + ivar + " = " + svar + ".length - 1"; @@ -1581,18 +1545,18 @@ if (svar !== sourcePart) { defPart = "" + this.tab + sourcePart + ";\n"; } - forPart = ("" + vars + "; " + cond + "; ") + (function() { + forPart = ("" + vars + "; " + cond + "; ") + ivar + (function() { switch (+pvar) { case 1: - return ivar + '++'; + return '++'; case -1: - return ivar + '--'; + return '--'; default: - return ivar + (pvar < 0 ? ' -= ' + pvar.slice(1) : ' += ' + pvar); + return pvar < 0 ? ' -= ' + pvar.slice(1) : ' += ' + pvar; } })(); } - if (!top) { + if (o.level > LVL_TOP || this.returns) { rvar = scope.freeVariable('result'); defPart += "" + this.tab + rvar + " = [];\n"; resultRet = this.compileReturnValue(rvar, o); @@ -1604,10 +1568,8 @@ if (namePart) { varPart = "" + idt + namePart + ";\n"; } - return "" + (defPart || '') + this.tab + "for (" + forPart + ") {\n" + (guardPart || '') + varPart + (body.compile(merge(o, { - indent: idt, - top: true - }))) + "\n" + this.tab + "}" + (resultRet || ''); + o.indent = idt; + return "" + (defPart || '') + this.tab + "for (" + forPart + ") {\n" + (guardPart || '') + varPart + (body.compile(o, LVL_TOP)) + "\n" + this.tab + "}" + (resultRet || ''); }; return For; })(); @@ -1626,43 +1588,51 @@ Switch.prototype.children = ['subject', 'cases', 'otherwise']; Switch.prototype.isStatement = YES; Switch.prototype.makeReturn = function() { - var _i, _len, _ref2, pair; + var _i, _len, _ref2, _ref3, pair; _ref2 = this.cases; for (_i = 0, _len = _ref2.length; _i < _len; _i++) { pair = _ref2[_i]; pair[1].makeReturn(); } - if (this.otherwise) { - this.otherwise.makeReturn(); + if ((_ref3 = this.otherwise) != null) { + _ref3.makeReturn(); } return this; }; Switch.prototype.compileNode = function(o) { - var _i, _j, _len, _len2, _ref2, _ref3, _ref4, block, code, condition, conditions, idt1, idt2; + var _i, _j, _len, _len2, _ref2, _ref3, _ref4, _ref5, block, code, cond, conditions, expr, i, idt1, idt2; idt1 = this.idt(1); idt2 = o.indent = this.idt(2); - o.top = true; - code = "" + this.tab + "switch (" + (((_ref2 = this.subject) != null ? _ref2.compile(o) : undefined) || true) + ") {"; - for (_i = 0, _len = this.cases.length; _i < _len; _i++) { - _ref3 = this.cases[_i], conditions = _ref3[0], block = _ref3[1]; + code = this.tab + ("switch (" + (((_ref2 = this.subject) != null ? _ref2.compile(o, LVL_PAREN) : undefined) || true) + ") {\n"); + for (i = 0, _len = this.cases.length; i < _len; i++) { + _ref3 = this.cases[i], conditions = _ref3[0], block = _ref3[1]; _ref4 = flatten([conditions]); - for (_j = 0, _len2 = _ref4.length; _j < _len2; _j++) { - condition = _ref4[_j]; + for (_i = 0, _len2 = _ref4.length; _i < _len2; _i++) { + cond = _ref4[_i]; if (!this.subject) { - condition = condition.invert().invert(); + cond = cond.invert().invert(); } - code += "\n" + idt1 + "case " + (condition.compile(o)) + ":"; + code += idt1 + ("case " + (cond.compile(o, LVL_PAREN)) + ":\n"); } - code += "\n" + (block.compile(o)); - if (!(last(block.expressions) instanceof Return)) { - code += "\n" + idt2 + "break;"; + code += block.compile(o, LVL_TOP) + '\n'; + if (i === this.cases.length - 1 && !this.otherwise) { + break; + } + _ref5 = block.expressions; + for (_j = _ref5.length - 1; _j >= 0; _j--) { + expr = _ref5[_j]; + if (!(expr instanceof Comment)) { + if (!(expr instanceof Return)) { + code += idt2 + 'break;\n'; + } + break; + } } } if (this.otherwise) { - code += "\n" + idt1 + "default:\n" + (this.otherwise.compile(o)); + code += idt1 + ("default:\n" + (this.otherwise.compile(o, LVL_TOP)) + "\n"); } - code += "\n" + this.tab + "}"; - return code; + return code + this.tab + '}'; }; return Switch; })(); @@ -1681,7 +1651,6 @@ })(); __extends(If, Base); If.prototype.children = ['condition', 'body', 'elseBody']; - If.prototype.topSensitive = true; If.prototype.bodyNode = function() { var _ref2; return (_ref2 = this.body) != null ? _ref2.unwrap() : undefined; @@ -1701,7 +1670,7 @@ }; If.prototype.isStatement = function(o) { var _ref2; - return this.statement || (this.statement = (o != null ? o.top : undefined) || this.bodyNode().isStatement(o) || ((_ref2 = this.elseBodyNode()) != null ? _ref2.isStatement(o) : undefined)); + return (o != null ? o.level : undefined) === LVL_TOP || this.bodyNode().isStatement(o) || ((_ref2 = this.elseBodyNode()) != null ? _ref2.isStatement(o) : undefined); }; If.prototype.compileNode = function(o) { return this.isStatement(o) ? this.compileStatement(o) : this.compileExpression(o); @@ -1719,13 +1688,12 @@ return node instanceof Expressions ? node : new Expressions([node]); }; If.prototype.compileStatement = function(o) { - var child, condO, ifPart, top; - top = del(o, 'top'); + var body, child, cond, ifPart; child = del(o, 'chainChild'); - condO = merge(o); + cond = this.condition.compile(o, LVL_PAREN); o.indent = this.idt(1); - o.top = true; - ifPart = "if (" + (this.condition.compile(condO, LVL_PAREN)) + ") {\n" + (this.body.compile(o)) + "\n" + this.tab + "}"; + body = this.ensureExpressions(this.body).compile(o); + ifPart = "if (" + cond + ") {\n" + body + "\n" + this.tab + "}"; if (!child) { ifPart = this.tab + ifPart; } @@ -1735,11 +1703,11 @@ return ifPart + ' else ' + (this.isChain ? this.elseBodyNode().compile(merge(o, { indent: this.tab, chainChild: true - })) : "{\n" + (this.elseBody.compile(o)) + "\n" + this.tab + "}"); + })) : "{\n" + (this.elseBody.compile(o, LVL_TOP)) + "\n" + this.tab + "}"); }; If.prototype.compileExpression = function(o) { var _ref2, code; - code = this.condition.compile(o, LVL_COND) + ' ? ' + this.bodyNode().compile(o, LVL_ASSIGN) + ' : ' + ((_ref2 = this.elseBodyNode()) != null ? _ref2.compile(o, LVL_ASSIGN) : undefined); + code = this.condition.compile(o, LVL_COND) + ' ? ' + this.bodyNode().compile(o, LVL_LIST) + ' : ' + ((_ref2 = this.elseBodyNode()) != null ? _ref2.compile(o, LVL_LIST) : undefined); return o.level >= LVL_COND ? "(" + code + ")" : code; }; If.prototype.unfoldSoak = function() { @@ -1801,10 +1769,9 @@ LVL_TOP = 0; LVL_PAREN = 1; LVL_LIST = 2; - LVL_ASSIGN = 3; - LVL_COND = 4; - LVL_OP = 5; - LVL_ACCESS = 6; + LVL_COND = 3; + LVL_OP = 4; + LVL_ACCESS = 5; TAB = ' '; TRAILING_WHITESPACE = /[ \t]+$/gm; IDENTIFIER = /^[$A-Za-z_][$\w]*$/; @@ -1817,4 +1784,7 @@ Scope.root.assign(ref, UTILITIES[name]); return ref; }; + multident = function(code, tab) { + return code.replace(/\n/g, '$&' + tab); + }; }).call(this); diff --git a/lib/rewriter.js b/lib/rewriter.js index 255f2319..86cecec1 100644 --- a/lib/rewriter.js +++ b/lib/rewriter.js @@ -30,7 +30,7 @@ tokens = this.tokens; i = 0; while (token = tokens[i]) { - (i += block.call(this, token, i, tokens)); + i += block.call(this, token, i, tokens); } return true; }; @@ -322,7 +322,7 @@ stack = []; debt = {}; for (key in INVERSES) { - (debt[key] = 0); + debt[key] = 0; } return this.scanTokens(function(token, i, tokens) { var _ref, inv, match, mtag, oppos, tag, val; diff --git a/lib/scope.js b/lib/scope.js index 8d8ee40b..3d1f67f3 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -101,7 +101,9 @@ index++; } this.add(temp, 'var'); - (_ref2 = last(this.garbage)) != null ? _ref2.push(temp) : undefined; + if ((_ref2 = last(this.garbage)) != null) { + _ref2.push(temp); + } return temp; }; Scope.prototype.assign = function(name, value) { @@ -123,7 +125,7 @@ }; Scope.prototype.declaredVariables = function() { var _i, _len, _ref2, _ref3, _result, v; - return (function() { + return ((function() { _ref2 = this.variables; _result = []; for (_i = 0, _len = _ref2.length; _i < _len; _i++) { @@ -133,7 +135,7 @@ } } return _result; - }).call(this).sort(); + }).call(this)).sort(); }; Scope.prototype.assignedVariables = function() { var _i, _len, _ref2, _result, v; diff --git a/src/nodes.coffee b/src/nodes.coffee index 8763dcd1..a14ce373 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -37,20 +37,15 @@ exports.Base = class Base # 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). - # - # If a Node is *topSensitive*, that means that it needs to compile differently - # depending on whether it's being used as part of a larger expression, or is a - # top-level statement within the function body. compile: (o, lvl) -> - o = if o then extend {}, o else {} - o.level = lvl if lvl - top = if @topSensitive then o.top else del o, 'top' - @tab = o.indent - if top or o.asStatement or this instanceof Comment or - @isPureStatement() or not @isStatement(o) - @compileNode o + o = if o then extend {}, o else {} + o.level = lvl if lvl? + node = @unfoldSoak(o) or this + node.tab = o.indent + if o.level is LVL_TOP or node.isPureStatement() or not node.isStatement(o) + node.compileNode o else - @compileClosure o + node.compileClosure o # Statements converted into expressions via closure-wrapping share a scope # object with their parent closure, to preserve the expected lexical scope. @@ -74,7 +69,7 @@ exports.Base = class Base # Compile to a source/variable pair suitable for looping. compileLoopReference: (o, name) -> - src = tmp = @compile o, LVL_ASSIGN + src = tmp = @compile o, LVL_LIST unless NUMBER.test(src) or IDENTIFIER.test(src) and o.scope.check(src, immediate: on) src = "#{ tmp = o.scope.freeVariable name } = #{src}" [src, tmp] @@ -147,7 +142,6 @@ exports.Base = class Base isPureStatement : NO isComplex : YES isChainable : NO - topSensitive : no unfoldSoak : NO # Is this node used to assign a certain variable? @@ -162,7 +156,7 @@ exports.Expressions = class Expressions extends Base children: ['expressions'] - isStatement: YES + isStatement: YES constructor: (nodes) -> super() @@ -170,12 +164,12 @@ exports.Expressions = class Expressions extends Base # Tack an expression on to the end of this expression list. push: (node) -> - @expressions.push(node) + @expressions.push node this # Add an expression at the beginning of this expression list. unshift: (node) -> - @expressions.unshift(node) + @expressions.unshift node this # If this Expressions consists of just a single node, unwrap it by pulling @@ -201,7 +195,8 @@ exports.Expressions = class Expressions extends Base if o.scope then super o, lvl else @compileRoot o compileNode: (o) -> - (@compileExpression node, merge o for node in @expressions).join '\n' + @tab = o.indent + (@compileExpression node, o for node in @expressions).join '\n' # If we happen to be the top-level **Expressions**, wrap everything in # a safety closure, unless requested not to. @@ -218,20 +213,23 @@ exports.Expressions = class Expressions extends Base # Compile the expressions body for the contents of a function, with # declarations of all inner variables pushed up to the top. compileWithDeclarations: (o) -> - code = @compileNode o - if o.scope.hasAssignments this - code = "#{@tab}var #{ o.scope.compiledAssignments().replace /\n/g, '$&' + @tab };\n#{code}" + code = @compileNode o + {scope} = o + if scope.hasAssignments this + code = "#{@tab}var #{ multident scope.compiledAssignments(), @tab };\n#{code}" if not o.globals and o.scope.hasDeclarations this - code = "#{@tab}var #{ o.scope.compiledDeclarations() };\n#{code}" + code = "#{@tab}var #{ scope.compiledDeclarations() };\n#{code}" code # Compiles a single expression within the expressions body. If we need to # return the result, and it's an expression, simply return it. If it's a # statement, ask the statement to do so. compileExpression: (node, o) -> - o.top = node.tags.front = on - @tab = o.indent - code = node.compile o + while node isnt node = node.unwrap() then + node = node.unfoldSoak(o) or node + node.tags.front = on + o.level = LVL_TOP + code = node.compile o if node.isStatement o then code else @tab + code + ';' # Wrap up the given nodes as an **Expressions**, unless it already happens @@ -247,24 +245,19 @@ exports.Expressions = class Expressions extends Base # `true`, `false`, `null`... exports.Literal = class Literal extends Base - constructor: (@value) -> - super() + constructor: (@value) -> super() - makeReturn: -> - if @isStatement() then this else super() + makeReturn: -> if @isStatement() then this else super() # Break and continue must be treated as pure statements -- they lose their # meaning when wrapped in a closure. - isStatement : -> @value in ['break', 'continue', 'debugger'] - isPureStatement: Literal::isStatement + isPureStatement: -> @value in ['break', 'continue', 'debugger'] isComplex: NO assigns: (name) -> name is @value - compileNode: -> - val = if @value.reserved then "\"#{@value}\"" else @value - if @isStatement() then @tab + val + ';' else val + compile: -> if @value.reserved then "\"#{@value}\"" else @value toString: -> ' "' + @value + '"' @@ -279,8 +272,7 @@ exports.Return = class Return extends Base isStatement : YES isPureStatement: YES - constructor: (@expression) -> - super() + constructor: (@expression) -> super() makeReturn: THIS @@ -289,11 +281,8 @@ exports.Return = class Return extends Base if expr and expr not instanceof Return then expr.compile o, lvl else super o, lvl compileNode: (o) -> - expr = '' - if @expression - o.asStatement = true if @expression.isStatement o - expr = ' ' + @expression.compile o, LVL_PAREN - @tab + 'return' + expr + ';' + o.level = LVL_PAREN + @tab + "return#{ if @expression then ' ' + @expression.compile o else '' };" #### Value @@ -311,7 +300,7 @@ exports.Value = class Value extends Base # Add a property access to the list. push: (prop) -> - @properties.push(prop) + @properties.push prop this hasProperties: -> @@ -346,7 +335,7 @@ exports.Value = class Value extends Base # Values are considered to be statements if their base is a statement. isStatement: (o) -> - @base.isStatement(o) and not @properties.length + not @properties.length and @base.isStatement o isSimpleNumber: -> @base instanceof Literal and SIMPLENUM.test @base.value @@ -369,21 +358,16 @@ exports.Value = class Value extends Base nref = new Index nref [base.push(name), new Value(bref or base.base, [nref or name])] - # Override compile to unwrap the value when possible. - compile: (o, lvl) -> - @base.tags.front = @tags.front - if not o.top or @properties.length then super o, lvl else @base.compile o, lvl - # We compile a value to JavaScript by compiling and joining each property. # Things get much more insteresting if the chain of properties has *soak* # operators `?.` interspersed. Then we have to take care not to accidentally # evaluate anything twice when building the soak chain. compileNode: (o) -> - return ifn.compile o if ifn = @unfoldSoak o + @base.tags.front = @tags.front props = @properties - code = @base.compile o, props.length and LVL_ACCESS + code = @base.compile o, if props.length then LVL_ACCESS else null code = "(#{code})" if props[0] instanceof Accessor and @isSimpleNumber() - (code += prop.compileNode o) for prop in props + (code += prop.compile o) for prop in props code # Unfold a soak into an `If`: `a?.b` -> `a.b if a?` @@ -410,15 +394,13 @@ exports.Value = class Value extends Base # at the same position. exports.Comment = class Comment extends Base - isStatement: YES + isPureStatement: YES - constructor: (@comment) -> - super() + constructor: (@comment) -> super() makeReturn: THIS - compileNode: (o) -> - @tab + '/*' + @comment.replace(/\n/g, '\n' + @tab) + '*/' + compileNode: (o) -> @tab + '/*' + multident(@comment, @tab) + '*/' #### Call @@ -488,7 +470,6 @@ exports.Call = class Call extends Base # Compile a vanilla function call. compileNode: (o) -> - return ifn.compile o if ifn = @unfoldSoak o @variable?.tags.front = @tags.front for arg in @args when arg instanceof Splat return @compileSplat o @@ -514,10 +495,10 @@ exports.Call = class Call extends Base base = Value.wrap @variable if (name = base.properties.pop()) and base.isComplex() ref = o.scope.freeVariable 'this' - fun = "(#{ref} = #{ base.compile o, LVL_ASSIGN })#{ name.compileNode o }" + fun = "(#{ref} = #{ base.compile o, LVL_LIST })#{ name.compile o }" else fun = ref = base.compile o, LVL_ACCESS - fun += name.compileNode o if name + fun += name.compile o if name return "#{fun}.apply(#{ref}, #{splatargs})" idt = @idt 1 """ @@ -537,11 +518,10 @@ exports.Extends = class Extends extends Base children: ['child', 'parent'] - constructor: (@child, @parent) -> - super() + constructor: (@child, @parent) -> super() # Hooks one constructor into another's prototype chain. - compileNode: (o) -> + compile: (o) -> new Call(new Value(new Literal utility 'extends'), [@child, @parent]).compile o #### Accessor @@ -557,7 +537,7 @@ exports.Accessor = class Accessor extends Base @proto = if tag is 'prototype' then '.prototype' else '' @soakNode = tag is 'soak' - compileNode: (o) -> + compile: (o) -> name = @name.compile o @proto + if IS_STRING.test name then "[#{name}]" else ".#{name}" @@ -570,10 +550,9 @@ exports.Index = class Index extends Base children: ['index'] - constructor: (@index) -> - super() + constructor: (@index) -> super() - compileNode: (o) -> + compile: (o) -> (if @proto then '.prototype' else '') + "[#{ @index.compile o, LVL_PAREN }]" isComplex: -> @index.isComplex() @@ -590,7 +569,6 @@ exports.ObjectLiteral = class ObjectLiteral extends Base @objects = @properties = props or [] compileNode: (o) -> - top = del o, 'top' o.indent = @idt 1 nonComments = prop for prop in @properties when prop not instanceof Comment lastNoncom = last nonComments @@ -660,7 +638,7 @@ exports.Class = class Class extends Base children: ['variable', 'parent', 'properties'] - isStatement: YES + isStatement: YES # Initialize a **Class** with its name, an optional superclass, and a # list of prototype property assignments. @@ -682,7 +660,6 @@ exports.Class = class Class extends Base variable = new Literal o.scope.freeVariable 'ctor' if variable.value is '__temp__' extension = @parent and new Extends variable, @parent props = new Expressions - o.top = true me = null className = variable.compile o constScope = null @@ -730,11 +707,12 @@ exports.Class = class Class extends Base constructor.className = className.match /[$\w]+$/ constructor.body.unshift new Literal "#{me} = this" if me - construct = @idt() + new Assign(variable, constructor).compile(merge o, sharedScope: constScope) + ';' - props = if !props.empty() then '\n' + props.compile(o) else '' - extension = if extension then '\n' + @idt() + extension.compile(o) + ';' else '' - returns = if @returns then '\n' + new Return(variable).compile(o) else '' - construct + extension + props + returns + o.sharedScope = constScope + construct = @tab + new Assign(variable, constructor).compile(o) + ';' + construct += '\n' + @tab + extension.compile(o) + ';' if extension + construct += '\n' + props.compile o if !props.empty() + construct += '\n' + new Return(variable).compile o if @returns + construct #### Assign @@ -749,10 +727,12 @@ exports.Assign = class Assign extends Base children: ['variable', 'value'] - topSensitive: yes + constructor: (@variable, @value, @context) -> super() - constructor: (@variable, @value, @context) -> - super() + assigns: (name) -> + @[if @context is 'object' then 'value' else 'variable'].assigns name + + unfoldSoak: (o) -> If.unfoldSoak o, this, 'variable' # Compile an assignment, delegating to `compilePatternMatch` or # `compileSplice` if appropriate. Keep track of the name of the base object @@ -760,31 +740,24 @@ exports.Assign = class Assign extends Base # has not been seen yet within the current scope, declare it. compileNode: (o) -> if isValue = @variable instanceof Value - return @compilePatternMatch(o) if @variable.isArray() or @variable.isObject() - if ifn = If.unfoldSoak o, this, 'variable' - delete o.top - return ifn.compile o - return @compileConditional o if @context in @CONDITIONAL - top = del o, 'top' - stmt = del o, 'asStatement' - name = @variable.compile o, LVL_ASSIGN + return @compilePatternMatch o if @variable.isArray() or @variable.isObject() + return @compileConditional o if @context in @CONDITIONAL + name = @variable.compile o, LVL_LIST if @value instanceof Code and match = @METHOD_DEF.exec name @value.name = match[2] @value.klass = match[1] - val = @value.compile o, LVL_ASSIGN + val = @value.compile o, LVL_LIST return "#{name}: #{val}" if @context is 'object' o.scope.find name unless isValue and (@variable.hasProperties() or @variable.namespaced) val = name + " #{ @context or '=' } " + val - return "#{@tab}#{val};" if stmt - if top or o.level <= LVL_ASSIGN then val else "(#{val})" + if o.level <= LVL_LIST then val else "(#{val})" # Brief implementation of recursive pattern matching, when assigning array or # object literals to a value. Peeks at their properties to assign inner names. # See the [ECMAScript Harmony Wiki](http://wiki.ecmascript.org/doku.php?id=harmony:destructuring) # for details. compilePatternMatch: (o) -> - top = del o, 'top' - otop = merge o, top: yes + top = o.level is LVL_TOP {value} = this {objects} = @variable.base return value.compile o unless olength = objects.length @@ -796,11 +769,12 @@ exports.Assign = class Assign extends Base else idx = if isObject if obj.tags.this then obj.properties[0].name else obj - else new Literal 0 + else + new Literal 0 accessClass = if IDENTIFIER.test idx.value then Accessor else Index (value = Value.wrap value).properties.push new accessClass idx - return new Assign(obj, value).compile otop - valVar = value.compile o + return new Assign(obj, value).compile o + valVar = value.compile o, LVL_LIST assigns = [] splat = false if not IDENTIFIER.test(valVar) or @variable.assigns(valVar) @@ -823,12 +797,13 @@ exports.Assign = class Assign extends Base val = new Literal obj.compileValue o, valVar, i, olength - i - 1 splat = true else - idx = new Literal(if splat then "#{valVar}.length - #{olength - idx}" else idx) if typeof idx isnt 'object' + if typeof idx isnt 'object' + idx = new Literal(if splat then "#{valVar}.length - #{ olength - idx }" else idx) val = new Value new Literal(valVar), [new accessClass idx] - assigns.push new Assign(obj, val).compile otop + assigns.push new Assign(obj, val).compile o, LVL_LIST assigns.push valVar unless top code = assigns.join ', ' - if top or o.level <= LVL_PAREN then code else "(#{code})" + if o.level < LVL_LIST then code else "(#{code})" # When compiling a conditional assignment, take care to ensure that the # operands are only evaluated once, even though we have to reference them @@ -837,9 +812,6 @@ exports.Assign = class Assign extends Base [left, rite] = @variable.cacheReference o return new Op(@context.slice(0, -1), left, new Assign(rite, @value)).compile o - assigns: (name) -> - @[if @context is 'object' then 'value' else 'variable'].assigns name - #### Code # A function definition. This is the only node that creates a new Scope. @@ -863,9 +835,7 @@ exports.Code = class Code extends Base # a closure. compileNode: (o) -> sharedScope = del o, 'sharedScope' - top = del o, 'top' - o.scope = sharedScope or new Scope(o.scope, @body, this) - o.top = true + o.scope = scope = sharedScope or new Scope o.scope, @body, this o.indent = @idt 1 empty = @body.expressions.length is 0 delete o.bare @@ -881,28 +851,34 @@ exports.Code = class Code extends Base else if param.attach {value} = param - [param, param.splat] = [new Literal(o.scope.freeVariable 'arg'), param.splat] + [param, param.splat] = [new Literal(scope.freeVariable 'arg'), param.splat] @body.unshift new Assign new Value(new Literal('this'), [new Accessor value]), param if param.splat splat = new Splat param.value splat.index = i splat.trailings = [] splat.arglength = @params.length - @body.unshift(splat) + @body.unshift splat else params.push param - o.scope.startLevel() - params = param.compile(o) for param in params + scope.startLevel() @body.makeReturn() unless empty or @noReturn - o.scope.parameter param for param in params + params = for param in params + scope.parameter param = param.compile o + param comm = if @comment then @comment.compile(o) + '\n' else '' o.indent = @idt 2 if @className - code = if @body.expressions.length then "\n#{ @body.compileWithDeclarations(o) }\n" else '' - open = if @className then "(function() {\n#{comm}#{@idt(1)}function #{@className}(" else "function(" - close = if @className then "#{code and @idt(1)}};\n#{@idt(1)}return #{@className};\n#{@tab}})()" else "#{code and @tab}}" - func = "#{open}#{ params.join(', ') }) {#{code}#{close}" - o.scope.endLevel() - return "#{utility 'bind'}(#{func}, #{@context})" if @bound + idt = @idt 1 + code = if @body.expressions.length then "\n#{ @body.compileWithDeclarations o }\n" else '' + if @className + open = "(function() {\n#{comm}#{idt}function #{@className}(" + close = "#{ code and idt }};\n#{idt}return #{@className};\n#{@tab}})()" + else + open = "function(" + close = "#{ code and @tab }}" + func = "#{open}#{ params.join ', ' }) {#{code}#{close}" + scope.endLevel() + return "#{ utility 'bind' }(#{func}, #{@context})" if @bound if @tags.front then "(#{func})" else func # Short-circuit `traverseChildren` method to prevent it from crossing scope boundaries @@ -922,13 +898,12 @@ exports.Param = class Param extends Base super() @value = new Literal @name - compileNode: (o) -> - @value.compile o, LVL_LIST + compile: (o) -> @value.compile o, LVL_LIST toString: -> - {name} = @ - name = '@' + name if @attach - name += '...' if @splat + {name} = this + name = '@' + name if @attach + name += '...' if @splat new Literal(name).toString() #### Splat @@ -945,7 +920,7 @@ exports.Splat = class Splat extends Base assigns: (name) -> @name.assigns name - compileNode: (o) -> + compile: (o) -> if @index? then @compileParam o else @name.compile o # Compiling a parameter splat means recovering the parameters that succeed @@ -1004,8 +979,7 @@ exports.While = class While extends Base children: ['condition', 'guard', 'body'] - topSensitive: yes - isStatement : YES + isStatement: YES constructor: (condition, opts) -> super() @@ -1024,22 +998,23 @@ exports.While = class While extends Base # *while* can be used as a part of a larger expression -- while loops may # return an array containing the computed result of each iteration. compileNode: (o) -> - top = del(o, 'top') and not @returns o.indent = @idt 1 - cond = @condition.compile o, LVL_PAREN - o.top = true set = '' - unless top - rvar = o.scope.freeVariable 'result' - set = "#{@tab}#{rvar} = [];\n" - @body = Push.wrap(rvar, @body) if @body - pre = "#{set}#{@tab}while (#{cond})" - @body = Expressions.wrap [new If @guard, @body] if @guard + {body} = this + if o.level > LVL_TOP or @returns + rvar = o.scope.freeVariable 'result' + set = "#{@tab}#{rvar} = [];\n" + body = Push.wrap rvar, body if body + body = Expressions.wrap [new If @guard, body] if @guard + code = set + @tab + """ + while (#{ @condition.compile o, LVL_PAREN }) { + #{ body.compile o, LVL_TOP } + #{@tab}} + """ if @returns - post = '\n' + new Return(new Literal rvar).compile merge o, indent: @idt() - else - post = '' - "#{pre} {\n#{ @body.compile(o) }\n#{@tab}}#{post}" + o.indent = @tab + code += '\n' + new Return(new Literal rvar).compile o + code #### Op @@ -1102,9 +1077,11 @@ exports.Op = class Op extends Base toString: (idt) -> super idt, @constructor.name + ' ' + @operator + unfoldSoak: (o) -> + @operator in @MUTATORS and If.unfoldSoak o, this, 'first' + compileNode: (o) -> if @isUnary() - return ifn.compile o if @operator in @MUTATORS and ifn = If.unfoldSoak o, this, 'first' return @compileUnary o return @compileChain o if @isChainable() and @first.unwrap().isChainable() return @compileExistence o if @operator is '?' @@ -1127,7 +1104,7 @@ exports.Op = class Op extends Base else fst = @first ref = fst.compile o - new Existence(fst).compile(o) + " ? #{ref} : #{ @second.compile o, LVL_ASSIGN }" + new Existence(fst).compile(o) + " ? #{ref} : #{ @second.compile o, LVL_LIST }" # Compile a unary **Op**. compileUnary: (o) -> @@ -1140,8 +1117,7 @@ exports.In = class In extends Base children: ['object', 'array'] - constructor: (@object, @array) -> - super() + constructor: (@object, @array) -> super() invert: -> @negated = not @negated @@ -1181,8 +1157,7 @@ exports.Try = class Try extends Base isStatement: YES - constructor: (@attempt, @error, @recovery, @ensure) -> - super() + constructor: (@attempt, @error, @recovery, @ensure) -> super() makeReturn: -> @attempt = @attempt .makeReturn() if @attempt @@ -1192,15 +1167,17 @@ exports.Try = class Try extends Base # Compilation is more or less as you would expect -- the *finally* clause # is optional, the *catch* is not. compileNode: (o) -> - o.indent = @idt 1 - o.top = true - attemptPart = @attempt.compile(o) - errorPart = if @error then " (#{ @error.compile o }) " else ' ' - catchPart = if @recovery - " catch#{errorPart}{\n#{ @recovery.compile o }\n#{@tab}}" - else unless @ensure or @recovery then ' catch (_e) {}' else '' - finallyPart = (@ensure or '') and ' finally {\n' + @ensure.compile(merge o) + "\n#{@tab}}" - "#{@tab}try {\n#{attemptPart}\n#{@tab}}#{catchPart}#{finallyPart}" + o.indent = @idt 1 + errorPart = if @error then " (#{ @error.compile o }) " else ' ' + catchPart = if @recovery + " catch#{errorPart}{\n#{ @recovery.compile o, LVL_TOP }\n#{@tab}}" + else unless @ensure or @recovery + ' catch (_e) {}' + """ + #{@tab}try { + #{ @attempt.compile o, LVL_TOP } + #{@tab}}#{ catchPart or '' } + """ + if @ensure then " finally {\n#{ @ensure.compile o, LVL_TOP }\n#{@tab}}" else '' #### Throw @@ -1211,14 +1188,12 @@ exports.Throw = class Throw extends Base isStatement: YES - constructor: (@expression) -> - super() + constructor: (@expression) -> super() # A **Throw** is already a return, of sorts... makeReturn: THIS - compileNode: (o) -> - "#{@tab}throw #{ @expression.compile o };" + compileNode: (o) -> @tab + "throw #{ @expression.compile o };" #### Existence @@ -1229,8 +1204,7 @@ exports.Existence = class Existence extends Base children: ['expression'] - constructor: (@expression) -> - super() + constructor: (@expression) -> super() compileNode: (o) -> code = @expression.compile o @@ -1251,29 +1225,20 @@ exports.Parens = class Parens extends Base children: ['expression'] - topSensitive: yes + constructor: (@expression) -> super() - constructor: (@expression) -> - super() - - isStatement: (o) -> - @expression.isStatement o - isComplex: -> - @expression.isComplex() - - makeReturn: -> - @expression.makeReturn() + unwrap : -> @expression + isComplex : -> @expression.isComplex() + makeReturn: -> @expression.makeReturn() compileNode: (o) -> - top = del o, 'top' expr = @expression if expr instanceof Value and expr.isAtomic() expr.tags.front = @tags.front return expr.compile o + bare = o.level < LVL_OP and (expr instanceof Op or expr instanceof Call) code = expr.compile o, LVL_PAREN - return code if o.level < LVL_OP and (expr instanceof Op or expr instanceof Call) - return (if top then @tab + code + ';' else code) if expr.isStatement o - "(#{code})" + if bare then code else "(#{code})" #### For @@ -1288,8 +1253,7 @@ exports.For = class For extends Base children: ['body', 'source', 'guard', 'step', 'from', 'to'] - topSensitive: yes - isStatement : YES + isStatement: YES constructor: (@body, head) -> if head.index instanceof Value @@ -1316,7 +1280,6 @@ exports.For = class For extends Base # some cannot. compileNode: (o) -> {scope} = o - top = del(o, 'top') and not @returns name = not @pattern and @name?.compile o index = @index?.compile o ivar = if not index then scope.freeVariable 'i' else index @@ -1338,9 +1301,9 @@ exports.For = class For extends Base if name [sourcePart, svar] = @source.compileLoopReference o, 'ref' else - sourcePart = svar = @source.compile o + sourcePart = svar = @source.compile o, LVL_PAREN namePart = if @pattern - new Assign(@name, new Literal "#{svar}[#{ivar}]").compile merge o, top: on + new Assign(@name, new Literal "#{svar}[#{ivar}]").compile o, LVL_TOP else if name "#{name} = #{svar}[#{ivar}]" unless @object @@ -1351,28 +1314,29 @@ exports.For = class For extends Base lvar = scope.freeVariable 'len' vars = "#{ivar} = 0, #{lvar} = #{svar}.length" cond = "#{ivar} < #{lvar}" - defPart = '' + defPart = '' if @object forPart = "#{ivar} in #{sourcePart}" guardPart = not @raw and "#{idt}if (!#{ utility 'hasProp' }.call(#{svar}, #{ivar})) continue;\n" else - vars += ", #{step}" if step isnt pvar + vars += ", #{step}" if step isnt pvar defPart = "#{@tab}#{sourcePart};\n" if svar isnt sourcePart - forPart = "#{vars}; #{cond}; " + switch +pvar - when 1 then ivar + '++' - when -1 then ivar + '--' - else ivar + if pvar < 0 then ' -= ' + pvar.slice 1 else ' += ' + pvar - unless top + forPart = "#{vars}; #{cond}; " + ivar + switch +pvar + when 1 then '++' + when -1 then '--' + else (if pvar < 0 then ' -= ' + pvar.slice 1 else ' += ' + pvar) + if o.level > LVL_TOP or @returns rvar = scope.freeVariable 'result' defPart += "#{@tab}#{rvar} = [];\n" resultRet = @compileReturnValue rvar, o body = Push.wrap rvar, body - body = Expressions.wrap [new If @guard, body] if @guard - varPart = "#{idt}#{namePart};\n" if namePart + body = Expressions.wrap [new If @guard, body] if @guard + varPart = "#{idt}#{namePart};\n" if namePart + o.indent = idt """ - #{ defPart or '' }#{@tab}for (#{forPart}) { - #{ guardPart or '' }#{varPart}#{ body.compile merge o, indent: idt, top: on } + #{ defPart or '' }#{@tab}for (#{forPart}) { + #{ guardPart or '' }#{varPart}#{ body.compile o, LVL_TOP } #{@tab}}#{ resultRet or '' } """ @@ -1385,29 +1349,28 @@ exports.Switch = class Switch extends Base isStatement: YES - constructor: (@subject, @cases, @otherwise) -> - super() + constructor: (@subject, @cases, @otherwise) -> super() makeReturn: -> pair[1].makeReturn() for pair in @cases - @otherwise.makeReturn() if @otherwise + @otherwise?.makeReturn() this compileNode: (o) -> - idt1 = @idt 1 - idt2 = o.indent = @idt 2 - o.top = yes - code = "#{ @tab }switch (#{ @subject?.compile(o) or true }) {" - for [conditions, block] in @cases - for condition in flatten [conditions] - condition = condition.invert().invert() unless @subject - code += "\n#{ idt1 }case #{ condition.compile o }:" - code += "\n#{ block.compile o }" - code += "\n#{ idt2 }break;" unless last(block.expressions) instanceof Return - if @otherwise - code += "\n#{ idt1 }default:\n#{ @otherwise.compile o }" - code += "\n#{ @tab }}" - code + idt1 = @idt 1 + idt2 = o.indent = @idt 2 + code = @tab + "switch (#{ @subject?.compile(o, LVL_PAREN) or true }) {\n" + for [conditions, block], i in @cases + for cond in flatten [conditions] + cond = cond.invert().invert() unless @subject + code += idt1 + "case #{ cond.compile o, LVL_PAREN }:\n" + code += block.compile(o, LVL_TOP) + '\n' + break if i is @cases.length - 1 and not @otherwise + for expr in block.expressions by -1 when expr not instanceof Comment + code += idt2 + 'break;\n' unless expr instanceof Return + break + code += idt1 + "default:\n#{ @otherwise.compile o, LVL_TOP }\n" if @otherwise + code + @tab + '}' #### If @@ -1420,8 +1383,6 @@ exports.If = class If extends Base children: ['condition', 'body', 'elseBody'] - topSensitive: yes - constructor: (condition, @body, tags) -> @tags = tags or= {} @condition = if tags.invert then condition.invert() else condition @@ -1444,7 +1405,7 @@ exports.If = class If extends Base # The **If** only compiles into a statement if either of its bodies needs # to be a statement. Otherwise a conditional operator is safe. isStatement: (o) -> - @statement or= o?.top or @bodyNode().isStatement(o) or @elseBodyNode()?.isStatement(o) + o?.level is LVL_TOP or @bodyNode().isStatement(o) or @elseBodyNode()?.isStatement(o) compileNode: (o) -> if @isStatement o then @compileStatement o else @compileExpression o @@ -1463,24 +1424,23 @@ exports.If = class If extends Base # Compile the **If** as a regular *if-else* statement. Flattened chains # force inner *else* bodies into statement form. compileStatement: (o) -> - top = del o, 'top' child = del o, 'chainChild' - condO = merge o + cond = @condition.compile o, LVL_PAREN o.indent = @idt 1 - o.top = true - ifPart = "if (#{ @condition.compile condO, LVL_PAREN }) {\n#{ @body.compile o }\n#{@tab}}" + body = @ensureExpressions(@body).compile o + ifPart = "if (#{cond}) {\n#{body}\n#{@tab}}" ifPart = @tab + ifPart unless child return ifPart unless @elseBody ifPart + ' else ' + if @isChain @elseBodyNode().compile merge o, indent: @tab, chainChild: true else - "{\n#{ @elseBody.compile o }\n#{@tab}}" + "{\n#{ @elseBody.compile o, LVL_TOP }\n#{@tab}}" # Compile the If as a conditional operator. compileExpression: (o) -> - code = @condition .compile(o, LVL_COND) + ' ? ' + - @bodyNode().compile(o, LVL_ASSIGN) + ' : ' + - @elseBodyNode()?.compile o, LVL_ASSIGN + code = @condition .compile(o, LVL_COND) + ' ? ' + + @bodyNode().compile(o, LVL_LIST) + ' : ' + + @elseBodyNode()?.compile o, LVL_LIST if o.level >= LVL_COND then "(#{code})" else code unfoldSoak: -> @soakNode and this @@ -1543,7 +1503,7 @@ UTILITIES = # Correctly set up a prototype chain for inheritance, including a reference # to the superclass for `super()` calls. See: # [goog.inherits](http://closure-library.googlecode.com/svn/docs/closureGoogBase.js.source.html#line1206). - extends: ''' + extends: ''' function(child, parent) { function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; @@ -1570,16 +1530,15 @@ UTILITIES = # Shortcuts to speed up the lookup time for native functions. hasProp: 'Object.prototype.hasOwnProperty' - slice: 'Array.prototype.slice' + slice : 'Array.prototype.slice' # Levels indicates a node's position in the AST. LVL_TOP = 0 # ...; LVL_PAREN = 1 # (...) LVL_LIST = 2 # [...] -LVL_ASSIGN = 3 # x = ... -LVL_COND = 4 # ... ? a : b -LVL_OP = 5 # +... -LVL_ACCESS = 6 # ...[0] +LVL_COND = 3 # ... ? x : y +LVL_OP = 4 # !... +LVL_ACCESS = 5 # ...[0] # Tabs are two spaces for pretty printing. TAB = ' ' @@ -1603,3 +1562,5 @@ utility = (name) -> ref = "__#{name}" Scope.root.assign ref, UTILITIES[name] ref + +multident = (code, tab) -> code.replace /\n/g, '$&' + tab