diff --git a/lib/coffee_script/nodes.js b/lib/coffee_script/nodes.js index 04e3057e..afbcb639 100644 --- a/lib/coffee_script/nodes.js +++ b/lib/coffee_script/nodes.js @@ -1,5 +1,5 @@ (function(){ - var Expressions, Node, TAB, TRAILING_WHITESPACE, __a, compact, del, dup, flatten, statement; + var Expressions, LiteralNode, Node, TAB, TRAILING_WHITESPACE, compact, del, dup, flatten, inherit, merge, statement; var __hasProp = Object.prototype.hasOwnProperty; // The abstract base class for all CoffeeScript nodes. // All nodes are implement a "compile_node" method, which performs the @@ -247,6 +247,21 @@ return output; } }; + // Merge objects. + merge = function merge(src, dest) { + var __a, __b, key, val; + dest[key] = (function() { + __a = []; __b = src; + for (key in __b) { + val = __b[key]; + if (__hasProp.call(__b, key)) { + __a.push(val); + } + } + return __a; + }).call(this); + return dest; + }; // Delete a key from an object, returning the value. del = function del(obj, key) { var val; @@ -254,6 +269,28 @@ delete obj[key]; return val; }; + // Quickie inheritance convenience wrapper to reduce typing. + inherit = function inherit(parent, props) { + var __a, __b, __c, klass, name, prop; + klass = props.constructor; + delete props.constructor; + __a = function(){}; + __a.prototype = parent.prototype; + klass.__superClass__ = parent.prototype; + klass.prototype = new __a(); + klass.prototype.constructor = klass; + klass.prototype[name] = (function() { + __b = []; __c = props; + for (name in __c) { + prop = __c[name]; + if (__hasProp.call(__c, name)) { + __b.push(prop); + } + } + return __b; + }).call(this); + return klass; + }; // # Provide a quick implementation of a children method. // children: (klass, attrs...) -> // klass::children: -> @@ -261,10 +298,10 @@ // compact flatten nodes // Mark a node as a statement, or a statement only. statement = function statement(klass, only) { - klass.prototype.statement = function statement() { + klass.prototype.is_statement = function is_statement() { return true; }; - return klass.prototype.statement_only = function statement_only() { + return klass.prototype.is_statement_only = function is_statement_only() { if (only) { return true; } @@ -288,8 +325,8 @@ }); this.indent = o.indent; top = this.top_sensitive() ? o.top : del(obj('top')); - closure = this.statement() && !this.statement_only() && !top && !o.returns && !this instanceof CommentNode && !this.contains(function(node) { - return node.statement_only(); + closure = this.is_statement() && !this.is_statement_only() && !top && !o.returns && !this instanceof CommentNode && !this.contains(function(node) { + return node.is_statement_only(); }); return closure ? this.compile_closure(this.options) : this.compile_node(this.options); }; @@ -330,85 +367,144 @@ return this; }; Node.prototype.children = []; - Node.prototype.statement = function statement() { + Node.prototype.is_statement = function is_statement() { return false; }; - Node.prototype.statement_only = function statement_only() { + Node.prototype.is_statement_only = function is_statement_only() { return false; }; Node.prototype.top_sensitive = function top_sensitive() { return false; }; // A collection of nodes, each one representing an expression. - Expressions = (exports.Expressions = function Expressions() { - var __a, nodes; - nodes = Array.prototype.slice.call(arguments, 0); - this.expressions = flatten(nodes); - __a = this.children = this.expressions; - return Expressions === this.constructor ? this : __a; - }); - __a = function(){}; - __a.prototype = Node.prototype; - Expressions.__superClass__ = Node.prototype; - Expressions.prototype = new __a(); - Expressions.prototype.constructor = Expressions; - statement(Expressions); - // Wrap up a node as an Expressions, unless it already is. - Expressions.prototype.wrap = function wrap() { - var nodes; - nodes = Array.prototype.slice.call(arguments, 0); - if (nodes.length === 1 && nodes[0] instanceof Expressions) { - return nodes[0]; - } - return new Expressions.apply(this, nodes); - }; - // Tack an expression on to the end of this expression list. - Expressions.prototype.push = function push(node) { - this.expressions.push(node); - return this; - }; - // Tack an expression on to the beginning of this expression list. - Expressions.prototype.unshift = function unshift(node) { - this.expressions.unshift(node); - return this; - }; - // If this Expressions consists of a single node, pull it back out. - Expressions.prototype.unwrap = function unwrap() { - return this.expressions.length === 1 ? this.expressions[0] : this; - }; - // Is this an empty block of code? - Expressions.prototype.empty = function empty() { - return this.expressions.length === 0; - }; - // Is the node last in this block of expressions? - Expressions.prototype.is_last = function is_last(node) { - var l; - l = this.expressions.length; - this.last_index = this.last_index || this.expressions[l - 1] instanceof CommentNode ? -2 : -1; - return node === this.expressions[l - this.last_index]; - }; - Expressions.prototype.compile = function compile(o) { - return o.scope ? Expressions.__superClass__.compile.call(this, o) : this.compile_root(o); - }; - // Compile each expression in the Expressions body. - Expressions.prototype.compile_node = function compile_node(o) { - var __b, __c, __d, node; - return ((function() { - __b = []; __c = this.expressions; - for (__d = 0; __d < __c.length; __d++) { - node = __c[__d]; - __b.push(this.compile_expression(node, dup(o))); + Expressions = (exports.Expressions = inherit(Node, { + constructor: function constructor() { + var nodes; + nodes = Array.prototype.slice.call(arguments, 0); + this.expressions = flatten(nodes); + return this.children = this.expressions; + }, + // Wrap up a node as an Expressions, unless it already is. + wrap: function wrap() { + var nodes; + nodes = Array.prototype.slice.call(arguments, 0); + if (nodes.length === 1 && nodes[0] instanceof Expressions) { + return nodes[0]; } - return __b; - }).call(this)).join("\n"); - }; - // If this is the top-level Expressions, wrap everything in a safety closure. - Expressions.prototype.compile_root = function compile_root(o) { - var code, indent; - o.indent = (this.indent = (indent = o.no_wrap ? '' : TAB)); - o.scope = new Scope(null, this, null); - code = o.globals ? this.compile_node(o) : this.compile_with_declarations(o); - code = code.replace(TRAILING_WHITESPACE, ''); - return o.no_wrap ? code : "(function(){\n" + code + "\n})();"; + return new Expressions.apply(this, nodes); + }, + // Tack an expression on to the end of this expression list. + push: function push(node) { + this.expressions.push(node); + return this; + }, + // Tack an expression on to the beginning of this expression list. + unshift: function unshift(node) { + this.expressions.unshift(node); + return this; + }, + // If this Expressions consists of a single node, pull it back out. + unwrap: function unwrap() { + return this.expressions.length === 1 ? this.expressions[0] : this; + }, + // Is this an empty block of code? + empty: function empty() { + return this.expressions.length === 0; + }, + // Is the node last in this block of expressions? + is_last: function is_last(node) { + var l; + l = this.expressions.length; + this.last_index = this.last_index || this.expressions[l - 1] instanceof CommentNode ? -2 : -1; + return node === this.expressions[l - this.last_index]; + }, + compile: function compile(o) { + return o.scope ? compile.__superClass__.constructor.call(this, o) : this.compile_root(o); + }, + // Compile each expression in the Expressions body. + compile_node: function compile_node(o) { + var __a, __b, __c, node; + return ((function() { + __a = []; __b = this.expressions; + for (__c = 0; __c < __b.length; __c++) { + node = __b[__c]; + __a.push(this.compile_expression(node, dup(o))); + } + return __a; + }).call(this)).join("\n"); + }, + // If this is the top-level Expressions, wrap everything in a safety closure. + compile_root: function compile_root(o) { + var code, indent; + o.indent = (this.indent = (indent = o.no_wrap ? '' : TAB)); + o.scope = new Scope(null, this, null); + code = o.globals ? this.compile_node(o) : this.compile_with_declarations(o); + code = code.replace(TRAILING_WHITESPACE, ''); + return o.no_wrap ? code : "(function(){\n" + code + "\n})();"; + }, + // Compile the expressions body, with declarations of all inner variables + // pushed up to the top. + compile_with_declarations: function compile_with_declarations(o) { + var args, argv, code; + code = this.compile_node(o); + args = this.contains(function(node) { + return node instanceof ValueNode && node.arguments(); + }); + argv = args && o.scope.check('arguments') ? '' : 'var '; + if (args) { + code = this.idt() + argv + "arguments = Array.prototype.slice.call(arguments, 0);\n" + code; + } + if (o.scope.has_assignments(this)) { + code = this.idt() + 'var ' + o.scope.compiled_assignments() + ";\n" + code; + } + if (o.scope.has_declarations(this)) { + code = this.idt() + 'var ' + o.scope.compiled_declarations() + ";\n" + code; + } + return code; + }, + // Compiles a single expression within the expressions body. + compile_expression: function compile_expression(node, o) { + var returns, stmt, temp; + this.indent = o.indent; + stmt = node.is_statement(); + // We need to return the result if this is the last node in the expressions body. + returns = o.returns && this.is_last(node) && !node.is_statement_only(); + delete o.returns; + // Return the regular compile of the node, unless we need to return the result. + if (!(returns)) { + return (stmt ? '' : this.idt()) + node.compile(merge(o, { + top: true + })) + (stmt ? '' : ';'); + } + // If it's a statement, the node knows how to return itself. + if (node.is_statement()) { + return node.compile(merge(o, { + returns: true + })); + } + // If it's not part of a constructor, we can just return the value of the expression. + if (!((o.scope.function == undefined ? undefined : o.scope.function.is_constructor()))) { + return this.idt() + 'return ' + node.compile(o); + } + // It's the last line of a constructor, add a safety check. + temp = o.scope.free_variable(); + return this.idt() + temp + ' = ' + node.compile(o) + ";\n" + this.idt() + "return " + o.scope.function.name + ' === this.constructor ? this : ' + temp + ';'; + } + })); + statement(Expressions); + // Literals are static values that can be passed through directly into + // JavaScript without translation, eg.: strings, numbers, true, false, null... + LiteralNode = (exports.LiteralNode = function LiteralNode(value) { + var __a; + this.value = value; + __a = this.children = [value]; + return LiteralNode === this.constructor ? this : __a; + }); + // Break and continue must be treated as statements -- they lose their meaning + // when wrapped in a closure. + LiteralNode.prototype.is_statement = function is_statement() { + return this.value === 'break' || this.value === 'continue'; }; + LiteralNode.prototype.is_statement_only = LiteralNode.prototype.is_statement; + LiteralNode.prototype.compile_node = function compile_node(o) { }; })(); \ No newline at end of file diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index a9edf163..ce8e5e9e 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -150,7 +150,7 @@ module CoffeeScript end # Compile the expressions body, with declarations of all inner variables - # at the top. + # pushed up to the top. def compile_with_declarations(o={}) code = compile_node(o) args = self.contains? {|n| n.is_a?(ValueNode) && n.arguments? } @@ -161,7 +161,7 @@ module CoffeeScript write(code) end - # Compiles a single expression within the expression list. + # Compiles a single expression within the expressions body. def compile_expression(node, o) @indent = o[:indent] stmt = node.statement? diff --git a/src/nodes.coffee b/src/nodes.coffee index 4a958250..65565221 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -63,12 +63,25 @@ dup: (input) -> (output[key]: val) for key, val of input output +# Merge objects. +merge: (src, dest) -> + dest[key]: val for key, val of src + dest + # Delete a key from an object, returning the value. del: (obj, key) -> val: obj[key] delete obj[key] val +# Quickie inheritance convenience wrapper to reduce typing. +inherit: (parent, props) -> + klass: props.constructor + delete props.constructor + klass extends parent + klass.prototype[name]: prop for name, prop of props + klass + # # Provide a quick implementation of a children method. # children: (klass, attrs...) -> # klass::children: -> @@ -77,8 +90,8 @@ del: (obj, key) -> # Mark a node as a statement, or a statement only. statement: (klass, only) -> - klass::statement: -> true - klass::statement_only: -> true if only + klass::is_statement: -> true + klass::is_statement_only: -> true if only # The abstract base class for all CoffeeScript nodes. @@ -98,9 +111,9 @@ Node::compile: (o) -> @options: dup(o || {}) @indent: o.indent top: if @top_sensitive() then o.top else del obj 'top' - closure: @statement() and not @statement_only() and not top and + closure: @is_statement() and not @is_statement_only() and not top and not o.returns and not this instanceof CommentNode and - not @contains (node) -> node.statement_only() + not @contains (node) -> node.is_statement_only() if closure then @compile_closure(@options) else @compile_node(@options) # Statements converted into expressions share scope with their parent @@ -124,64 +137,124 @@ Node::contains: (block) -> false # Default implementations of the common node methods. -Node::unwrap: -> this -Node::children: [] -Node::statement: -> false -Node::statement_only: -> false -Node::top_sensitive: -> false +Node::unwrap: -> this +Node::children: [] +Node::is_statement: -> false +Node::is_statement_only: -> false +Node::top_sensitive: -> false # A collection of nodes, each one representing an expression. -Expressions: exports.Expressions: (nodes...) -> - @expressions: flatten nodes - @children: @expressions +Expressions: exports.Expressions: inherit Node, { + + constructor: (nodes...) -> + @expressions: flatten nodes + @children: @expressions + + # Wrap up a node as an Expressions, unless it already is. + wrap: (nodes...) -> + return nodes[0] if nodes.length is 1 and nodes[0] instanceof Expressions + new Expressions(nodes...) + + # Tack an expression on to the end of this expression list. + push: (node) -> + @expressions.push(node) + this + + # Tack an expression on to the beginning of this expression list. + unshift: (node) -> + @expressions.unshift(node) + this + + # If this Expressions consists of a single node, pull it back out. + unwrap: -> + if @expressions.length is 1 then @expressions[0] else this + + # Is this an empty block of code? + empty: -> + @expressions.length is 0 + + # Is the node last in this block of expressions? + is_last: (node) -> + l: @expressions.length + @last_index ||= if @expressions[l - 1] instanceof CommentNode then -2 else -1 + node is @expressions[l - @last_index] + + compile: (o) -> + if o.scope then super(o) else @compile_root(o) + + # Compile each expression in the Expressions body. + compile_node: (o) -> + (@compile_expression(node, dup(o)) for node in @expressions).join("\n") + + # If this is the top-level Expressions, wrap everything in a safety closure. + compile_root: (o) -> + o.indent: @indent: indent: if o.no_wrap then '' else TAB + o.scope: new Scope(null, this, null) + code: if o.globals then @compile_node(o) else @compile_with_declarations(o) + code: code.replace(TRAILING_WHITESPACE, '') + if o.no_wrap then code else "(function(){\n"+code+"\n})();" + + # Compile the expressions body, with declarations of all inner variables + # pushed up to the top. + compile_with_declarations: (o) -> + code: @compile_node(o) + args: @contains (node) -> node instanceof ValueNode and node.arguments() + argv: if args and o.scope.check('arguments') then '' else 'var ' + code: @idt() + argv + "arguments = Array.prototype.slice.call(arguments, 0);\n" + code if args + code: @idt() + 'var ' + o.scope.compiled_assignments() + ";\n" + code if o.scope.has_assignments(this) + code: @idt() + 'var ' + o.scope.compiled_declarations() + ";\n" + code if o.scope.has_declarations(this) + code + + # Compiles a single expression within the expressions body. + compile_expression: (node, o) -> + @indent: o.indent + stmt: node.is_statement() + # We need to return the result if this is the last node in the expressions body. + returns: o.returns and @is_last(node) and not node.is_statement_only() + delete o.returns + # Return the regular compile of the node, unless we need to return the result. + return (if stmt then '' else @idt()) + node.compile(merge(o, {top: true})) + (if stmt then '' else ';') unless returns + # If it's a statement, the node knows how to return itself. + return node.compile(merge(o, {returns: true})) if node.is_statement() + # If it's not part of a constructor, we can just return the value of the expression. + return @idt() + 'return ' + node.compile(o) unless o.scope.function?.is_constructor() + # It's the last line of a constructor, add a safety check. + temp: o.scope.free_variable() + @idt() + temp + ' = ' + node.compile(o) + ";\n" + @idt() + "return " + o.scope.function.name + ' === this.constructor ? this : ' + temp + ';' +} -Expressions extends Node statement Expressions -# Wrap up a node as an Expressions, unless it already is. -Expressions::wrap: (nodes...) -> - return nodes[0] if nodes.length is 1 and nodes[0] instanceof Expressions - new Expressions(nodes...) -# Tack an expression on to the end of this expression list. -Expressions::push: (node) -> - @expressions.push(node) - this +# Literals are static values that can be passed through directly into +# JavaScript without translation, eg.: strings, numbers, true, false, null... +LiteralNode: exports.LiteralNode: (value) -> + @value: value + @children: [value] + +# Break and continue must be treated as statements -- they lose their meaning +# when wrapped in a closure. +LiteralNode::is_statement: -> + @value is 'break' or @value is 'continue' + +LiteralNode::is_statement_only: LiteralNode::is_statement + +LiteralNode::compile_node: (o) -> + + + + + + + + + + + -# Tack an expression on to the beginning of this expression list. -Expressions::unshift: (node) -> - @expressions.unshift(node) - this -# If this Expressions consists of a single node, pull it back out. -Expressions::unwrap: -> - if @expressions.length is 1 then @expressions[0] else this -# Is this an empty block of code? -Expressions::empty: -> - @expressions.length is 0 - -# Is the node last in this block of expressions? -Expressions::is_last: (node) -> - l: @expressions.length - @last_index ||= if @expressions[l - 1] instanceof CommentNode then -2 else -1 - node is @expressions[l - @last_index] - -Expressions::compile: (o) -> - if o.scope then super(o) else @compile_root(o) - -# Compile each expression in the Expressions body. -Expressions::compile_node: (o) -> - (@compile_expression(node, dup(o)) for node in @expressions).join("\n") - -# If this is the top-level Expressions, wrap everything in a safety closure. -Expressions::compile_root: (o) -> - o.indent: @indent: indent: if o.no_wrap then '' else TAB - o.scope: new Scope(null, this, null) - code: if o.globals then @compile_node(o) else @compile_with_declarations(o) - code: code.replace(TRAILING_WHITESPACE, '') - if o.no_wrap then code else "(function(){\n"+code+"\n})();"