diff --git a/lib/coffee_script/nodes.js b/lib/coffee_script/nodes.js index 050abd3a..04e3057e 100644 --- a/lib/coffee_script/nodes.js +++ b/lib/coffee_script/nodes.js @@ -1,5 +1,5 @@ (function(){ - var compact, dup, flatten; + var Expressions, Node, TAB, TRAILING_WHITESPACE, __a, compact, del, dup, flatten, 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 @@ -8,13 +8,6 @@ // generated code should be wrapped up in a closure. An options hash is passed // and cloned throughout, containing messages from higher in the AST, // information about the current scope, and indentation level. - exports.Node = function Node() { - var __a; - var arguments = Array.prototype.slice.call(arguments, 0); - this.values = arguments; - __a = this.name = this.constructor.name; - return Node === this.constructor ? this : __a; - }; exports.Expressions = function Expressions() { var __a; var arguments = Array.prototype.slice.call(arguments, 0); @@ -201,139 +194,124 @@ return this.values = values; }; // Some helper functions - // TODO -- shallow (1 deep) flatten.. - // need recursive version.. - flatten = function flatten(aggList, newList) { - var __a, __b, item; - __a = newList; - for (__b = 0; __b < __a.length; __b++) { - item = __a[__b]; - aggList.push(item); - } - return aggList; - }; - compact = function compact(input) { - var __a, __b, __c, compected, item; - compected = []; - __a = []; __b = input; + // Tabs are two spaces for pretty printing. + TAB = ' '; + TRAILING_WHITESPACE = /\s+$/g; + // Flatten nested arrays recursively. + flatten = function flatten(list) { + var __a, __b, __c, item, memo; + memo = []; + __a = []; __b = list; for (__c = 0; __c < __b.length; __c++) { item = __b[__c]; - __a.push((typeof item !== "undefined" && item !== null) ? compacted.push(item) : null); + if (item instanceof Array) { + return memo.concat(flatten(item)); + } + memo.push(item); + memo; } return __a; }; - dup = function dup(input) { - var __a, __b, __c, key, output, val; - output = null; - if (input instanceof Array) { - output = []; - __a = input; - for (__b = 0; __b < __a.length; __b++) { - val = __a[__b]; - output.push(val); + // Remove all null values from an array. + compact = function compact(input) { + var __a, __b, __c, item; + __a = []; __b = input; + for (__c = 0; __c < __b.length; __c++) { + item = __b[__c]; + if ((typeof item !== "undefined" && item !== null)) { + __a.push(item); } + } + return __a; + }; + // Dup an array or object. + dup = function dup(input) { + var __a, __b, __c, __d, key, output, val; + if (input instanceof Array) { + __a = []; __b = input; + for (__c = 0; __c < __b.length; __c++) { + val = __b[__c]; + __a.push(val); + } + return __a; } else { output = { }; - __c = input; - for (key in __c) { - val = __c[key]; - if (__hasProp.call(__c, key)) { - output.key = val; + __d = input; + for (key in __d) { + val = __d[key]; + if (__hasProp.call(__d, key)) { + ((output[key] = val)); } } - output; + return output; } - return output; }; - exports.Node.prototype.TAB = ' '; - // Tag this node as a statement, meaning that it can't be used directly as - // the result of an expression. - exports.Node.prototype.mark_as_statement = function mark_as_statement() { - return this.is_statement = function is_statement() { + // Delete a key from an object, returning the value. + del = function del(obj, key) { + var val; + val = obj[key]; + delete obj[key]; + return val; + }; + // # Provide a quick implementation of a children method. + // children: (klass, attrs...) -> + // klass::children: -> + // nodes: this[attr] for attr in attrs + // compact flatten nodes + // Mark a node as a statement, or a statement only. + statement = function statement(klass, only) { + klass.prototype.statement = function statement() { return true; }; - }; - // Tag this node as a statement that cannot be transformed into an expression. - // (break, continue, etc.) It doesn't make sense to try to transform it. - exports.Node.prototype.mark_as_statement_only = function mark_as_statement_only() { - this.mark_as_statement(); - return this.is_statement_only = function is_statement_only() { - return true; + return klass.prototype.statement_only = function statement_only() { + if (only) { + return true; + } }; }; - // This node needs to know if it's being compiled as a top-level statement, - // in order to compile without special expression conversion. - exports.Node.prototype.mark_as_top_sensitive = function mark_as_top_sensitive() { - return this.is_top_sensitive = function is_top_sensitive() { - return true; - }; - }; - // Provide a quick implementation of a children method. - exports.Node.prototype.children = function children(attributes) { - var __a, __b, agg, compacted, item; - // TODO -- are these optimal impls of flatten and compact - // .. do better ones exist in a stdlib? - agg = []; - __a = attributes; - for (__b = 0; __b < __a.length; __b++) { - item = __a[__b]; - agg = flatten(agg, item); - } - compacted = compact(agg); - return this.children = function children() { - return compacted; - }; - }; - exports.Node.prototype.write = function write(code) { - // hm.. - // TODO -- should print to STDOUT in "VERBOSE" how to - // go about this.. ? jsonify 'this'? - // use node's puts ?? - return code; - }; + // The abstract base class for all CoffeeScript nodes. + // All nodes are implement a "compile_node" method, which performs the + // code generation for that node. To compile a node, call the "compile" + // method, which wraps "compile_node" in some extra smarts, to know when the + // generated code should be wrapped up in a closure. An options hash is passed + // and cloned throughout, containing messages from higher in the AST, + // information about the current scope, and indentation level. + Node = (exports.Node = function Node() { }); // This is extremely important -- we convert JS statements into expressions // by wrapping them in a closure, only if it's possible, and we're not at // the top level of a block (which would be unnecessary), and we haven't // already been asked to return the result. - exports.Node.prototype.compile = function compile(o) { - var closure, opts, top; - // TODO -- need JS dup/clone - opts = (typeof !o !== "undefined" && !o !== null) ? { - } : o; - this.options = opts; - this.indent = opts.indent; - top = this.options.top; - !this.is_top_sentitive() ? (this.options.top = undefined) : null; - closure = this.is_statement() && !this.is_statement_only() && !top && typeof (this) === "CommentNode"; - closure = closure && !this.do_i_contain(function(n) { - return n.is_statement_only(); + Node.prototype.compile = function compile(o) { + var closure, top; + this.options = dup(o || { }); - return closure ? this.compile_closure(this.options) : compile_node(this.options); + 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(); + }); + return closure ? this.compile_closure(this.options) : this.compile_node(this.options); }; // Statements converted into expressions share scope with their parent // closure, to preserve JavaScript-style lexical scope. - exports.Node.prototype.compile_closure = function compile_closure(o) { - var opts; - opts = (typeof !o !== "undefined" && !o !== null) ? { - } : o; - this.indent = opts.indent; - opts.shared_scope = o.scope; - return exports.ClosureNode.wrap(this).compile(opts); + Node.prototype.compile_closure = function compile_closure(o) { + this.indent = o.indent; + o.shared_scope = o.scope; + return ClosureNode.wrap(this).compile(o); }; // Quick short method for the current indentation level, plus tabbing in. - exports.Node.prototype.idt = function idt(tLvl) { - var __a, __b, __c, __d, tabAmt, tabs, x; - tabs = (typeof tLvl !== "undefined" && tLvl !== null) ? tLvl : 0; - tabAmt = ''; - __c = 0; __d = tabs; - for (__b=0, x=__c; (__c <= __d ? x < __d : x > __d); (__c <= __d ? x += 1 : x -= 1), __b++) { - tabAmt = tabAmt + this.TAB; + Node.prototype.idt = function idt(tabs) { + var __a, __b, __c, __d, i, idt; + idt = this.indent; + __c = 0; __d = (tabs || 0); + for (__b=0, i=__c; (__c <= __d ? i <= __d : i >= __d); (__c <= __d ? i += 1 : i -= 1), __b++) { + idt += TAB; } - return this.indent + tabAmt; + return idt; }; - //Does this node, or any of it's children, contain a node of a certain kind? - exports.Node.prototype.do_i_contain = function do_i_contain(block) { + // Does this node, or any of its children, contain a node of a certain kind? + Node.prototype.contains = function contains(block) { var __a, __b, node; __a = this.children; for (__b = 0; __b < __a.length; __b++) { @@ -341,103 +319,96 @@ if (block(node)) { return true; } - if (node instanceof exports.Node && node.do_i_contain(block)) { + if (node instanceof Node && node.contains(block)) { return true; } } return false; }; // Default implementations of the common node methods. - exports.Node.prototype.unwrap = function unwrap() { + Node.prototype.unwrap = function unwrap() { return this; }; - exports.Node.prototype.children = []; - exports.Node.prototype.is_a_statement = function is_a_statement() { + Node.prototype.children = []; + Node.prototype.statement = function statement() { return false; }; - exports.Node.prototype.is_a_statement_only = function is_a_statement_only() { + Node.prototype.statement_only = function statement_only() { return false; }; - exports.Node.prototype.is_top_sensitive = function is_top_sensitive() { + Node.prototype.top_sensitive = function top_sensitive() { return false; }; // A collection of nodes, each one representing an expression. - // exports.Expressions: (nodes) -> - // this.mark_as_statement() - // this.expressions: [] - // this.children([this.expressions]) - // for n in nodes - // this.expressions: flatten this.expressions, n - // exports.Expressions extends exports.Node - exports.Expressions.prototype.TRAILING_WHITESPACE = /\s+$/; + 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. - exports.Expressions.prototype.wrap = function wrap(nodes) { - if (nodes.length === 1 && nodes[0] instanceof exports.Expressions) { + 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(nodes); + return new Expressions.apply(this, nodes); }; // Tack an expression on to the end of this expression list. - exports.Expressions.prototype.push = function push(node) { + Expressions.prototype.push = function push(node) { this.expressions.push(node); return this; }; // Tack an expression on to the beginning of this expression list. - exports.Expressions.prototype.unshift = function unshift(node) { + Expressions.prototype.unshift = function unshift(node) { this.expressions.unshift(node); return this; }; // If this Expressions consists of a single node, pull it back out. - exports.Expressions.prototype.unwrap = function unwrap() { + Expressions.prototype.unwrap = function unwrap() { return this.expressions.length === 1 ? this.expressions[0] : this; }; // Is this an empty block of code? - exports.Expressions.prototype.is_empty = function is_empty() { + Expressions.prototype.empty = function empty() { return this.expressions.length === 0; }; - // Is the node last in this block of expressions. - exports.Expressions.prototype.is_last = function is_last(node) { - var arr_length; - arr_length = this.expressions.length; - this.last_index = this.last_index || this.expressions[arr_length - 1] instanceof exports.CommentNode ? -2 : -1; - return node === this.expressions[arr_length - this.last_index]; + // 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]; }; - exports.Expressions.prototype.compile = function compile(o) { - var opts; - opts = (typeof o !== "undefined" && o !== null) ? o : { - }; - return opts.scope ? exports.Expressions.__superClass__.compile.call(this, dup(opts)) : this.compile_root(o); + 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. - exports.Expressions.prototype.compile_node = function compile_node(options) { - var __a, __b, __c, __d, __e, code, compiled, e, line, opts; - opts = (typeof options !== "undefined" && options !== null) ? options : { - }; - compiled = []; - __a = this.expressions; - for (__b = 0; __b < __a.length; __b++) { - e = __a[__b]; - compiled.push(this.compile_expression(e, dup(options))); - } - code = ''; - __c = []; __d = compiled; - for (__e = 0; __e < __d.length; __e++) { - line = __d[__e]; - __c.push((code = code + line + '\n')); - } - return __c; + 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))); + } + return __b; + }).call(this)).join("\n"); }; // If this is the top-level Expressions, wrap everything in a safety closure. - exports.Expressions.prototype.compile_root = function compile_root(o) { - var code, indent, opts; - opts = (typeof o !== "undefined" && o !== null) ? o : { - }; - indent = opts.no_wrap ? '' : this.TAB; - this.indent = indent; - opts.indent = indent; - opts.scope = new Scope(null, this, null); - code = opts.globals ? compile_node(opts) : compile_with_declarations(opts); - code.replace(this.TRAILING_WHITESPACE, ''); - return this.write(opts.no_wrap ? code : "(function(){\n" + code + "\n})();"); + 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})();"; }; })(); \ No newline at end of file diff --git a/lib/coffee_script/nodes.rb b/lib/coffee_script/nodes.rb index 6b0ce78d..a9edf163 100644 --- a/lib/coffee_script/nodes.rb +++ b/lib/coffee_script/nodes.rb @@ -67,7 +67,7 @@ module CoffeeScript @indent + (TAB * tabs) end - # Does this node, or any of it's children, contain a node of a certain kind? + # Does this node, or any of its children, contain a node of a certain kind? def contains?(&block) children.each do |node| return true if yield(node) @@ -124,7 +124,7 @@ module CoffeeScript @expressions.empty? end - # Is the node last in this block of expressions. + # Is the node last in this block of expressions? def last?(node) @last_index ||= @expressions.last.is_a?(CommentNode) ? -2 : -1 node == @expressions[@last_index] @@ -135,8 +135,8 @@ module CoffeeScript end # Compile each expression in the Expressions body. - def compile_node(options={}) - write(@expressions.map {|n| compile_expression(n, options.dup) }.join("\n")) + def compile_node(o={}) + write(@expressions.map {|n| compile_expression(n, o.dup) }.join("\n")) end # If this is the top-level Expressions, wrap everything in a safety closure. diff --git a/lib/coffee_script/parser.js b/lib/coffee_script/parser.js index 3ad533ca..1eeb7dd1 100644 --- a/lib/coffee_script/parser.js +++ b/lib/coffee_script/parser.js @@ -84,6 +84,8 @@ return new AssignNode(new ValueNode(yytext), $3, 'object'); }), o("STRING ASSIGN Expression", function() { return new AssignNode(new ValueNode(new LiteralNode(yytext)), $3, 'object'); + }), o("NUMBER ASSIGN Expression", function() { + return new AssignNode(new ValueNode(new LiteralNode(yytext)), $3, 'object'); }), o("Comment") ], // A return statement. @@ -210,208 +212,264 @@ return new ValueNode($1); }), o("Range", function() { return new ValueNode($1); - }), - // o "Value Accessor", -> $1.push($2) - o("Invocation Accessor", function() { + }), o("Value Accessor", function() { + return $1.push($2); + }), o("Invocation Accessor", function() { return new ValueNode($1, [$2]); }) + ], + // Accessing into an object or array, through dot or index notation. + Accessor: [o("PROPERTY_ACCESS IDENTIFIER", function() { + return new AccessorNode(yytext); + }), o("PROTOTYPE_ACCESS IDENTIFIER", function() { + return new AccessorNode(yytext, 'prototype'); + }), o("SOAK_ACCESS IDENTIFIER", function() { + return new AccessorNode(yytext, 'soak'); + }), o("Index"), o("Slice", function() { + return new SliceNode($1); + }) + ], + // Indexing into an object or array. + Index: [o("INDEX_START Expression INDEX_END", function() { + return new IndexNode($2); + }) + ], + // An object literal. + Object: [o("{ AssignList }", function() { + return new ObjectNode($2); + }) + ], + // Assignment within an object literal (comma or newline separated). + AssignList: [o("", function() { + return []; + }), o("AssignObj", function() { + return [$1]; + }), o("AssignList , AssignObj", function() { + return $1.push($3); + }), o("AssignList TERMINATOR AssignObj", function() { + return $1.push($3); + }), o("AssignList , TERMINATOR AssignObj", function() { + return $1.push($4); + }), o("INDENT AssignList OUTDENT", function() { + return $2; + }) + ], + // All flavors of function call (instantiation, super, and regular). + Call: [o("Invocation", function() { + return $1; + }), o("NEW Invocation", function() { + return $2.new_instance(); + }), o("Super", function() { + return $1; + }) + ], + // Extending an object's prototype. + Extends: [o("Value EXTENDS Value", function() { + return new ExtendsNode($1, $3); + }) + ], + // A generic function invocation. + Invocation: [o("Value Arguments", function() { + return new CallNode($1, $2); + }), o("Invocation Arguments", function() { + return new CallNode($1, $2); + }) + ], + // The list of arguments to a function invocation. + Arguments: [o("CALL_START ArgList CALL_END", function() { + return $2; + }) + ], + // Calling super. + Super: [o("SUPER CALL_START ArgList CALL_END", function() { + return new CallNode('super', $3); + }) + ], + // The range literal. + Range: [o("[ Expression . . Expression ]", function() { + return new RangeNode($2, $5); + }), o("[ Expression . . . Expression ]", function() { + return new RangeNode($2, $6, true); + }) + ], + // The slice literal. + Slice: [o("INDEX_START Expression . . Expression INDEX_END", function() { + return new RangeNode($2, $5); + }), o("INDEX_START Expression . . . Expression INDEX_END", function() { + return new RangeNode($2, $6, true); + }) + ], + // The array literal. + Array: [o("[ ArgList ]", function() { + return new ArrayNode($2); + }) + ], + // A list of arguments to a method call, or as the contents of an array. + ArgList: [o("", function() { + return []; + }), o("Expression", function() { + return val; + }), o("INDENT Expression", function() { + return [$2]; + }), o("ArgList , Expression", function() { + return $1.push($3); + }), o("ArgList TERMINATOR Expression", function() { + return $1.push($3); + }), o("ArgList , TERMINATOR Expression", function() { + return $1.push($4); + }), o("ArgList , INDENT Expression", function() { + return $1.push($4); + }), o("ArgList OUTDENT", function() { + return $1; + }) + ], + // Just simple, comma-separated, required arguments (no fancy syntax). + SimpleArgs: [o("Expression", function() { + return $1; + }), o("SimpleArgs , Expression", function() { + return ([$1].push($3)).reduce(function(a, b) { + return a.concat(b); + }); + }) + ], + // Try/catch/finally exception handling blocks. + Try: [o("TRY Block Catch", function() { + return new TryNode($2, $3[0], $3[1]); + }), o("TRY Block FINALLY Block", function() { + return new TryNode($2, null, null, $4); + }), o("TRY Block Catch FINALLY Block", function() { + return new TryNode($2, $3[0], $3[1], $5); + }) + ], + // A catch clause. + Catch: [o("CATCH IDENTIFIER Block", function() { + return [$2, $3]; + }) + ], + // Throw an exception. + Throw: [o("THROW Expression", function() { + return new ThrowNode($2); + }) + ], + // Parenthetical expressions. + Parenthetical: [o("( Expression )", function() { + return new ParentheticalNode($2); + }) + ], + // The while loop. (there is no do..while). + While: [o("WHILE Expression Block", function() { + return new WhileNode($2, $3); + }), o("WHILE Expression", function() { + return new WhileNode($2, null); + }), o("Expression WHILE Expression", function() { + return new WhileNode($3, Expressions.wrap($1)); + }) + ], + // Array comprehensions, including guard and current index. + // Looks a little confusing, check nodes.rb for the arguments to ForNode. + For: [o("Expression FOR ForVariables ForSource", function() { + return new ForNode($1, $4, $3[0], $3[1]); + }), o("FOR ForVariables ForSource Block", function() { + return new ForNode($4, $3, $2[0], $2[1]); + }) + ], + // An array comprehension has variables for the current element and index. + ForVariables: [o("IDENTIFIER", function() { + return [$1]; + }), o("IDENTIFIER , IDENTIFIER", function() { + return [$1, $3]; + }) + ], + // The source of the array comprehension can optionally be filtered. + ForSource: [o("IN Expression", function() { + return { + source: $2 + }; + }), o("OF Expression", function() { + return { + source: $2, + object: true + }; + }), o("ForSource WHEN Expression", function() { + $1.filter = $3; + return $1; + }), o("ForSource BY Expression", function() { + $1.step = $3; + return $1; + }) + ], + // Switch/When blocks. + Switch: [o("SWITCH Expression INDENT Whens OUTDENT", function() { + return $4.rewrite_condition($2); + }), o("SWITCH Expression INDENT Whens ELSE Block OUTDENT", function() { + return $4.rewrite_condition($2).add_else($6); + }) + ], + // The inner list of whens. + Whens: [o("When", function() { + return $1; + }), o("Whens When", function() { + return $1.push($2); + }) + ], + // An individual when. + When: [o("LEADING_WHEN SimpleArgs Block", function() { + return new IfNode($2, $3, null, { + statement: true + }); + }), o("LEADING_WHEN SimpleArgs Block TERMINATOR", function() { + return new IfNode($2, $3, null, { + statement: true + }); + }), o("Comment TERMINATOR When", function() { + return $3.add_comment($1); + }) + ], + // The most basic form of "if". + IfBlock: [o("IF Expression Block", function() { + return new IfNode($2, $3); + }) + ], + // An elsif portion of an if-else block. + ElsIf: [o("ELSE IfBlock", function() { + return $2.force_statement(); + }) + ], + // Multiple elsifs can be chained together. + ElsIfs: [o("ElsIf", function() { + return $1; + }), o("ElsIfs ElsIf", function() { + return $1.add_else($2); + }) + ], + // Terminating else bodies are strictly optional. + ElseBody: [o("", function() { + return null; + }), o("ELSE Block", function() { + return $2; + }) + ], + // All the alternatives for ending an if-else block. + IfEnd: [o("ElseBody", function() { + return $1; + }), o("ElsIfs ElseBody", function() { + return $1.add_else($2); + }) + ], + // The full complement of if blocks, including postfix one-liner ifs and unlesses. + If: [o("IfBlock IfEnd", function() { + return $1.add_else($2); + }), o("Expression IF Expression", function() { + return new IfNode($3, Expressions.wrap($1), null, { + statement: true + }); + }), o("Expression UNLESS Expression", function() { + return new IfNode($3, Expressions.wrap($1), null, { + statement: true, + invert: true + }); + }) ] - // # Accessing into an object or array, through dot or index notation. - // Accessor: [ - // o "PROPERTY_ACCESS IDENTIFIER", -> new AccessorNode($2) - // o "PROTOTYPE_ACCESS IDENTIFIER", -> new AccessorNode($2, 'prototype') - // o "SOAK_ACCESS IDENTIFIER", -> new AccessorNode($2, 'soak') - // o "Index" - // o "Slice", -> new SliceNode($1) - // ] - // - // # Indexing into an object or array. - // Index: [ - // o "INDEX_START Expression INDEX_END", -> new IndexNode($2) - // ] - // - // # An object literal. - // Object: [ - // o "{ AssignList }", -> new ObjectNode($2) - // ] - // - // # Assignment within an object literal (comma or newline separated). - // AssignList: [ - // o "", -> [] - // o "AssignObj", -> [$1] - // o "AssignList , AssignObj", -> $1.push $3 - // o "AssignList TERMINATOR AssignObj", -> $1.push $3 - // o "AssignList , TERMINATOR AssignObj", -> $1.push $4 - // o "INDENT AssignList OUTDENT", -> $2 - // ] - // - // # All flavors of function call (instantiation, super, and regular). - // Call: [ - // o "Invocation", -> $1 - // o "NEW Invocation", -> $2.new_instance() - // o "Super", -> $1 - // ] - // - // # Extending an object's prototype. - // Extends: [ - // o "Value EXTENDS Value", -> new ExtendsNode($1, $3) - // ] - // - // # A generic function invocation. - // Invocation: [ - // o "Value Arguments", -> new CallNode($1, $2) - // o "Invocation Arguments", -> new CallNode($1, $2) - // ] - // - // # The list of arguments to a function invocation. - // Arguments: [ - // o "CALL_START ArgList CALL_END", -> $2 - // ] - // - // # Calling super. - // Super: [ - // o "SUPER CALL_START ArgList CALL_END", -> new CallNode('super', $3) - // ] - // - // # The range literal. - // Range: [ - // o "[ Expression . . Expression ]", -> new RangeNode($2, $5) - // o "[ Expression . . . Expression ]", -> new RangeNode($2, $6, true) - // ] - // - // # The slice literal. - // Slice: [ - // o "INDEX_START Expression . . Expression INDEX_END", -> new RangeNode($2, $5) - // o "INDEX_START Expression . . . Expression INDEX_END", -> new RangeNode($2, $6, true) - // ] - // - // # The array literal. - // Array: [ - // o "[ ArgList ]", -> new ArrayNode($2) - // ] - // - // # A list of arguments to a method call, or as the contents of an array. - // ArgList: [ - // o "", -> [] - // o "Expression", -> val - // o "INDENT Expression", -> [$2] - // o "ArgList , Expression", -> $1.push $3 - // o "ArgList TERMINATOR Expression", -> $1.push $3 - // o "ArgList , TERMINATOR Expression", -> $1.push $4 - // o "ArgList , INDENT Expression", -> $1.push $4 - // o "ArgList OUTDENT", -> $1 - // ] - // - // # Just simple, comma-separated, required arguments (no fancy syntax). - // SimpleArgs: [ - // o "Expression", -> $1 - // o "SimpleArgs , Expression", -> - // ([$1].push($3)).reduce (a, b) -> a.concat(b) - // ] - // - // # Try/catch/finally exception handling blocks. - // Try: [ - // o "TRY Block Catch", -> new TryNode($2, $3[0], $3[1]) - // o "TRY Block FINALLY Block", -> new TryNode($2, nil, nil, $4) - // o "TRY Block Catch FINALLY Block", -> new TryNode($2, $3[0], $3[1], $5) - // ] - // - // # A catch clause. - // Catch: [ - // o "CATCH IDENTIFIER Block", -> [$2, $3] - // ] - // - // # Throw an exception. - // Throw: [ - // o "THROW Expression", -> new ThrowNode($2) - // ] - // - // # Parenthetical expressions. - // Parenthetical: [ - // o "( Expression )", -> new ParentheticalNode($2) - // ] - // - // # The while loop. (there is no do..while). - // While: [ - // o "WHILE Expression Block", -> new WhileNode($2, $3) - // o "WHILE Expression", -> new WhileNode($2, nil) - // o "Expression WHILE Expression", -> new WhileNode($3, Expressions.wrap($1)) - // ] - // - // # Array comprehensions, including guard and current index. - // # Looks a little confusing, check nodes.rb for the arguments to ForNode. - // For: [ - // o "Expression FOR ForVariables ForSource", -> new ForNode($1, $4, $3[0], $3[1]) - // o "FOR ForVariables ForSource Block", -> new ForNode($4, $3, $2[0], $2[1]) - // ] - // - // # An array comprehension has variables for the current element and index. - // ForVariables: [ - // o "IDENTIFIER", -> [$1] - // o "IDENTIFIER , IDENTIFIER", -> [$1, $3] - // ] - // - // # The source of the array comprehension can optionally be filtered. - // ForSource: [ - // o "IN Expression", -> {source: $2} - // o "OF Expression", -> {source: $2, object: true} - // o "ForSource WHEN Expression", -> $1.filter: $3; $1 - // o "ForSource BY Expression", -> $1.step: $3; $1 - // ] - // - // # Switch/When blocks. - // Switch: [ - // o "SWITCH Expression INDENT Whens OUTDENT", -> $4.rewrite_condition($2) - // o "SWITCH Expression INDENT Whens ELSE Block OUTDENT", -> $4.rewrite_condition($2).add_else($6) - // ] - // - // # The inner list of whens. - // Whens: [ - // o "When", -> $1 - // o "Whens When", -> $1.push $2 - // ] - // - // # An individual when. - // When: [ - // o "LEADING_WHEN SimpleArgs Block", -> new IfNode($2, $3, nil, {statement: true}) - // o "LEADING_WHEN SimpleArgs Block TERMINATOR", -> new IfNode($2, $3, nil, {statement: true}) - // o "Comment TERMINATOR When", -> $3.add_comment($1) - // ] - // - // # The most basic form of "if". - // IfBlock: [ - // o "IF Expression Block", -> new IfNode($2, $3) - // ] - // - // # An elsif portion of an if-else block. - // ElsIf: [ - // o "ELSE IfBlock", -> $2.force_statement() - // ] - // - // # Multiple elsifs can be chained together. - // ElsIfs: [ - // o "ElsIf", -> $1 - // o "ElsIfs ElsIf", -> $1.add_else($2) - // ] - // - // # Terminating else bodies are strictly optional. - // ElseBody: [ - // o "", -> null - // o "ELSE Block", -> $2 - // ] - // - // # All the alternatives for ending an if-else block. - // IfEnd: [ - // o "ElseBody", -> $1 - // o "ElsIfs ElseBody", -> $1.add_else($2) - // ] - // - // # The full complement of if blocks, including postfix one-liner ifs and unlesses. - // If: [ - // o "IfBlock IfEnd", -> $1.add_else($2) - // o "Expression IF Expression", -> new IfNode($3, Expressions.wrap($1), nil, {statement: true}) - // o "Expression UNLESS Expression", -> new IfNode($3, Expressions.wrap($1), nil, {statement: true, invert: true}) - // ] }; // Helpers ============================================================== // Make the Jison parser. diff --git a/lib/coffee_script/rewriter.js b/lib/coffee_script/rewriter.js index 901c6039..5e1a3d90 100644 --- a/lib/coffee_script/rewriter.js +++ b/lib/coffee_script/rewriter.js @@ -199,7 +199,7 @@ last = stack.pop(); stack[stack.length - 1] += last; } - if (stack[stack.length - 1] > 0 && (IMPLICIT_END.indexOf(token[0]) >= 0 || (typeof !post !== "undefined" && !post !== null))) { + if (stack[stack.length - 1] > 0 && (IMPLICIT_END.indexOf(token[0]) >= 0 || !(typeof post !== "undefined" && post !== null))) { idx = token[0] === 'OUTDENT' ? i + 1 : i; __k = 0; __l = stack[stack.length - 1]; for (__j=0, tmp=__k; (__k <= __l ? tmp < __l : tmp > __l); (__k <= __l ? tmp += 1 : tmp -= 1), __j++) { diff --git a/src/lexer.coffee b/src/lexer.coffee index 330e47ba..1431b8f9 100644 --- a/src/lexer.coffee +++ b/src/lexer.coffee @@ -232,13 +232,13 @@ lex::token: (tag, value) -> # Look at a tag in the current token stream. lex::tag: (index, tag) -> - return unless tok: this.tokens[this.tokens.length - (index || 1)] + return unless tok: this.tokens[this.tokens.length - (index or 1)] return tok[0]: tag if tag? tok[0] # Look at a value in the current token stream. lex::value: (index, val) -> - return unless tok: this.tokens[this.tokens.length - (index || 1)] + return unless tok: this.tokens[this.tokens.length - (index or 1)] return tok[1]: val if val? tok[1] diff --git a/src/nodes.coffee b/src/nodes.coffee index bae86a4e..4a958250 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -5,7 +5,6 @@ # generated code should be wrapped up in a closure. An options hash is passed # and cloned throughout, containing messages from higher in the AST, # information about the current scope, and indentation level. -exports.Node : -> @values: arguments; @name: this.constructor.name exports.Expressions : -> @name: this.constructor.name; @values: arguments exports.LiteralNode : -> @name: this.constructor.name; @values: arguments @@ -39,176 +38,179 @@ exports.Expressions.wrap : (values) -> @values: values # Some helper functions -# TODO -- shallow (1 deep) flatten.. -# need recursive version.. -flatten: (aggList, newList) -> - for item in newList - aggList.push(item) - aggList +# Tabs are two spaces for pretty printing. +TAB: ' ' +TRAILING_WHITESPACE: /\s+$/g +# Flatten nested arrays recursively. +flatten: (list) -> + memo: [] + for item in list + return memo.concat(flatten(item)) if item instanceof Array + memo.push(item) + memo + +# Remove all null values from an array. compact: (input) -> - compected: [] - for item in input - if item? - compacted.push(item) + item for item in input when item? +# Dup an array or object. dup: (input) -> - output: null if input instanceof Array - output: [] - for val in input - output.push(val) + val for val in input else output: {} - for key, val of input - output.key: val + (output[key]: val) for key, val of input output - output -exports.Node::TAB: ' ' +# Delete a key from an object, returning the value. +del: (obj, key) -> + val: obj[key] + delete obj[key] + val -# Tag this node as a statement, meaning that it can't be used directly as -# the result of an expression. -exports.Node::mark_as_statement: -> - this.is_statement: -> true +# # Provide a quick implementation of a children method. +# children: (klass, attrs...) -> +# klass::children: -> +# nodes: this[attr] for attr in attrs +# compact flatten nodes -# Tag this node as a statement that cannot be transformed into an expression. -# (break, continue, etc.) It doesn't make sense to try to transform it. -exports.Node::mark_as_statement_only: -> - this.mark_as_statement() - this.is_statement_only: -> true +# Mark a node as a statement, or a statement only. +statement: (klass, only) -> + klass::statement: -> true + klass::statement_only: -> true if only -# This node needs to know if it's being compiled as a top-level statement, -# in order to compile without special expression conversion. -exports.Node::mark_as_top_sensitive: -> - this.is_top_sensitive: -> true -# Provide a quick implementation of a children method. -exports.Node::children: (attributes) -> - # TODO -- are these optimal impls of flatten and compact - # .. do better ones exist in a stdlib? - agg: [] - for item in attributes - agg: flatten agg, item - compacted: compact agg - this.children: -> - compacted - -exports.Node::write: (code) -> - # hm.. - # TODO -- should print to STDOUT in "VERBOSE" how to - # go about this.. ? jsonify 'this'? - # use node's puts ?? - code +# The abstract base class for all CoffeeScript nodes. +# All nodes are implement a "compile_node" method, which performs the +# code generation for that node. To compile a node, call the "compile" +# method, which wraps "compile_node" in some extra smarts, to know when the +# generated code should be wrapped up in a closure. An options hash is passed +# and cloned throughout, containing messages from higher in the AST, +# information about the current scope, and indentation level. +Node: exports.Node: -> # This is extremely important -- we convert JS statements into expressions # by wrapping them in a closure, only if it's possible, and we're not at # the top level of a block (which would be unnecessary), and we haven't # already been asked to return the result. -exports.Node::compile: (o) -> - # TODO -- need JS dup/clone - opts: if not o? then {} else o - this.options: opts - this.indent: opts.indent - top: this.options.top - if not this.is_top_sentitive() - this.options.top: undefined - closure: this.is_statement() and not this.is_statement_only() and not top and typeof(this) == "CommentNode" - closure &&= not this.do_i_contain (n) -> n.is_statement_only() - if closure then this.compile_closure(this.options) else compile_node(this.options) +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 + not o.returns and not this instanceof CommentNode and + not @contains (node) -> node.statement_only() + if closure then @compile_closure(@options) else @compile_node(@options) # Statements converted into expressions share scope with their parent # closure, to preserve JavaScript-style lexical scope. -exports.Node::compile_closure: (o) -> - opts: if not o? then {} else o - this.indent: opts.indent - opts.shared_scope: o.scope - exports.ClosureNode.wrap(this).compile(opts) +Node::compile_closure: (o) -> + @indent: o.indent + o.shared_scope: o.scope + ClosureNode.wrap(this).compile(o) # Quick short method for the current indentation level, plus tabbing in. -exports.Node::idt: (tLvl) -> - tabs: if tLvl? then tLvl else 0 - tabAmt: '' - for x in [0...tabs] - tabAmt: tabAmt + this.TAB - this.indent + tabAmt +Node::idt: (tabs) -> + idt: @indent + idt += TAB for i in [0..(tabs or 0)] + idt -#Does this node, or any of it's children, contain a node of a certain kind? -exports.Node::do_i_contain: (block) -> - for node in this.children +# Does this node, or any of its children, contain a node of a certain kind? +Node::contains: (block) -> + for node in @children return true if block(node) - return true if node instanceof exports.Node and node.do_i_contain(block) + return true if node instanceof Node and node.contains block false # Default implementations of the common node methods. -exports.Node::unwrap: -> this -exports.Node::children: [] -exports.Node::is_a_statement: -> false -exports.Node::is_a_statement_only: -> false -exports.Node::is_top_sensitive: -> false +Node::unwrap: -> this +Node::children: [] +Node::statement: -> false +Node::statement_only: -> false +Node::top_sensitive: -> false + # A collection of nodes, each one representing an expression. -# exports.Expressions: (nodes) -> -# this.mark_as_statement() -# this.expressions: [] -# this.children([this.expressions]) -# for n in nodes -# this.expressions: flatten this.expressions, n -# exports.Expressions extends exports.Node +Expressions: exports.Expressions: (nodes...) -> + @expressions: flatten nodes + @children: @expressions -exports.Expressions::TRAILING_WHITESPACE: /\s+$/ +Expressions extends Node +statement Expressions # Wrap up a node as an Expressions, unless it already is. -exports.Expressions::wrap: (nodes) -> - return nodes[0] if nodes.length == 1 and nodes[0] instanceof exports.Expressions - new Expressions(nodes) +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. -exports.Expressions::push: (node) -> - this.expressions.push(node) +Expressions::push: (node) -> + @expressions.push(node) this # Tack an expression on to the beginning of this expression list. -exports.Expressions::unshift: (node) -> - this.expressions.unshift(node) +Expressions::unshift: (node) -> + @expressions.unshift(node) this # If this Expressions consists of a single node, pull it back out. -exports.Expressions::unwrap: -> - if this.expressions.length == 1 then this.expressions[0] else this +Expressions::unwrap: -> + if @expressions.length is 1 then @expressions[0] else this # Is this an empty block of code? -exports.Expressions::is_empty: -> - this.expressions.length == 0 +Expressions::empty: -> + @expressions.length is 0 -# Is the node last in this block of expressions. -exports.Expressions::is_last: (node) -> - arr_length: this.expressions.length - this.last_index ||= if this.expressions[arr_length - 1] instanceof exports.CommentNode then -2 else -1 - node == this.expressions[arr_length - this.last_index] +# 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] -exports.Expressions::compile: (o) -> - opts: if o? then o else {} - if opts.scope then super(dup(opts)) else this.compile_root(o) +Expressions::compile: (o) -> + if o.scope then super(o) else @compile_root(o) # Compile each expression in the Expressions body. -exports.Expressions::compile_node: (options) -> - opts: if options? then options else {} - compiled: [] - for e in this.expressions - compiled.push(this.compile_expression(e, dup(options))) - code: '' - for line in compiled - code: code + line + '\n' +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. -exports.Expressions::compile_root: (o) -> - opts: if o? then o else {} - indent: if opts.no_wrap then '' else this.TAB - this.indent: indent - opts.indent: indent - opts.scope: new Scope(null, this, null) - code: if opts.globals then compile_node(opts) else compile_with_declarations(opts) - code.replace(this.TRAILING_WHITESPACE, '') - this.write(if opts.no_wrap then code else "(function(){\n"+code+"\n})();") +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})();" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/parser.coffee b/src/parser.coffee index 88cdd251..6e4ae3e6 100644 --- a/src/parser.coffee +++ b/src/parser.coffee @@ -230,206 +230,206 @@ grammar: { o "Object", -> new ValueNode($1) o "Parenthetical", -> new ValueNode($1) o "Range", -> new ValueNode($1) - # o "Value Accessor", -> $1.push($2) + o "Value Accessor", -> $1.push($2) o "Invocation Accessor", -> new ValueNode($1, [$2]) ] - # # Accessing into an object or array, through dot or index notation. - # Accessor: [ - # o "PROPERTY_ACCESS IDENTIFIER", -> new AccessorNode($2) - # o "PROTOTYPE_ACCESS IDENTIFIER", -> new AccessorNode($2, 'prototype') - # o "SOAK_ACCESS IDENTIFIER", -> new AccessorNode($2, 'soak') - # o "Index" - # o "Slice", -> new SliceNode($1) - # ] - # - # # Indexing into an object or array. - # Index: [ - # o "INDEX_START Expression INDEX_END", -> new IndexNode($2) - # ] - # - # # An object literal. - # Object: [ - # o "{ AssignList }", -> new ObjectNode($2) - # ] - # - # # Assignment within an object literal (comma or newline separated). - # AssignList: [ - # o "", -> [] - # o "AssignObj", -> [$1] - # o "AssignList , AssignObj", -> $1.push $3 - # o "AssignList TERMINATOR AssignObj", -> $1.push $3 - # o "AssignList , TERMINATOR AssignObj", -> $1.push $4 - # o "INDENT AssignList OUTDENT", -> $2 - # ] - # - # # All flavors of function call (instantiation, super, and regular). - # Call: [ - # o "Invocation", -> $1 - # o "NEW Invocation", -> $2.new_instance() - # o "Super", -> $1 - # ] - # - # # Extending an object's prototype. - # Extends: [ - # o "Value EXTENDS Value", -> new ExtendsNode($1, $3) - # ] - # - # # A generic function invocation. - # Invocation: [ - # o "Value Arguments", -> new CallNode($1, $2) - # o "Invocation Arguments", -> new CallNode($1, $2) - # ] - # - # # The list of arguments to a function invocation. - # Arguments: [ - # o "CALL_START ArgList CALL_END", -> $2 - # ] - # - # # Calling super. - # Super: [ - # o "SUPER CALL_START ArgList CALL_END", -> new CallNode('super', $3) - # ] - # - # # The range literal. - # Range: [ - # o "[ Expression . . Expression ]", -> new RangeNode($2, $5) - # o "[ Expression . . . Expression ]", -> new RangeNode($2, $6, true) - # ] - # - # # The slice literal. - # Slice: [ - # o "INDEX_START Expression . . Expression INDEX_END", -> new RangeNode($2, $5) - # o "INDEX_START Expression . . . Expression INDEX_END", -> new RangeNode($2, $6, true) - # ] - # - # # The array literal. - # Array: [ - # o "[ ArgList ]", -> new ArrayNode($2) - # ] - # - # # A list of arguments to a method call, or as the contents of an array. - # ArgList: [ - # o "", -> [] - # o "Expression", -> val - # o "INDENT Expression", -> [$2] - # o "ArgList , Expression", -> $1.push $3 - # o "ArgList TERMINATOR Expression", -> $1.push $3 - # o "ArgList , TERMINATOR Expression", -> $1.push $4 - # o "ArgList , INDENT Expression", -> $1.push $4 - # o "ArgList OUTDENT", -> $1 - # ] - # - # # Just simple, comma-separated, required arguments (no fancy syntax). - # SimpleArgs: [ - # o "Expression", -> $1 - # o "SimpleArgs , Expression", -> - # ([$1].push($3)).reduce (a, b) -> a.concat(b) - # ] - # - # # Try/catch/finally exception handling blocks. - # Try: [ - # o "TRY Block Catch", -> new TryNode($2, $3[0], $3[1]) - # o "TRY Block FINALLY Block", -> new TryNode($2, nil, nil, $4) - # o "TRY Block Catch FINALLY Block", -> new TryNode($2, $3[0], $3[1], $5) - # ] - # - # # A catch clause. - # Catch: [ - # o "CATCH IDENTIFIER Block", -> [$2, $3] - # ] - # - # # Throw an exception. - # Throw: [ - # o "THROW Expression", -> new ThrowNode($2) - # ] - # - # # Parenthetical expressions. - # Parenthetical: [ - # o "( Expression )", -> new ParentheticalNode($2) - # ] - # - # # The while loop. (there is no do..while). - # While: [ - # o "WHILE Expression Block", -> new WhileNode($2, $3) - # o "WHILE Expression", -> new WhileNode($2, nil) - # o "Expression WHILE Expression", -> new WhileNode($3, Expressions.wrap($1)) - # ] - # - # # Array comprehensions, including guard and current index. - # # Looks a little confusing, check nodes.rb for the arguments to ForNode. - # For: [ - # o "Expression FOR ForVariables ForSource", -> new ForNode($1, $4, $3[0], $3[1]) - # o "FOR ForVariables ForSource Block", -> new ForNode($4, $3, $2[0], $2[1]) - # ] - # - # # An array comprehension has variables for the current element and index. - # ForVariables: [ - # o "IDENTIFIER", -> [$1] - # o "IDENTIFIER , IDENTIFIER", -> [$1, $3] - # ] - # - # # The source of the array comprehension can optionally be filtered. - # ForSource: [ - # o "IN Expression", -> {source: $2} - # o "OF Expression", -> {source: $2, object: true} - # o "ForSource WHEN Expression", -> $1.filter: $3; $1 - # o "ForSource BY Expression", -> $1.step: $3; $1 - # ] - # - # # Switch/When blocks. - # Switch: [ - # o "SWITCH Expression INDENT Whens OUTDENT", -> $4.rewrite_condition($2) - # o "SWITCH Expression INDENT Whens ELSE Block OUTDENT", -> $4.rewrite_condition($2).add_else($6) - # ] - # - # # The inner list of whens. - # Whens: [ - # o "When", -> $1 - # o "Whens When", -> $1.push $2 - # ] - # - # # An individual when. - # When: [ - # o "LEADING_WHEN SimpleArgs Block", -> new IfNode($2, $3, nil, {statement: true}) - # o "LEADING_WHEN SimpleArgs Block TERMINATOR", -> new IfNode($2, $3, nil, {statement: true}) - # o "Comment TERMINATOR When", -> $3.add_comment($1) - # ] - # - # # The most basic form of "if". - # IfBlock: [ - # o "IF Expression Block", -> new IfNode($2, $3) - # ] - # - # # An elsif portion of an if-else block. - # ElsIf: [ - # o "ELSE IfBlock", -> $2.force_statement() - # ] - # - # # Multiple elsifs can be chained together. - # ElsIfs: [ - # o "ElsIf", -> $1 - # o "ElsIfs ElsIf", -> $1.add_else($2) - # ] - # - # # Terminating else bodies are strictly optional. - # ElseBody: [ - # o "", -> null - # o "ELSE Block", -> $2 - # ] - # - # # All the alternatives for ending an if-else block. - # IfEnd: [ - # o "ElseBody", -> $1 - # o "ElsIfs ElseBody", -> $1.add_else($2) - # ] - # - # # The full complement of if blocks, including postfix one-liner ifs and unlesses. - # If: [ - # o "IfBlock IfEnd", -> $1.add_else($2) - # o "Expression IF Expression", -> new IfNode($3, Expressions.wrap($1), nil, {statement: true}) - # o "Expression UNLESS Expression", -> new IfNode($3, Expressions.wrap($1), nil, {statement: true, invert: true}) - # ] + # Accessing into an object or array, through dot or index notation. + Accessor: [ + o "PROPERTY_ACCESS IDENTIFIER", -> new AccessorNode(yytext) + o "PROTOTYPE_ACCESS IDENTIFIER", -> new AccessorNode(yytext, 'prototype') + o "SOAK_ACCESS IDENTIFIER", -> new AccessorNode(yytext, 'soak') + o "Index" + o "Slice", -> new SliceNode($1) + ] + + # Indexing into an object or array. + Index: [ + o "INDEX_START Expression INDEX_END", -> new IndexNode($2) + ] + + # An object literal. + Object: [ + o "{ AssignList }", -> new ObjectNode($2) + ] + + # Assignment within an object literal (comma or newline separated). + AssignList: [ + o "", -> [] + o "AssignObj", -> [$1] + o "AssignList , AssignObj", -> $1.push $3 + o "AssignList TERMINATOR AssignObj", -> $1.push $3 + o "AssignList , TERMINATOR AssignObj", -> $1.push $4 + o "INDENT AssignList OUTDENT", -> $2 + ] + + # All flavors of function call (instantiation, super, and regular). + Call: [ + o "Invocation", -> $1 + o "NEW Invocation", -> $2.new_instance() + o "Super", -> $1 + ] + + # Extending an object's prototype. + Extends: [ + o "Value EXTENDS Value", -> new ExtendsNode($1, $3) + ] + + # A generic function invocation. + Invocation: [ + o "Value Arguments", -> new CallNode($1, $2) + o "Invocation Arguments", -> new CallNode($1, $2) + ] + + # The list of arguments to a function invocation. + Arguments: [ + o "CALL_START ArgList CALL_END", -> $2 + ] + + # Calling super. + Super: [ + o "SUPER CALL_START ArgList CALL_END", -> new CallNode('super', $3) + ] + + # The range literal. + Range: [ + o "[ Expression . . Expression ]", -> new RangeNode($2, $5) + o "[ Expression . . . Expression ]", -> new RangeNode($2, $6, true) + ] + + # The slice literal. + Slice: [ + o "INDEX_START Expression . . Expression INDEX_END", -> new RangeNode($2, $5) + o "INDEX_START Expression . . . Expression INDEX_END", -> new RangeNode($2, $6, true) + ] + + # The array literal. + Array: [ + o "[ ArgList ]", -> new ArrayNode($2) + ] + + # A list of arguments to a method call, or as the contents of an array. + ArgList: [ + o "", -> [] + o "Expression", -> val + o "INDENT Expression", -> [$2] + o "ArgList , Expression", -> $1.push $3 + o "ArgList TERMINATOR Expression", -> $1.push $3 + o "ArgList , TERMINATOR Expression", -> $1.push $4 + o "ArgList , INDENT Expression", -> $1.push $4 + o "ArgList OUTDENT", -> $1 + ] + + # Just simple, comma-separated, required arguments (no fancy syntax). + SimpleArgs: [ + o "Expression", -> $1 + o "SimpleArgs , Expression", -> + ([$1].push($3)).reduce (a, b) -> a.concat(b) + ] + + # Try/catch/finally exception handling blocks. + Try: [ + o "TRY Block Catch", -> new TryNode($2, $3[0], $3[1]) + o "TRY Block FINALLY Block", -> new TryNode($2, null, null, $4) + o "TRY Block Catch FINALLY Block", -> new TryNode($2, $3[0], $3[1], $5) + ] + + # A catch clause. + Catch: [ + o "CATCH IDENTIFIER Block", -> [$2, $3] + ] + + # Throw an exception. + Throw: [ + o "THROW Expression", -> new ThrowNode($2) + ] + + # Parenthetical expressions. + Parenthetical: [ + o "( Expression )", -> new ParentheticalNode($2) + ] + + # The while loop. (there is no do..while). + While: [ + o "WHILE Expression Block", -> new WhileNode($2, $3) + o "WHILE Expression", -> new WhileNode($2, null) + o "Expression WHILE Expression", -> new WhileNode($3, Expressions.wrap($1)) + ] + + # Array comprehensions, including guard and current index. + # Looks a little confusing, check nodes.rb for the arguments to ForNode. + For: [ + o "Expression FOR ForVariables ForSource", -> new ForNode($1, $4, $3[0], $3[1]) + o "FOR ForVariables ForSource Block", -> new ForNode($4, $3, $2[0], $2[1]) + ] + + # An array comprehension has variables for the current element and index. + ForVariables: [ + o "IDENTIFIER", -> [$1] + o "IDENTIFIER , IDENTIFIER", -> [$1, $3] + ] + + # The source of the array comprehension can optionally be filtered. + ForSource: [ + o "IN Expression", -> {source: $2} + o "OF Expression", -> {source: $2, object: true} + o "ForSource WHEN Expression", -> $1.filter: $3; $1 + o "ForSource BY Expression", -> $1.step: $3; $1 + ] + + # Switch/When blocks. + Switch: [ + o "SWITCH Expression INDENT Whens OUTDENT", -> $4.rewrite_condition($2) + o "SWITCH Expression INDENT Whens ELSE Block OUTDENT", -> $4.rewrite_condition($2).add_else($6) + ] + + # The inner list of whens. + Whens: [ + o "When", -> $1 + o "Whens When", -> $1.push $2 + ] + + # An individual when. + When: [ + o "LEADING_WHEN SimpleArgs Block", -> new IfNode($2, $3, null, {statement: true}) + o "LEADING_WHEN SimpleArgs Block TERMINATOR", -> new IfNode($2, $3, null, {statement: true}) + o "Comment TERMINATOR When", -> $3.add_comment($1) + ] + + # The most basic form of "if". + IfBlock: [ + o "IF Expression Block", -> new IfNode($2, $3) + ] + + # An elsif portion of an if-else block. + ElsIf: [ + o "ELSE IfBlock", -> $2.force_statement() + ] + + # Multiple elsifs can be chained together. + ElsIfs: [ + o "ElsIf", -> $1 + o "ElsIfs ElsIf", -> $1.add_else($2) + ] + + # Terminating else bodies are strictly optional. + ElseBody: [ + o "", -> null + o "ELSE Block", -> $2 + ] + + # All the alternatives for ending an if-else block. + IfEnd: [ + o "ElseBody", -> $1 + o "ElsIfs ElseBody", -> $1.add_else($2) + ] + + # The full complement of if blocks, including postfix one-liner ifs and unlesses. + If: [ + o "IfBlock IfEnd", -> $1.add_else($2) + o "Expression IF Expression", -> new IfNode($3, Expressions.wrap($1), null, {statement: true}) + o "Expression UNLESS Expression", -> new IfNode($3, Expressions.wrap($1), null, {statement: true, invert: true}) + ] }