diff --git a/lib/nodes.js b/lib/nodes.js index da915b2e..c473248a 100644 --- a/lib/nodes.js +++ b/lib/nodes.js @@ -1,7 +1,7 @@ (function() { var AccessorNode, ArrayNode, AssignNode, BaseNode, CallNode, ClassNode, ClosureNode, CodeNode, CommentNode, ExistenceNode, Expressions, ExtendsNode, ForNode, IDENTIFIER, IS_STRING, IfNode, InNode, IndexNode, LiteralNode, NO, NUMBER, ObjectNode, OpNode, ParamNode, ParentheticalNode, PushNode, RangeNode, ReturnNode, SIMPLENUM, Scope, SliceNode, SplatNode, SwitchNode, TAB, THIS, TRAILING_WHITESPACE, ThrowNode, TryNode, UTILITIES, ValueNode, WhileNode, YES, _ref, compact, del, ends, flatten, include, indexOf, last, literal, merge, starts, utility; var __extends = function(child, parent) { - var ctor = function(){}; + var ctor = function() {}; ctor.prototype = parent.prototype; child.prototype = new ctor(); child.prototype.constructor = child; @@ -83,13 +83,13 @@ return contains; }; BaseNode.prototype.containsType = function(type) { - return this instanceof type || this.contains(function(n) { - return n instanceof type; + return this instanceof type || this.contains(function(node) { + return node instanceof type; }); }; BaseNode.prototype.containsPureStatement = function() { - return this.isPureStatement() || this.contains(function(n) { - return (typeof n.isPureStatement !== "function" ? undefined : n.isPureStatement()); + return this.isPureStatement() || this.contains(function(node) { + return node.isPureStatement(); }); }; BaseNode.prototype.traverse = function(block) { @@ -216,7 +216,7 @@ var code; code = this.compileNode(o); if (o.scope.hasAssignments(this)) { - code = ("" + (this.tab) + "var " + (o.scope.compiledAssignments()) + ";\n" + code); + code = ("" + (this.tab) + "var " + (o.scope.compiledAssignments().replace(/\n/g, '$&' + this.tab)) + ";\n" + code); } if (!o.globals && o.scope.hasDeclarations(this)) { code = ("" + (this.tab) + "var " + (o.scope.compiledDeclarations()) + ";\n" + code); @@ -561,7 +561,7 @@ return "" + (this.superReference(o)) + ".call(this" + (args.length ? ', ' : '') + args + ")"; }; CallNode.prototype.compileSplat = function(o) { - var _i, _len, _ref2, a, arg, argvar, b, base, c, call, fun, idt, name, ref, splatargs; + var _i, _len, _ref2, arg, argvar, base, call, ctor, fun, idt, name, ref, result, splatargs; splatargs = this.compileSplatArguments(o); if (this.isSuper) { return ("" + (this.superReference(o)) + ".apply(this, " + splatargs + ")"); @@ -582,8 +582,8 @@ return ("" + fun + ".apply(" + ref + ", " + splatargs + ")"); } call = 'call(this)'; - argvar = function(n) { - return n instanceof LiteralNode && n.value === 'arguments'; + argvar = function(node) { + return node instanceof LiteralNode && node.value === 'arguments'; }; _ref2 = this.args; for (_i = 0, _len = _ref2.length; _i < _len; _i++) { @@ -593,10 +593,10 @@ break; } } - a = o.scope.freeVariable('ctor'); - b = o.scope.freeVariable('ref'); - c = o.scope.freeVariable('result'); - return "(function() {\n" + (idt = this.idt(1)) + "var ctor = function() {};\n" + idt + (utility('extends')) + "(ctor, " + a + " = " + (this.variable.compile(o)) + ");\n" + idt + "return typeof (" + c + " = " + a + ".apply(" + b + " = new ctor, " + splatargs + ")) === \"object\" ? " + c + " : " + b + ";\n" + (this.tab) + "})." + call; + ctor = o.scope.freeVariable('ctor'); + ref = o.scope.freeVariable('ref'); + result = o.scope.freeVariable('result'); + return "(function() {\n" + (idt = this.idt(1)) + "var ctor = function() {};\n" + idt + (utility('extends')) + "(ctor, " + ctor + " = " + (this.variable.compile(o)) + ");\n" + idt + "return typeof (" + result + " = " + ctor + ".apply(" + ref + " = new ctor, " + splatargs + ")) === \"object\" ? " + result + " : " + ref + ";\n" + (this.tab) + "})." + call; }; return CallNode; })(); @@ -867,11 +867,11 @@ })(); exports.ClassNode = (function() { ClassNode = (function() { - return function ClassNode(_arg, _arg2, _arg3) { - this.properties = _arg3; - this.parent = _arg2; - this.variable = _arg; + return function ClassNode(variable, _arg, _arg2) { + this.properties = _arg2; + this.parent = _arg; ClassNode.__super__.constructor.call(this); + this.variable = variable === '__temp__' ? literal(variable) : variable; this.properties || (this.properties = []); this.returns = false; return this; @@ -885,15 +885,16 @@ return this; }; ClassNode.prototype.compileNode = function(o) { - var _i, _len, _ref2, _ref3, _ref4, access, applied, apply, className, constScope, construct, constructor, extension, func, me, pname, prop, props, pvar, ref, returns, val; - if (this.variable === '__temp__') { - this.variable = literal(o.scope.freeVariable('ctor')); + var _i, _len, _ref2, _ref3, _ref4, access, applied, apply, className, constScope, construct, constructor, extension, func, me, pname, prop, props, pvar, ref, returns, val, variable; + variable = this.variable; + if (variable.value === '__temp__') { + variable = literal(o.scope.freeVariable('ctor')); } - extension = this.parent && new ExtendsNode(this.variable, this.parent); + extension = this.parent && new ExtendsNode(variable, this.parent); props = new Expressions; o.top = true; me = null; - className = this.variable.compile(o); + className = variable.compile(o); constScope = null; if (this.parent) { applied = new ValueNode(this.parent, [new AccessorNode(literal('apply'))]); @@ -919,8 +920,8 @@ } func.name = className; func.body.push(new ReturnNode(literal('this'))); - this.variable = new ValueNode(this.variable); - this.variable.namespaced = include(func.name, '.'); + variable = new ValueNode(variable); + variable.namespaced = include(func.name, '.'); constructor = func; continue; } @@ -940,7 +941,7 @@ } if (pvar) { access = prop.context === 'this' ? pvar.base.properties[0] : new AccessorNode(pvar, 'prototype'); - val = new ValueNode(this.variable, [access]); + val = new ValueNode(variable, [access]); prop = new AssignNode(val, func); } props.push(prop); @@ -949,12 +950,12 @@ if (me) { constructor.body.unshift(literal("" + me + " = this")); } - construct = this.idt() + (new AssignNode(this.variable, constructor)).compile(merge(o, { + construct = this.idt() + new AssignNode(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 ReturnNode(this.variable).compile(o) : ''; + returns = this.returns ? '\n' + new ReturnNode(variable).compile(o) : ''; return construct + extension + props + returns; }; return ClassNode; @@ -1642,8 +1643,8 @@ topLevel = del(o, 'top') && !this.returns; range = this.source instanceof ValueNode && this.source.base instanceof RangeNode && !this.source.properties.length; source = range ? this.source.base : this.source; - codeInBody = this.body.contains(function(n) { - return n instanceof CodeNode; + codeInBody = this.body.contains(function(node) { + return node instanceof CodeNode; }); scope = o.scope; name = this.name && this.name.compile(o); @@ -1851,7 +1852,7 @@ })().join(' || '); }; IfNode.prototype.compileNode = function(o) { - return this.isStatement(o) ? this.compileStatement(o) : this.compileTernary(o); + return this.isStatement(o) ? this.compileStatement(o) : this.compileExpression(o); }; IfNode.prototype.makeReturn = function() { if (this.isStatement()) { @@ -1885,7 +1886,7 @@ })) : (" else {\n" + (this.elseBody.compile(o)) + "\n" + (this.tab) + "}"); return "" + ifPart + elsePart; }; - IfNode.prototype.compileTernary = function(o) { + IfNode.prototype.compileExpression = function(o) { var code, elsePart, ifPart; this.bodyNode().tags.operation = (this.condition.tags.operation = true); if (this.elseBody) { @@ -1898,31 +1899,23 @@ }; return IfNode; })(); - PushNode = (exports.PushNode = { - wrap: function(array, expressions) { - var expr; - expr = expressions.unwrap(); - if (expr.isPureStatement() || expr.containsPureStatement()) { + PushNode = { + wrap: function(name, expressions) { + if (expressions.empty() || expressions.containsPureStatement()) { return expressions; } - return Expressions.wrap([new CallNode(new ValueNode(literal(array), [new AccessorNode(literal('push'))]), [expr])]); + return Expressions.wrap([new CallNode(new ValueNode(literal(name), [new AccessorNode(literal('push'))]), [expressions.unwrap()])]); } - }); - ClosureNode = (exports.ClosureNode = { + }; + ClosureNode = { wrap: function(expressions, statement) { - var args, call, func, mentionsArgs, mentionsThis, meth; + var args, call, func, mentionsArgs, meth; if (expressions.containsPureStatement()) { return expressions; } func = new ParentheticalNode(new CodeNode([], Expressions.wrap([expressions]))); args = []; - mentionsArgs = expressions.contains(function(n) { - return n instanceof LiteralNode && (n.value === 'arguments'); - }); - mentionsThis = expressions.contains(function(n) { - return (n instanceof LiteralNode && (n.value === 'this')) || (n instanceof CodeNode && n.bound); - }); - if (mentionsArgs || mentionsThis) { + if ((mentionsArgs = expressions.contains(this.literalArgs)) || (expressions.contains(this.literalThis))) { meth = literal(mentionsArgs ? 'apply' : 'call'); args = [literal('this')]; if (mentionsArgs) { @@ -1932,11 +1925,17 @@ } call = new CallNode(func, args); return statement ? Expressions.wrap([call]) : call; + }, + literalArgs: function(node) { + return node instanceof LiteralNode && node.value === 'arguments'; + }, + literalThis: function(node) { + return node instanceof LiteralNode && node.value === 'this' || node instanceof CodeNode && node.bound; } - }); + }; UTILITIES = { - "extends": "function(child, parent) {\n var ctor = function(){};\n ctor.prototype = parent.prototype;\n child.prototype = new ctor();\n child.prototype.constructor = child;\n if (typeof parent.extended === \"function\") parent.extended(child);\n child.__super__ = parent.prototype;\n }", - bind: "function(func, context) {\n return function(){ return func.apply(context, arguments); };\n }", + "extends": 'function(child, parent) {\n var ctor = function() {};\n ctor.prototype = parent.prototype;\n child.prototype = new ctor();\n child.prototype.constructor = child;\n if (typeof parent.extended === "function") parent.extended(child);\n child.__super__ = parent.prototype;\n}', + bind: 'function(func, context) {\n return function() { return func.apply(context, arguments); };\n}', hasProp: 'Object.prototype.hasOwnProperty', slice: 'Array.prototype.slice' }; diff --git a/src/nodes.coffee b/src/nodes.coffee index a15c4a76..b8cd0e55 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -96,12 +96,12 @@ exports.BaseNode = class BaseNode # Is this node of a certain type, or does it contain the type? containsType: (type) -> - this instanceof type or @contains (n) -> n instanceof type + this instanceof type or @contains (node) -> node instanceof type # Convenience for the most common use of contains. Does the node contain # a pure statement? containsPureStatement: -> - @isPureStatement() or @contains (n) -> n.isPureStatement?() + @isPureStatement() or @contains (node) -> node.isPureStatement() # Perform an in-order traversal of the AST. Crosses scope boundaries. traverse: (block) -> @traverseChildren true, block @@ -207,8 +207,14 @@ exports.Expressions = class Expressions extends BaseNode # declarations of all inner variables pushed up to the top. compileWithDeclarations: (o) -> code = @compileNode(o) - code = "#{@tab}var #{o.scope.compiledAssignments()};\n#{code}" if o.scope.hasAssignments(this) - code = "#{@tab}var #{o.scope.compiledDeclarations()};\n#{code}" if not o.globals and o.scope.hasDeclarations(this) + code = """ + #{@tab}var #{ o.scope.compiledAssignments().replace /\n/g, '$&' + @tab }; + #{code} + """ if o.scope.hasAssignments this + code = """ + #{@tab}var #{o.scope.compiledDeclarations()}; + #{code} + """ if not o.globals and o.scope.hasDeclarations this code # Compiles a single expression within the expressions body. If we need to @@ -520,18 +526,18 @@ exports.CallNode = class CallNode extends BaseNode fun += name.compile o if name return "#{fun}.apply(#{ref}, #{splatargs})" call = 'call(this)' - argvar = (n) -> n instanceof LiteralNode and n.value is 'arguments' + argvar = (node) -> node instanceof LiteralNode and node.value is 'arguments' for arg in @args when arg.contains argvar call = 'apply(this, arguments)' break - a = o.scope.freeVariable 'ctor' - b = o.scope.freeVariable 'ref' - c = o.scope.freeVariable 'result' + ctor = o.scope.freeVariable 'ctor' + ref = o.scope.freeVariable 'ref' + result = o.scope.freeVariable 'result' """ (function() { #{idt = @idt 1}var ctor = function() {}; - #{idt}#{utility 'extends'}(ctor, #{a} = #{ @variable.compile o }); - #{idt}return typeof (#{c} = #{a}.apply(#{b} = new ctor, #{splatargs})) === "object" ? #{c} : #{b}; + #{idt}#{utility 'extends'}(ctor, #{ctor} = #{ @variable.compile o }); + #{idt}return typeof (#{result} = #{ctor}.apply(#{ref} = new ctor, #{splatargs})) === "object" ? #{result} : #{ref}; #{@tab}}).#{call} """ @@ -754,8 +760,9 @@ exports.ClassNode = class ClassNode extends BaseNode # Initialize a **ClassNode** with its name, an optional superclass, and a # list of prototype property assignments. - constructor: (@variable, @parent, @properties) -> + constructor: (variable, @parent, @properties) -> super() + @variable = if variable is '__temp__' then literal variable else variable @properties or= [] @returns = false @@ -767,12 +774,13 @@ exports.ClassNode = class ClassNode extends BaseNode # equivalent syntax tree and compile that, in pieces. You can see the # constructor, property assignments, and inheritance getting built out below. compileNode: (o) -> - @variable = literal o.scope.freeVariable 'ctor' if @variable is '__temp__' - extension = @parent and new ExtendsNode(@variable, @parent) + {variable} = this + variable = literal o.scope.freeVariable 'ctor' if variable.value is '__temp__' + extension = @parent and new ExtendsNode variable, @parent props = new Expressions o.top = true me = null - className = @variable.compile o + className = variable.compile o constScope = null if @parent @@ -794,8 +802,8 @@ exports.ClassNode = class ClassNode extends BaseNode throw new Error "cannot define a constructor as a bound function." if func.bound func.name = className func.body.push new ReturnNode literal 'this' - @variable = new ValueNode @variable - @variable.namespaced = include func.name, '.' + variable = new ValueNode variable + variable.namespaced = include func.name, '.' constructor = func continue if func instanceof CodeNode and func.bound @@ -810,16 +818,16 @@ exports.ClassNode = class ClassNode extends BaseNode constructor.body.unshift literal "this.#{pname} = function(){ return #{className}.prototype.#{pname}.apply(#{me}, arguments); }" if pvar access = if prop.context is 'this' then pvar.base.properties[0] else new AccessorNode(pvar, 'prototype') - val = new ValueNode(@variable, [access]) + val = new ValueNode variable, [access] prop = new AssignNode(val, func) props.push prop constructor.className = className.match /[\w\d\$_]+$/ constructor.body.unshift literal "#{me} = this" if me - construct = @idt() + (new AssignNode(@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 ReturnNode(@variable).compile(o) else '' + construct = @idt() + new AssignNode(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 ReturnNode(variable).compile(o) else '' construct + extension + props + returns #### AssignNode @@ -1404,7 +1412,7 @@ exports.ForNode = class ForNode extends BaseNode topLevel = del(o, 'top') and not @returns range = @source instanceof ValueNode and @source.base instanceof RangeNode and not @source.properties.length source = if range then @source.base else @source - codeInBody = @body.contains (n) -> n instanceof CodeNode + codeInBody = @body.contains (node) -> node instanceof CodeNode scope = o.scope name = @name and @name.compile o index = @index and @index.compile o @@ -1501,7 +1509,7 @@ exports.SwitchNode = class SwitchNode extends BaseNode # *If/else* statements. Acts as an expression by pushing down requested returns # to the last line of each clause. # -# Single-expression **IfNodes** are compiled into ternary operators if possible, +# Single-expression **IfNodes** are compiled into conditional operators if possible, # because ternaries are already proper expressions, and don't need conversion. exports.IfNode = class IfNode extends BaseNode @@ -1532,7 +1540,7 @@ exports.IfNode = class IfNode extends BaseNode this # The **IfNode** only compiles into a statement if either of its bodies needs - # to be a statement. Otherwise a ternary is safe. + # to be a statement. Otherwise a conditional operator is safe. isStatement: (o) -> @statement or= !!((o and o.top) or @bodyNode().isStatement(o) or (@elseBody and @elseBodyNode().isStatement(o))) @@ -1542,7 +1550,7 @@ exports.IfNode = class IfNode extends BaseNode (cond.compile(o) for cond in conditions).join(' || ') compileNode: (o) -> - if @isStatement(o) then @compileStatement(o) else @compileTernary(o) + if @isStatement o then @compileStatement o else @compileExpression o makeReturn: -> if @isStatement() @@ -1574,8 +1582,8 @@ exports.IfNode = class IfNode extends BaseNode " else {\n#{ @elseBody.compile(o) }\n#{@tab}}" "#{ifPart}#{elsePart}" - # Compile the IfNode as a ternary operator. - compileTernary: (o) -> + # Compile the IfNode as a conditional operator. + compileExpression: (o) -> @bodyNode().tags.operation = @condition.tags.operation = yes @elseBodyNode().tags.operation = yes if @elseBody ifPart = @condition.compile(o) + ' ? ' + @bodyNode().compile(o) @@ -1585,79 +1593,76 @@ exports.IfNode = class IfNode extends BaseNode # Faux-Nodes # ---------- +# Faux-nodes are never created by the grammar, but are used during code +# generation to generate other combinations of nodes. #### PushNode -# Faux-nodes are never created by the grammar, but are used during code -# generation to generate other combinations of nodes. The **PushNode** creates -# the tree for `array.push(value)`, which is helpful for recording the result -# arrays from comprehensions. -PushNode = exports.PushNode = - wrap: (array, expressions) -> - expr = expressions.unwrap() - return expressions if expr.isPureStatement() or expr.containsPureStatement() - Expressions.wrap([new CallNode( - new ValueNode(literal(array), [new AccessorNode(literal('push'))]), [expr] - )]) +# The **PushNode** creates the tree for `array.push(value)`, +# which is helpful for recording the result arrays from comprehensions. +PushNode = + wrap: (name, expressions) -> + return expressions if expressions.empty() or expressions.containsPureStatement() + Expressions.wrap [new CallNode( + new ValueNode literal(name), [new AccessorNode literal 'push'] + [expressions.unwrap()] + )] #### ClosureNode # A faux-node used to wrap an expressions body in a closure. -ClosureNode = exports.ClosureNode = +ClosureNode = # Wrap the expressions body, unless it contains a pure statement, # in which case, no dice. If the body mentions `this` or `arguments`, # then make sure that the closure wrapper preserves the original values. wrap: (expressions, statement) -> return expressions if expressions.containsPureStatement() - func = new ParentheticalNode(new CodeNode([], Expressions.wrap([expressions]))) + func = new ParentheticalNode new CodeNode [], Expressions.wrap [expressions] args = [] - mentionsArgs = expressions.contains (n) -> - n instanceof LiteralNode and (n.value is 'arguments') - mentionsThis = expressions.contains (n) -> - (n instanceof LiteralNode and (n.value is 'this')) or - (n instanceof CodeNode and n.bound) - if mentionsArgs or mentionsThis - meth = literal(if mentionsArgs then 'apply' else 'call') - args = [literal('this')] + if (mentionsArgs = expressions.contains @literalArgs) or + ( expressions.contains @literalThis) + meth = literal if mentionsArgs then 'apply' else 'call' + args = [literal 'this'] args.push literal 'arguments' if mentionsArgs - func = new ValueNode func, [new AccessorNode(meth)] - call = new CallNode(func, args) - if statement then Expressions.wrap([call]) else call + func = new ValueNode func, [new AccessorNode meth] + call = new CallNode func, args + if statement then Expressions.wrap [call] else call -# Utility Functions -# ----------------- + literalArgs: (node) -> node instanceof LiteralNode and node.value is 'arguments' + literalThis: (node) -> node instanceof LiteralNode and node.value is 'this' or + node instanceof CodeNode and node.bound + +# Constants +# --------- 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: """ - function(child, parent) { - var ctor = function(){}; - ctor.prototype = parent.prototype; - child.prototype = new ctor(); - child.prototype.constructor = child; - if (typeof parent.extended === "function") parent.extended(child); - child.__super__ = parent.prototype; - } - """ + extends: ''' + function(child, parent) { + var ctor = function() {}; + ctor.prototype = parent.prototype; + child.prototype = new ctor(); + child.prototype.constructor = child; + if (typeof parent.extended === "function") parent.extended(child); + child.__super__ = parent.prototype; + } + ''' # Create a function bound to the current value of "this". - bind: """ - function(func, context) { - return function(){ return func.apply(context, arguments); }; - } - """ + bind: ''' + function(func, context) { + return function() { return func.apply(context, arguments); }; + } + ''' # Shortcuts to speed up the lookup time for native functions. hasProp: 'Object.prototype.hasOwnProperty' slice: 'Array.prototype.slice' -# Constants -# --------- - # Tabs are two spaces for pretty printing. TAB = ' ' @@ -1675,9 +1680,8 @@ IS_STRING = /^['"]/ # Utility Functions # ----------------- -# Handy helper for a generating LiteralNode. -literal = (name) -> - new LiteralNode(name) +# Handy helper for generating a LiteralNode. +literal = (name) -> new LiteralNode name # Helper for ensuring that utility functions are assigned at the top level. utility = (name) ->