determine @children dynamically based on attribute names, instead of manual bookkeeping

This commit is contained in:
gfxmonk
2010-05-10 20:58:01 +10:00
parent ee4e34bf6d
commit eb91f9922d
2 changed files with 257 additions and 140 deletions

View File

@@ -1,15 +1,15 @@
(function(){ (function(){
var AccessorNode, ArrayNode, AssignNode, BaseNode, CallNode, ClassNode, ClosureNode, CodeNode, CommentNode, CurryNode, ExistenceNode, Expressions, ExtendsNode, ForNode, IDENTIFIER, IS_STRING, IfNode, IndexNode, LiteralNode, ObjectNode, OpNode, ParentheticalNode, PushNode, RangeNode, ReturnNode, Scope, SliceNode, SplatNode, TAB, TRAILING_WHITESPACE, ThrowNode, TryNode, UTILITIES, ValueNode, WhileNode, _a, compact, del, flatten, helpers, literal, merge, statement, utility; var AccessorNode, ArrayNode, AssignNode, BaseNode, CallNode, ClassNode, ClosureNode, CodeNode, CommentNode, CurryNode, ExistenceNode, Expressions, ExtendsNode, ForNode, IDENTIFIER, IS_STRING, IfNode, IndexNode, LiteralNode, ObjectNode, OpNode, ParentheticalNode, PushNode, RangeNode, ReturnNode, Scope, SliceNode, SplatNode, TAB, TRAILING_WHITESPACE, ThrowNode, TryNode, UTILITIES, ValueNode, WhileNode, _a, children, compact, del, flatten, helpers, literal, merge, statement, utility;
var __extends = function(child, parent) { var __slice = Array.prototype.slice, __bind = function(func, obj, args) {
return function() {
return func.apply(obj || {}, args ? args.concat(__slice.call(arguments, 0)) : arguments);
};
}, __extends = function(child, parent) {
var ctor = function(){ }; var ctor = function(){ };
ctor.prototype = parent.prototype; ctor.prototype = parent.prototype;
child.__superClass__ = parent.prototype; child.__superClass__ = parent.prototype;
child.prototype = new ctor(); child.prototype = new ctor();
child.prototype.constructor = child; child.prototype.constructor = child;
}, __slice = Array.prototype.slice, __bind = function(func, obj, args) {
return function() {
return func.apply(obj || {}, args ? args.concat(__slice.call(arguments, 0)) : arguments);
};
}; };
// `nodes.coffee` contains all of the node classes for the syntax tree. Most // `nodes.coffee` contains all of the node classes for the syntax tree. Most
// nodes are created as the result of actions in the [grammar](grammar.html), // nodes are created as the result of actions in the [grammar](grammar.html),
@@ -46,6 +46,12 @@
return klass.prototype.is_pure_statement; return klass.prototype.is_pure_statement;
} }
}; };
children = function children(klass) {
var child_attrs;
child_attrs = __slice.call(arguments, 1, arguments.length - 0);
klass.prototype._children_attributes = child_attrs;
return klass.prototype._children_attributes;
};
//### BaseNode //### BaseNode
// The **BaseNode** is the abstract base class for all nodes in the syntax tree. // The **BaseNode** is the abstract base class for all nodes in the syntax tree.
// Each subclass implements the `compile_node` method, which performs the // Each subclass implements the `compile_node` method, which performs the
@@ -119,18 +125,15 @@
// and returning true when the block finds a match. `contains` does not cross // and returning true when the block finds a match. `contains` does not cross
// scope boundaries. // scope boundaries.
BaseNode.prototype.contains = function contains(block) { BaseNode.prototype.contains = function contains(block) {
var _b, _c, _d, node; var contains;
_c = this.children; contains = false;
for (_b = 0, _d = _c.length; _b < _d; _b++) { this._traverse_children(false, __bind(function(node, attr, index) {
node = _c[_b]; if (block(node)) {
if (block(node)) { contains = true;
return true; return false;
} }
if (node.contains && node.contains(block)) { }, this));
return true; return contains;
}
}
return false;
}; };
// Is this node of a certain type, or does it contain the type? // Is this node of a certain type, or does it contain the type?
BaseNode.prototype.contains_type = function contains_type(type) { BaseNode.prototype.contains_type = function contains_type(type) {
@@ -147,18 +150,7 @@
}; };
// Perform an in-order traversal of the AST. Crosses scope boundaries. // Perform an in-order traversal of the AST. Crosses scope boundaries.
BaseNode.prototype.traverse = function traverse(block) { BaseNode.prototype.traverse = function traverse(block) {
var _b, _c, _d, _e, node; return this._traverse_children(true, block);
_b = []; _d = this.children;
for (_c = 0, _e = _d.length; _c < _e; _c++) {
node = _d[_c];
_b.push((function() {
block(node);
if (node.traverse) {
return node.traverse(block);
}
})());
}
return _b;
}; };
// `toString` representation of the node, for inspecting the parse tree. // `toString` representation of the node, for inspecting the parse tree.
// This is what `coffee --nodes` prints out. // This is what `coffee --nodes` prints out.
@@ -166,7 +158,7 @@
var _b, _c, _d, _e, child; var _b, _c, _d, _e, child;
idt = idt || ''; idt = idt || '';
return '\n' + idt + this.constructor.name + (function() { return '\n' + idt + this.constructor.name + (function() {
_b = []; _d = this.children; _b = []; _d = this.children();
for (_c = 0, _e = _d.length; _c < _e; _c++) { for (_c = 0, _e = _d.length; _c < _e; _c++) {
child = _d[_c]; child = _d[_c];
_b.push(child.toString(idt + TAB)); _b.push(child.toString(idt + TAB));
@@ -174,12 +166,55 @@
return _b; return _b;
}).call(this).join(''); }).call(this).join('');
}; };
BaseNode.prototype.children = function children() {
var _children;
_children = [];
this._each_child(function(child) {
return _children.push(child);
});
return _children;
};
BaseNode.prototype._each_child = function _each_child(func) {
var _b, _c, _d, _e, _f, _g, attr, child, child_collection, i, result;
_c = this._children_attributes;
for (_b = 0, _d = _c.length; _b < _d; _b++) {
attr = _c[_b];
child_collection = this[attr];
if (!((typeof child_collection !== "undefined" && child_collection !== null))) {
continue;
}
if (child_collection instanceof Array) {
i = 0;
_f = child_collection;
for (_e = 0, _g = _f.length; _e < _g; _e++) {
child = _f[_e];
result = func(child, attr, i);
if (result === false) {
return null;
}
i += 1;
}
} else {
func(child_collection, attr);
}
}
};
BaseNode.prototype._traverse_children = function _traverse_children(cross_scope, func) {
if (!(this._children_attributes)) {
return null;
}
return this._each_child(function(child, attr, i) {
func.apply(this, arguments);
if (child instanceof BaseNode) {
return child._traverse_children(cross_scope, func);
}
});
};
// Default implementations of the common node identification methods. Nodes // Default implementations of the common node identification methods. Nodes
// will override these with custom logic, if needed. // will override these with custom logic, if needed.
BaseNode.prototype.unwrap = function unwrap() { BaseNode.prototype.unwrap = function unwrap() {
return this; return this;
}; };
BaseNode.prototype.children = [];
BaseNode.prototype.is_statement = function is_statement() { BaseNode.prototype.is_statement = function is_statement() {
return false; return false;
}; };
@@ -197,7 +232,7 @@
// `if`, `switch`, or `try`, and so on... // `if`, `switch`, or `try`, and so on...
exports.Expressions = (function() { exports.Expressions = (function() {
Expressions = function Expressions(nodes) { Expressions = function Expressions(nodes) {
this.children = (this.expressions = compact(flatten(nodes || []))); this.expressions = compact(flatten(nodes || []));
return this; return this;
}; };
__extends(Expressions, BaseNode); __extends(Expressions, BaseNode);
@@ -226,7 +261,7 @@
}; };
// Make a copy of this node. // Make a copy of this node.
Expressions.prototype.copy = function copy() { Expressions.prototype.copy = function copy() {
return new Expressions(this.children.slice()); return new Expressions(this.Expressions.slice());
}; };
// An Expressions node does not return its entire body, rather it // An Expressions node does not return its entire body, rather it
// ensures that the final expression is returned. // ensures that the final expression is returned.
@@ -317,6 +352,7 @@
} }
return new Expressions(nodes); return new Expressions(nodes);
}; };
children(Expressions, 'expressions');
statement(Expressions); statement(Expressions);
//### LiteralNode //### LiteralNode
// Literals are static values that can be passed through directly into // Literals are static values that can be passed through directly into
@@ -350,7 +386,7 @@
// make sense. // make sense.
exports.ReturnNode = (function() { exports.ReturnNode = (function() {
ReturnNode = function ReturnNode(expression) { ReturnNode = function ReturnNode(expression) {
this.children = [(this.expression = expression)]; this.expression = expression;
return this; return this;
}; };
__extends(ReturnNode, BaseNode); __extends(ReturnNode, BaseNode);
@@ -372,12 +408,14 @@
return ReturnNode; return ReturnNode;
})(); })();
statement(ReturnNode, true); statement(ReturnNode, true);
children(ReturnNode, 'expression');
//### ValueNode //### ValueNode
// A value, variable or literal or parenthesized, indexed or dotted into, // A value, variable or literal or parenthesized, indexed or dotted into,
// or vanilla. // or vanilla.
exports.ValueNode = (function() { exports.ValueNode = (function() {
ValueNode = function ValueNode(base, properties) { ValueNode = function ValueNode(base, properties) {
this.children = flatten([(this.base = base), (this.properties = (properties || []))]); this.base = base;
this.properties = (properties || []);
return this; return this;
}; };
__extends(ValueNode, BaseNode); __extends(ValueNode, BaseNode);
@@ -386,7 +424,6 @@
// Add a property access to the list. // Add a property access to the list.
ValueNode.prototype.push = function push(prop) { ValueNode.prototype.push = function push(prop) {
this.properties.push(prop); this.properties.push(prop);
this.children.push(prop);
return this; return this;
}; };
ValueNode.prototype.has_properties = function has_properties() { ValueNode.prototype.has_properties = function has_properties() {
@@ -464,6 +501,7 @@
}; };
return ValueNode; return ValueNode;
})(); })();
children(ValueNode, 'base', 'properties');
//### CommentNode //### CommentNode
// CoffeeScript passes through comments as JavaScript comments at the // CoffeeScript passes through comments as JavaScript comments at the
// same position. // same position.
@@ -491,7 +529,7 @@
this.is_new = false; this.is_new = false;
this.is_super = variable === 'super'; this.is_super = variable === 'super';
this.variable = this.is_super ? null : variable; this.variable = this.is_super ? null : variable;
this.children = compact(flatten([this.variable, (this.args = (args || []))])); this.args = (args || []);
this.compile_splat_arguments = __bind(SplatNode.compile_mixed_array, this, [this.args]); this.compile_splat_arguments = __bind(SplatNode.compile_mixed_array, this, [this.args]);
return this; return this;
}; };
@@ -558,13 +596,16 @@
}; };
return CallNode; return CallNode;
})(); })();
children(CallNode, 'variable', 'args');
//### CurryNode //### CurryNode
// Binds a context object and a list of arguments to a function, // Binds a context object and a list of arguments to a function,
// returning the bound function. After ECMAScript 5, Prototype.js, and // returning the bound function. After ECMAScript 5, Prototype.js, and
// Underscore's `bind` functions. // Underscore's `bind` functions.
exports.CurryNode = (function() { exports.CurryNode = (function() {
CurryNode = function CurryNode(meth, args) { CurryNode = function CurryNode(meth, args) {
this.children = flatten([(this.meth = meth), (this.context = args[0]), (this.args = (args.slice(1) || []))]); this.meth = meth;
this.context = args[0];
this.args = (args.slice(1) || []);
this.compile_splat_arguments = __bind(SplatNode.compile_mixed_array, this, [this.args]); this.compile_splat_arguments = __bind(SplatNode.compile_mixed_array, this, [this.args]);
return this; return this;
}; };
@@ -588,13 +629,15 @@
}; };
return CurryNode; return CurryNode;
}).apply(this, arguments); }).apply(this, arguments);
children(CurryNode, 'meth', 'context', 'args');
//### ExtendsNode //### ExtendsNode
// Node to extend an object's prototype with an ancestor object. // Node to extend an object's prototype with an ancestor object.
// After `goog.inherits` from the // After `goog.inherits` from the
// [Closure Library](http://closure-library.googlecode.com/svn/docs/closure_goog_base.js.html). // [Closure Library](http://closure-library.googlecode.com/svn/docs/closure_goog_base.js.html).
exports.ExtendsNode = (function() { exports.ExtendsNode = (function() {
ExtendsNode = function ExtendsNode(child, parent) { ExtendsNode = function ExtendsNode(child, parent) {
this.children = [(this.child = child), (this.parent = parent)]; this.child = child;
this.parent = parent;
return this; return this;
}; };
__extends(ExtendsNode, BaseNode); __extends(ExtendsNode, BaseNode);
@@ -606,12 +649,13 @@
}; };
return ExtendsNode; return ExtendsNode;
})(); })();
children(ExtendsNode, 'child', 'parent');
//### AccessorNode //### AccessorNode
// A `.` accessor into a property of a value, or the `::` shorthand for // A `.` accessor into a property of a value, or the `::` shorthand for
// an accessor into the object's prototype. // an accessor into the object's prototype.
exports.AccessorNode = (function() { exports.AccessorNode = (function() {
AccessorNode = function AccessorNode(name, tag) { AccessorNode = function AccessorNode(name, tag) {
this.children = [(this.name = name)]; this.name = name;
this.prototype = tag === 'prototype'; this.prototype = tag === 'prototype';
this.soak_node = tag === 'soak'; this.soak_node = tag === 'soak';
this; this;
@@ -625,11 +669,12 @@
}; };
return AccessorNode; return AccessorNode;
})(); })();
children(AccessorNode, 'name');
//### IndexNode //### IndexNode
// A `[ ... ]` indexed accessor into an array or object. // A `[ ... ]` indexed accessor into an array or object.
exports.IndexNode = (function() { exports.IndexNode = (function() {
IndexNode = function IndexNode(index, tag) { IndexNode = function IndexNode(index, tag) {
this.children = [(this.index = index)]; this.index = index;
this.soak_node = tag === 'soak'; this.soak_node = tag === 'soak';
return this; return this;
}; };
@@ -641,13 +686,15 @@
}; };
return IndexNode; return IndexNode;
})(); })();
children(IndexNode, 'index');
//### RangeNode //### RangeNode
// A range literal. Ranges can be used to extract portions (slices) of arrays, // A range literal. Ranges can be used to extract portions (slices) of arrays,
// to specify a range for comprehensions, or as a value, to be expanded into the // to specify a range for comprehensions, or as a value, to be expanded into the
// corresponding array of integers at runtime. // corresponding array of integers at runtime.
exports.RangeNode = (function() { exports.RangeNode = (function() {
RangeNode = function RangeNode(from, to, exclusive) { RangeNode = function RangeNode(from, to, exclusive) {
this.children = [(this.from = from), (this.to = to)]; this.from = from;
this.to = to;
this.exclusive = !!exclusive; this.exclusive = !!exclusive;
return this; return this;
}; };
@@ -697,13 +744,14 @@
}; };
return RangeNode; return RangeNode;
})(); })();
children(RangeNode, 'from', 'to');
//### SliceNode //### SliceNode
// An array slice literal. Unlike JavaScript's `Array#slice`, the second parameter // An array slice literal. Unlike JavaScript's `Array#slice`, the second parameter
// specifies the index of the end of the slice, just as the first parameter // specifies the index of the end of the slice, just as the first parameter
// is the index of the beginning. // is the index of the beginning.
exports.SliceNode = (function() { exports.SliceNode = (function() {
SliceNode = function SliceNode(range) { SliceNode = function SliceNode(range) {
this.children = [(this.range = range)]; this.range = range;
this; this;
return this; return this;
}; };
@@ -717,11 +765,12 @@
}; };
return SliceNode; return SliceNode;
})(); })();
children(SliceNode, 'range');
//### ObjectNode //### ObjectNode
// An object literal, nothing fancy. // An object literal, nothing fancy.
exports.ObjectNode = (function() { exports.ObjectNode = (function() {
ObjectNode = function ObjectNode(props) { ObjectNode = function ObjectNode(props) {
this.children = (this.objects = (this.properties = props || [])); this.objects = (this.properties = props || []);
return this; return this;
}; };
__extends(ObjectNode, BaseNode); __extends(ObjectNode, BaseNode);
@@ -767,11 +816,12 @@
}; };
return ObjectNode; return ObjectNode;
})(); })();
children(ObjectNode, 'properties');
//### ArrayNode //### ArrayNode
// An array literal. // An array literal.
exports.ArrayNode = (function() { exports.ArrayNode = (function() {
ArrayNode = function ArrayNode(objects) { ArrayNode = function ArrayNode(objects) {
this.children = (this.objects = objects || []); this.objects = objects || [];
this.compile_splat_literal = __bind(SplatNode.compile_mixed_array, this, [this.objects]); this.compile_splat_literal = __bind(SplatNode.compile_mixed_array, this, [this.objects]);
return this; return this;
}; };
@@ -803,11 +853,14 @@
}; };
return ArrayNode; return ArrayNode;
})(); })();
children(ArrayNode, 'objects');
//### ClassNode //### ClassNode
// The CoffeeScript class definition. // The CoffeeScript class definition.
exports.ClassNode = (function() { exports.ClassNode = (function() {
ClassNode = function ClassNode(variable, parent, props) { ClassNode = function ClassNode(variable, parent, props) {
this.children = compact(flatten([(this.variable = variable), (this.parent = parent), (this.properties = props || [])])); this.variable = variable;
this.parent = parent;
this.properties = props || [];
this.returns = false; this.returns = false;
return this; return this;
}; };
@@ -862,12 +915,14 @@
return ClassNode; return ClassNode;
})(); })();
statement(ClassNode); statement(ClassNode);
children(ClassNode, 'variable', 'parent', 'properties');
//### AssignNode //### AssignNode
// The **AssignNode** is used to assign a local variable to value, or to set the // The **AssignNode** is used to assign a local variable to value, or to set the
// property of an object -- including within object literals. // property of an object -- including within object literals.
exports.AssignNode = (function() { exports.AssignNode = (function() {
AssignNode = function AssignNode(variable, value, context) { AssignNode = function AssignNode(variable, value, context) {
this.children = [(this.variable = variable), (this.value = value)]; this.variable = variable;
this.value = value;
this.context = context; this.context = context;
return this; return this;
}; };
@@ -994,6 +1049,7 @@
}; };
return AssignNode; return AssignNode;
})(); })();
children(AssignNode, 'variable', 'value');
//### CodeNode //### CodeNode
// A function definition. This is the only node that creates a new Scope. // A function definition. This is the only node that creates a new Scope.
// When for the purposes of walking the contents of a function body, the CodeNode // When for the purposes of walking the contents of a function body, the CodeNode
@@ -1068,27 +1124,18 @@
CodeNode.prototype.top_sensitive = function top_sensitive() { CodeNode.prototype.top_sensitive = function top_sensitive() {
return true; return true;
}; };
// When traversing (for printing or inspecting), return the real children of // Short-circuit _traverse_children method to prevent it from crossing scope boundaries
// the function -- the parameters and body of expressions. // unless cross_scope is true
CodeNode.prototype.real_children = function real_children() { CodeNode.prototype._traverse_children = function _traverse_children(cross_scope, func) {
return flatten([this.params, this.body.expressions]); if (cross_scope) {
}; return CodeNode.__superClass__._traverse_children.call(this, cross_scope, func);
// Custom `traverse` implementation that uses the `real_children`.
CodeNode.prototype.traverse = function traverse(block) {
var _b, _c, _d, _e, child;
block(this);
_b = []; _d = this.real_children();
for (_c = 0, _e = _d.length; _c < _e; _c++) {
child = _d[_c];
_b.push(child.traverse(block));
} }
return _b;
}; };
CodeNode.prototype.toString = function toString(idt) { CodeNode.prototype.toString = function toString(idt) {
var _b, _c, _d, _e, child, children; var _b, _c, _d, _e, child;
idt = idt || ''; idt = idt || '';
children = (function() { children = (function() {
_b = []; _d = this.real_children(); _b = []; _d = this.children();
for (_c = 0, _e = _d.length; _c < _e; _c++) { for (_c = 0, _e = _d.length; _c < _e; _c++) {
child = _d[_c]; child = _d[_c];
_b.push(child.toString(idt + TAB)); _b.push(child.toString(idt + TAB));
@@ -1099,6 +1146,7 @@
}; };
return CodeNode; return CodeNode;
})(); })();
children(CodeNode, 'params', 'body');
//### SplatNode //### SplatNode
// A splat, either as a parameter to a function, an argument to a call, // A splat, either as a parameter to a function, an argument to a call,
// or as part of a destructuring assignment. // or as part of a destructuring assignment.
@@ -1107,7 +1155,7 @@
if (!(name.compile)) { if (!(name.compile)) {
name = literal(name); name = literal(name);
} }
this.children = [(this.name = name)]; this.name = name;
return this; return this;
}; };
__extends(SplatNode, BaseNode); __extends(SplatNode, BaseNode);
@@ -1170,6 +1218,7 @@
}; };
return SplatNode; return SplatNode;
}).call(this); }).call(this);
children(SplatNode, 'name');
//### WhileNode //### WhileNode
// A while loop, the only sort of low-level loop exposed by CoffeeScript. From // A while loop, the only sort of low-level loop exposed by CoffeeScript. From
// it, all other loops can be manufactured. Useful in cases where you need more // it, all other loops can be manufactured. Useful in cases where you need more
@@ -1179,13 +1228,13 @@
if (opts && opts.invert) { if (opts && opts.invert) {
condition = new OpNode('!', condition); condition = new OpNode('!', condition);
} }
this.children = [(this.condition = condition)]; this.condition = condition;
this.guard = opts && opts.guard; this.guard = opts && opts.guard;
return this; return this;
}; };
__extends(WhileNode, BaseNode); __extends(WhileNode, BaseNode);
WhileNode.prototype.add_body = function add_body(body) { WhileNode.prototype.add_body = function add_body(body) {
this.children.push((this.body = body)); this.body = body;
return this; return this;
}; };
WhileNode.prototype.make_return = function make_return() { WhileNode.prototype.make_return = function make_return() {
@@ -1227,13 +1276,15 @@
return WhileNode; return WhileNode;
})(); })();
statement(WhileNode); statement(WhileNode);
children(WhileNode, 'condition', 'guard', 'body');
//### OpNode //### OpNode
// Simple Arithmetic and logical operations. Performs some conversion from // Simple Arithmetic and logical operations. Performs some conversion from
// CoffeeScript operations into their JavaScript equivalents. // CoffeeScript operations into their JavaScript equivalents.
exports.OpNode = (function() { exports.OpNode = (function() {
OpNode = function OpNode(operator, first, second, flip) { OpNode = function OpNode(operator, first, second, flip) {
this.constructor.name += ' ' + operator; this.constructor.name += ' ' + operator;
this.children = compact([(this.first = first), (this.second = second)]); this.first = first;
this.second = second;
this.operator = this.CONVERSIONS[operator] || operator; this.operator = this.CONVERSIONS[operator] || operator;
this.flip = !!flip; this.flip = !!flip;
return this; return this;
@@ -1329,11 +1380,14 @@
}; };
return OpNode; return OpNode;
})(); })();
children(OpNode, 'first', 'second');
//### TryNode //### TryNode
// A classic *try/catch/finally* block. // A classic *try/catch/finally* block.
exports.TryNode = (function() { exports.TryNode = (function() {
TryNode = function TryNode(attempt, error, recovery, ensure) { TryNode = function TryNode(attempt, error, recovery, ensure) {
this.children = compact([(this.attempt = attempt), (this.recovery = recovery), (this.ensure = ensure)]); this.attempt = attempt;
this.recovery = recovery;
this.ensure = ensure;
this.error = error; this.error = error;
this; this;
return this; return this;
@@ -1363,11 +1417,12 @@
return TryNode; return TryNode;
})(); })();
statement(TryNode); statement(TryNode);
children(TryNode, 'attempt', 'recovery', 'ensure');
//### ThrowNode //### ThrowNode
// Simple node to throw an exception. // Simple node to throw an exception.
exports.ThrowNode = (function() { exports.ThrowNode = (function() {
ThrowNode = function ThrowNode(expression) { ThrowNode = function ThrowNode(expression) {
this.children = [(this.expression = expression)]; this.expression = expression;
return this; return this;
}; };
__extends(ThrowNode, BaseNode); __extends(ThrowNode, BaseNode);
@@ -1381,13 +1436,14 @@
return ThrowNode; return ThrowNode;
})(); })();
statement(ThrowNode); statement(ThrowNode);
children(ThrowNode, 'expression');
//### ExistenceNode //### ExistenceNode
// Checks a variable for existence -- not *null* and not *undefined*. This is // Checks a variable for existence -- not *null* and not *undefined*. This is
// similar to `.nil?` in Ruby, and avoids having to consult a JavaScript truth // similar to `.nil?` in Ruby, and avoids having to consult a JavaScript truth
// table. // table.
exports.ExistenceNode = (function() { exports.ExistenceNode = (function() {
ExistenceNode = function ExistenceNode(expression) { ExistenceNode = function ExistenceNode(expression) {
this.children = [(this.expression = expression)]; this.expression = expression;
return this; return this;
}; };
__extends(ExistenceNode, BaseNode); __extends(ExistenceNode, BaseNode);
@@ -1414,6 +1470,7 @@
}; };
return ExistenceNode; return ExistenceNode;
}).call(this); }).call(this);
children(ExistenceNode, 'expression');
//### ParentheticalNode //### ParentheticalNode
// An extra set of parentheses, specified explicitly in the source. At one time // An extra set of parentheses, specified explicitly in the source. At one time
// we tried to clean up the results by detecting and removing redundant // we tried to clean up the results by detecting and removing redundant
@@ -1421,7 +1478,7 @@
// Parentheses are a good way to force any statement to become an expression. // Parentheses are a good way to force any statement to become an expression.
exports.ParentheticalNode = (function() { exports.ParentheticalNode = (function() {
ParentheticalNode = function ParentheticalNode(expression) { ParentheticalNode = function ParentheticalNode(expression) {
this.children = [(this.expression = expression)]; this.expression = expression;
return this; return this;
}; };
__extends(ParentheticalNode, BaseNode); __extends(ParentheticalNode, BaseNode);
@@ -1449,6 +1506,7 @@
}; };
return ParentheticalNode; return ParentheticalNode;
})(); })();
children(ParentheticalNode, 'expression');
//### ForNode //### ForNode
// CoffeeScript's replacement for the *for* loop is our array and object // CoffeeScript's replacement for the *for* loop is our array and object
// comprehensions, that compile into *for* loops here. They also act as an // comprehensions, that compile into *for* loops here. They also act as an
@@ -1475,7 +1533,6 @@
if (this.index instanceof ValueNode) { if (this.index instanceof ValueNode) {
throw new Error('index cannot be a pattern matching expression'); throw new Error('index cannot be a pattern matching expression');
} }
this.children = compact([this.body, this.source, this.guard]);
this.returns = false; this.returns = false;
return this; return this;
}; };
@@ -1566,6 +1623,7 @@
return ForNode; return ForNode;
})(); })();
statement(ForNode); statement(ForNode);
children(ForNode, 'body', 'source', 'guard');
//### IfNode //### IfNode
// *If/else* statements. Our *switch/when* will be compiled into this. Acts as an // *If/else* statements. Our *switch/when* will be compiled into this. Acts as an
// expression by pushing down requested returns to the last line of each clause. // expression by pushing down requested returns to the last line of each clause.
@@ -1576,7 +1634,6 @@
this.condition = condition; this.condition = condition;
this.body = body; this.body = body;
this.else_body = null; this.else_body = null;
this.populate_children();
this.tags = tags || {}; this.tags = tags || {};
if (this.condition instanceof Array) { if (this.condition instanceof Array) {
this.multiple = true; this.multiple = true;
@@ -1588,10 +1645,6 @@
return this; return this;
}; };
__extends(IfNode, BaseNode); __extends(IfNode, BaseNode);
IfNode.prototype.populate_children = function populate_children() {
this.children = compact(flatten([this.condition, this.body, this.else_body]));
return this.children;
};
IfNode.prototype.body_node = function body_node() { IfNode.prototype.body_node = function body_node() {
return this.body == undefined ? undefined : this.body.unwrap(); return this.body == undefined ? undefined : this.body.unwrap();
}; };
@@ -1611,12 +1664,11 @@
// Rewrite a chain of **IfNodes** with their switch condition for equality. // Rewrite a chain of **IfNodes** with their switch condition for equality.
// Ensure that the switch expression isn't evaluated more than once. // Ensure that the switch expression isn't evaluated more than once.
IfNode.prototype.rewrite_switch = function rewrite_switch(o) { IfNode.prototype.rewrite_switch = function rewrite_switch(o) {
var _b, _c, _d, assigner, cond, i, variable; var _b, _c, _d, cond, i, variable;
assigner = this.switch_subject; this.assigner = this.switch_subject;
if (!((this.switch_subject.unwrap() instanceof LiteralNode))) { if (!((this.switch_subject.unwrap() instanceof LiteralNode))) {
variable = literal(o.scope.free_variable()); variable = literal(o.scope.free_variable());
assigner = new AssignNode(variable, this.switch_subject); this.assigner = new AssignNode(variable, this.switch_subject);
this.children.push(assigner);
this.switch_subject = variable; this.switch_subject = variable;
} }
this.condition = (function() { this.condition = (function() {
@@ -1624,11 +1676,11 @@
_b = []; _c = this.condition; _b = []; _c = this.condition;
for (i = 0, _d = _c.length; i < _d; i++) { for (i = 0, _d = _c.length; i < _d; i++) {
cond = _c[i]; cond = _c[i];
_b.push(new OpNode('==', (i === 0 ? assigner : this.switch_subject), cond)); _b.push(new OpNode('==', (i === 0 ? this.assigner : this.switch_subject), cond));
} }
return _b; return _b;
} else { } else {
return new OpNode('==', assigner, this.condition); return new OpNode('==', this.assigner, this.condition);
} }
}).call(this); }).call(this);
if (this.is_chain) { if (this.is_chain) {
@@ -1645,7 +1697,6 @@
} else { } else {
this.is_chain = else_body instanceof IfNode; this.is_chain = else_body instanceof IfNode;
this.else_body = this.ensure_expressions(else_body); this.else_body = this.ensure_expressions(else_body);
this.populate_children();
} }
return this; return this;
}; };
@@ -1717,6 +1768,7 @@
}; };
return IfNode; return IfNode;
})(); })();
children(IfNode, 'condition', 'body', 'else_body', 'assigner');
// Faux-Nodes // Faux-Nodes
// ---------- // ----------
//### PushNode //### PushNode

View File

@@ -24,6 +24,9 @@ statement: (klass, only) ->
klass::is_statement: -> true klass::is_statement: -> true
(klass::is_pure_statement: -> true) if only (klass::is_pure_statement: -> true) if only
children: (klass, child_attrs...) ->
klass::_children_attributes: child_attrs
#### BaseNode #### BaseNode
# The **BaseNode** is the abstract base class for all nodes in the syntax tree. # The **BaseNode** is the abstract base class for all nodes in the syntax tree.
@@ -90,10 +93,12 @@ exports.BaseNode: class BaseNode
# and returning true when the block finds a match. `contains` does not cross # and returning true when the block finds a match. `contains` does not cross
# scope boundaries. # scope boundaries.
contains: (block) -> contains: (block) ->
for node in @children contains: false
return true if block(node) @_traverse_children false, (node, attr, index) =>
return true if node.contains and node.contains block if block(node)
false contains: true
return false
return contains
# Is this node of a certain type, or does it contain the type? # Is this node of a certain type, or does it contain the type?
contains_type: (type) -> contains_type: (type) ->
@@ -105,21 +110,41 @@ exports.BaseNode: class BaseNode
@is_pure_statement() or @contains (n) -> n.is_pure_statement() @is_pure_statement() or @contains (n) -> n.is_pure_statement()
# Perform an in-order traversal of the AST. Crosses scope boundaries. # Perform an in-order traversal of the AST. Crosses scope boundaries.
traverse: (block) -> traverse: (block) -> @_traverse_children true, block
for node in @children
block node
node.traverse block if node.traverse
# `toString` representation of the node, for inspecting the parse tree. # `toString` representation of the node, for inspecting the parse tree.
# This is what `coffee --nodes` prints out. # This is what `coffee --nodes` prints out.
toString: (idt) -> toString: (idt) ->
idt: or '' idt: or ''
'\n' + idt + @constructor.name + (child.toString(idt + TAB) for child in @children).join('') '\n' + idt + @constructor.name + (child.toString(idt + TAB) for child in @children()).join('')
children: ->
_children: []
@_each_child (child) -> _children.push(child)
_children
_each_child: (func) ->
for attr in @_children_attributes
child_collection: this[attr]
continue unless child_collection?
if child_collection instanceof Array
i: 0
for child in child_collection
result: func(child, attr, i)
return if result is false
i += 1
else
func(child_collection, attr)
_traverse_children: (cross_scope, func) ->
return unless @_children_attributes
@_each_child (child, attr, i) ->
func.apply(this, arguments)
child._traverse_children(cross_scope, func) if child instanceof BaseNode
# Default implementations of the common node identification methods. Nodes # Default implementations of the common node identification methods. Nodes
# will override these with custom logic, if needed. # will override these with custom logic, if needed.
unwrap: -> this unwrap: -> this
children: []
is_statement: -> false is_statement: -> false
is_pure_statement: -> false is_pure_statement: -> false
top_sensitive: -> false top_sensitive: -> false
@@ -132,7 +157,7 @@ exports.BaseNode: class BaseNode
exports.Expressions: class Expressions extends BaseNode exports.Expressions: class Expressions extends BaseNode
constructor: (nodes) -> constructor: (nodes) ->
@children: @expressions: compact flatten nodes or [] @expressions: compact flatten nodes or []
# Tack an expression on to the end of this expression list. # Tack an expression on to the end of this expression list.
push: (node) -> push: (node) ->
@@ -155,7 +180,7 @@ exports.Expressions: class Expressions extends BaseNode
# Make a copy of this node. # Make a copy of this node.
copy: -> copy: ->
new Expressions @children.slice() new Expressions @Expressions.slice()
# An Expressions node does not return its entire body, rather it # An Expressions node does not return its entire body, rather it
# ensures that the final expression is returned. # ensures that the final expression is returned.
@@ -206,6 +231,7 @@ Expressions.wrap: (nodes) ->
return nodes[0] if nodes.length is 1 and nodes[0] instanceof Expressions return nodes[0] if nodes.length is 1 and nodes[0] instanceof Expressions
new Expressions(nodes) new Expressions(nodes)
children Expressions, 'expressions'
statement Expressions statement Expressions
#### LiteralNode #### LiteralNode
@@ -239,7 +265,7 @@ exports.LiteralNode: class LiteralNode extends BaseNode
exports.ReturnNode: class ReturnNode extends BaseNode exports.ReturnNode: class ReturnNode extends BaseNode
constructor: (expression) -> constructor: (expression) ->
@children: [@expression: expression] @expression: expression
top_sensitive: -> top_sensitive: ->
true true
@@ -252,6 +278,7 @@ exports.ReturnNode: class ReturnNode extends BaseNode
"${@tab}return ${@expression.compile(o)};" "${@tab}return ${@expression.compile(o)};"
statement ReturnNode, true statement ReturnNode, true
children ReturnNode, 'expression'
#### ValueNode #### ValueNode
@@ -263,12 +290,12 @@ exports.ValueNode: class ValueNode extends BaseNode
# A **ValueNode** has a base and a list of property accesses. # A **ValueNode** has a base and a list of property accesses.
constructor: (base, properties) -> constructor: (base, properties) ->
@children: flatten [@base: base, @properties: (properties or [])] @base: base
@properties: (properties or [])
# Add a property access to the list. # Add a property access to the list.
push: (prop) -> push: (prop) ->
@properties.push(prop) @properties.push(prop)
@children.push(prop)
this this
has_properties: -> has_properties: ->
@@ -327,6 +354,8 @@ exports.ValueNode: class ValueNode extends BaseNode
if op and soaked then "($complete)" else complete if op and soaked then "($complete)" else complete
children ValueNode, 'base', 'properties'
#### CommentNode #### CommentNode
# CoffeeScript passes through comments as JavaScript comments at the # CoffeeScript passes through comments as JavaScript comments at the
@@ -355,7 +384,7 @@ exports.CallNode: class CallNode extends BaseNode
@is_new: false @is_new: false
@is_super: variable is 'super' @is_super: variable is 'super'
@variable: if @is_super then null else variable @variable: if @is_super then null else variable
@children: compact flatten [@variable, @args: (args or [])] @args: (args or [])
@compile_splat_arguments: SplatNode.compile_mixed_array <- @, @args @compile_splat_arguments: SplatNode.compile_mixed_array <- @, @args
# Tag this invocation as creating a new instance. # Tag this invocation as creating a new instance.
@@ -398,6 +427,8 @@ exports.CallNode: class CallNode extends BaseNode
meth: "($temp = ${ @variable.source })${ @variable.last }" meth: "($temp = ${ @variable.source })${ @variable.last }"
"${@prefix()}${meth}.apply($obj, ${ @compile_splat_arguments(o) })" "${@prefix()}${meth}.apply($obj, ${ @compile_splat_arguments(o) })"
children CallNode, 'variable', 'args'
#### CurryNode #### CurryNode
# Binds a context object and a list of arguments to a function, # Binds a context object and a list of arguments to a function,
@@ -406,7 +437,9 @@ exports.CallNode: class CallNode extends BaseNode
exports.CurryNode: class CurryNode extends CallNode exports.CurryNode: class CurryNode extends CallNode
constructor: (meth, args) -> constructor: (meth, args) ->
@children: flatten [@meth: meth, @context: args[0], @args: (args.slice(1) or [])] @meth: meth
@context: args[0]
@args: (args.slice(1) or [])
@compile_splat_arguments: SplatNode.compile_mixed_array <- @, @args @compile_splat_arguments: SplatNode.compile_mixed_array <- @, @args
arguments: (o) -> arguments: (o) ->
@@ -419,6 +452,7 @@ exports.CurryNode: class CurryNode extends CallNode
ref: new ValueNode literal utility 'bind' ref: new ValueNode literal utility 'bind'
(new CallNode(ref, [@meth, @context, literal(@arguments(o))])).compile o (new CallNode(ref, [@meth, @context, literal(@arguments(o))])).compile o
children CurryNode, 'meth', 'context', 'args'
#### ExtendsNode #### ExtendsNode
@@ -428,13 +462,16 @@ exports.CurryNode: class CurryNode extends CallNode
exports.ExtendsNode: class ExtendsNode extends BaseNode exports.ExtendsNode: class ExtendsNode extends BaseNode
constructor: (child, parent) -> constructor: (child, parent) ->
@children: [@child: child, @parent: parent] @child: child
@parent: parent
# Hooks one constructor into another's prototype chain. # Hooks one constructor into another's prototype chain.
compile_node: (o) -> compile_node: (o) ->
ref: new ValueNode literal utility 'extends' ref: new ValueNode literal utility 'extends'
(new CallNode ref, [@child, @parent]).compile o (new CallNode ref, [@child, @parent]).compile o
children ExtendsNode, 'child', 'parent'
#### AccessorNode #### AccessorNode
# A `.` accessor into a property of a value, or the `::` shorthand for # A `.` accessor into a property of a value, or the `::` shorthand for
@@ -442,7 +479,7 @@ exports.ExtendsNode: class ExtendsNode extends BaseNode
exports.AccessorNode: class AccessorNode extends BaseNode exports.AccessorNode: class AccessorNode extends BaseNode
constructor: (name, tag) -> constructor: (name, tag) ->
@children: [@name: name] @name: name
@prototype:tag is 'prototype' @prototype:tag is 'prototype'
@soak_node: tag is 'soak' @soak_node: tag is 'soak'
this this
@@ -451,19 +488,23 @@ exports.AccessorNode: class AccessorNode extends BaseNode
proto_part: if @prototype then 'prototype.' else '' proto_part: if @prototype then 'prototype.' else ''
".$proto_part${@name.compile(o)}" ".$proto_part${@name.compile(o)}"
children AccessorNode, 'name'
#### IndexNode #### IndexNode
# A `[ ... ]` indexed accessor into an array or object. # A `[ ... ]` indexed accessor into an array or object.
exports.IndexNode: class IndexNode extends BaseNode exports.IndexNode: class IndexNode extends BaseNode
constructor: (index, tag) -> constructor: (index, tag) ->
@children: [@index: index] @index: index
@soak_node: tag is 'soak' @soak_node: tag is 'soak'
compile_node: (o) -> compile_node: (o) ->
idx: @index.compile o idx: @index.compile o
"[$idx]" "[$idx]"
children IndexNode, 'index'
#### RangeNode #### RangeNode
# A range literal. Ranges can be used to extract portions (slices) of arrays, # A range literal. Ranges can be used to extract portions (slices) of arrays,
@@ -472,7 +513,8 @@ exports.IndexNode: class IndexNode extends BaseNode
exports.RangeNode: class RangeNode extends BaseNode exports.RangeNode: class RangeNode extends BaseNode
constructor: (from, to, exclusive) -> constructor: (from, to, exclusive) ->
@children: [@from: from, @to: to] @from: from
@to: to
@exclusive: !!exclusive @exclusive: !!exclusive
# Compiles the range's source variables -- where it starts and where it ends. # Compiles the range's source variables -- where it starts and where it ends.
@@ -505,6 +547,9 @@ exports.RangeNode: class RangeNode extends BaseNode
arr: Expressions.wrap([new ForNode(body, {source: (new ValueNode(this))}, literal(name))]) arr: Expressions.wrap([new ForNode(body, {source: (new ValueNode(this))}, literal(name))])
(new ParentheticalNode(new CallNode(new CodeNode([], arr.make_return())))).compile(o) (new ParentheticalNode(new CallNode(new CodeNode([], arr.make_return())))).compile(o)
children RangeNode, 'from', 'to'
#### SliceNode #### SliceNode
# An array slice literal. Unlike JavaScript's `Array#slice`, the second parameter # An array slice literal. Unlike JavaScript's `Array#slice`, the second parameter
@@ -513,7 +558,7 @@ exports.RangeNode: class RangeNode extends BaseNode
exports.SliceNode: class SliceNode extends BaseNode exports.SliceNode: class SliceNode extends BaseNode
constructor: (range) -> constructor: (range) ->
@children: [@range: range] @range: range
this this
compile_node: (o) -> compile_node: (o) ->
@@ -522,13 +567,15 @@ exports.SliceNode: class SliceNode extends BaseNode
plus_part: if @range.exclusive then '' else ' + 1' plus_part: if @range.exclusive then '' else ' + 1'
".slice($from, $to$plus_part)" ".slice($from, $to$plus_part)"
children SliceNode, 'range'
#### ObjectNode #### ObjectNode
# An object literal, nothing fancy. # An object literal, nothing fancy.
exports.ObjectNode: class ObjectNode extends BaseNode exports.ObjectNode: class ObjectNode extends BaseNode
constructor: (props) -> constructor: (props) ->
@children: @objects: @properties: props or [] @objects: @properties: props or []
# All the mucking about with commas is to make sure that CommentNodes and # All the mucking about with commas is to make sure that CommentNodes and
# AssignNodes get interleaved correctly, with no trailing commas or # AssignNodes get interleaved correctly, with no trailing commas or
@@ -548,13 +595,15 @@ exports.ObjectNode: class ObjectNode extends BaseNode
inner: if props then '\n' + props + '\n' + @idt() else '' inner: if props then '\n' + props + '\n' + @idt() else ''
"{$inner}" "{$inner}"
children ObjectNode, 'properties'
#### ArrayNode #### ArrayNode
# An array literal. # An array literal.
exports.ArrayNode: class ArrayNode extends BaseNode exports.ArrayNode: class ArrayNode extends BaseNode
constructor: (objects) -> constructor: (objects) ->
@children: @objects: objects or [] @objects: objects or []
@compile_splat_literal: SplatNode.compile_mixed_array <- @, @objects @compile_splat_literal: SplatNode.compile_mixed_array <- @, @objects
compile_node: (o) -> compile_node: (o) ->
@@ -576,6 +625,8 @@ exports.ArrayNode: class ArrayNode extends BaseNode
else else
"[$objects]" "[$objects]"
children ArrayNode, 'objects'
#### ClassNode #### ClassNode
# The CoffeeScript class definition. # The CoffeeScript class definition.
@@ -584,7 +635,9 @@ exports.ClassNode: class ClassNode extends BaseNode
# Initialize a **ClassNode** with its name, an optional superclass, and a # Initialize a **ClassNode** with its name, an optional superclass, and a
# list of prototype property assignments. # list of prototype property assignments.
constructor: (variable, parent, props) -> constructor: (variable, parent, props) ->
@children: compact flatten [@variable: variable, @parent: parent, @properties: props or []] @variable: variable
@parent: parent
@properties: props or []
@returns: false @returns: false
make_return: -> make_return: ->
@@ -628,6 +681,7 @@ exports.ClassNode: class ClassNode extends BaseNode
"$construct$extension$props$returns" "$construct$extension$props$returns"
statement ClassNode statement ClassNode
children ClassNode, 'variable', 'parent', 'properties'
#### AssignNode #### AssignNode
@@ -640,7 +694,8 @@ exports.AssignNode: class AssignNode extends BaseNode
LEADING_DOT: /^\.(prototype\.)?/ LEADING_DOT: /^\.(prototype\.)?/
constructor: (variable, value, context) -> constructor: (variable, value, context) ->
@children: [@variable: variable, @value: value] @variable: variable
@value: value
@context: context @context: context
top_sensitive: -> top_sensitive: ->
@@ -727,6 +782,8 @@ exports.AssignNode: class AssignNode extends BaseNode
val: @value.compile(o) val: @value.compile(o)
"${name}.splice.apply($name, [$from, $to].concat($val))" "${name}.splice.apply($name, [$from, $to].concat($val))"
children AssignNode, 'variable', 'value'
#### CodeNode #### CodeNode
# A function definition. This is the only node that creates a new Scope. # A function definition. This is the only node that creates a new Scope.
@@ -781,21 +838,17 @@ exports.CodeNode: class CodeNode extends BaseNode
top_sensitive: -> top_sensitive: ->
true true
# When traversing (for printing or inspecting), return the real children of # Short-circuit _traverse_children method to prevent it from crossing scope boundaries
# the function -- the parameters and body of expressions. # unless cross_scope is true
real_children: -> _traverse_children: (cross_scope, func) -> super(cross_scope, func) if cross_scope
flatten [@params, @body.expressions]
# Custom `traverse` implementation that uses the `real_children`.
traverse: (block) ->
block this
child.traverse block for child in @real_children()
toString: (idt) -> toString: (idt) ->
idt: or '' idt: or ''
children: (child.toString(idt + TAB) for child in @real_children()).join('') children: (child.toString(idt + TAB) for child in @children()).join('')
"\n$idt$children" "\n$idt$children"
children CodeNode, 'params', 'body'
#### SplatNode #### SplatNode
# A splat, either as a parameter to a function, an argument to a call, # A splat, either as a parameter to a function, an argument to a call,
@@ -804,7 +857,7 @@ exports.SplatNode: class SplatNode extends BaseNode
constructor: (name) -> constructor: (name) ->
name: literal(name) unless name.compile name: literal(name) unless name.compile
@children: [@name: name] @name: name
compile_node: (o) -> compile_node: (o) ->
if @index? then @compile_param(o) else @name.compile(o) if @index? then @compile_param(o) else @name.compile(o)
@@ -847,6 +900,8 @@ exports.SplatNode: class SplatNode extends BaseNode
i: + 1 i: + 1
args.join('') args.join('')
children SplatNode, 'name'
#### WhileNode #### WhileNode
# A while loop, the only sort of low-level loop exposed by CoffeeScript. From # A while loop, the only sort of low-level loop exposed by CoffeeScript. From
@@ -856,11 +911,11 @@ exports.WhileNode: class WhileNode extends BaseNode
constructor: (condition, opts) -> constructor: (condition, opts) ->
condition: new OpNode('!', condition) if opts and opts.invert condition: new OpNode('!', condition) if opts and opts.invert
@children:[@condition: condition] @condition: condition
@guard: opts and opts.guard @guard: opts and opts.guard
add_body: (body) -> add_body: (body) ->
@children.push @body: body @body: body
this this
make_return: -> make_return: ->
@@ -893,6 +948,7 @@ exports.WhileNode: class WhileNode extends BaseNode
"$pre {\n${ @body.compile(o) }\n$@tab}\n$post" "$pre {\n${ @body.compile(o) }\n$@tab}\n$post"
statement WhileNode statement WhileNode
children WhileNode, 'condition', 'guard', 'body'
#### OpNode #### OpNode
@@ -918,7 +974,8 @@ exports.OpNode: class OpNode extends BaseNode
constructor: (operator, first, second, flip) -> constructor: (operator, first, second, flip) ->
@constructor.name: + ' ' + operator @constructor.name: + ' ' + operator
@children: compact [@first: first, @second: second] @first: first
@second: second
@operator: @CONVERSIONS[operator] or operator @operator: @CONVERSIONS[operator] or operator
@flip: !!flip @flip: !!flip
@@ -970,13 +1027,17 @@ exports.OpNode: class OpNode extends BaseNode
parts: parts.reverse() if @flip parts: parts.reverse() if @flip
parts.join('') parts.join('')
children OpNode, 'first', 'second'
#### TryNode #### TryNode
# A classic *try/catch/finally* block. # A classic *try/catch/finally* block.
exports.TryNode: class TryNode extends BaseNode exports.TryNode: class TryNode extends BaseNode
constructor: (attempt, error, recovery, ensure) -> constructor: (attempt, error, recovery, ensure) ->
@children: compact [@attempt: attempt, @recovery: recovery, @ensure: ensure] @attempt: attempt
@recovery: recovery
@ensure: ensure
@error: error @error: error
this this
@@ -997,6 +1058,7 @@ exports.TryNode: class TryNode extends BaseNode
"${@tab}try {\n$attempt_part\n$@tab}$catch_part$finally_part" "${@tab}try {\n$attempt_part\n$@tab}$catch_part$finally_part"
statement TryNode statement TryNode
children TryNode, 'attempt', 'recovery', 'ensure'
#### ThrowNode #### ThrowNode
@@ -1004,7 +1066,7 @@ statement TryNode
exports.ThrowNode: class ThrowNode extends BaseNode exports.ThrowNode: class ThrowNode extends BaseNode
constructor: (expression) -> constructor: (expression) ->
@children: [@expression: expression] @expression: expression
# A **ThrowNode** is already a return, of sorts... # A **ThrowNode** is already a return, of sorts...
make_return: -> make_return: ->
@@ -1014,6 +1076,7 @@ exports.ThrowNode: class ThrowNode extends BaseNode
"${@tab}throw ${@expression.compile(o)};" "${@tab}throw ${@expression.compile(o)};"
statement ThrowNode statement ThrowNode
children ThrowNode, 'expression'
#### ExistenceNode #### ExistenceNode
@@ -1023,7 +1086,7 @@ statement ThrowNode
exports.ExistenceNode: class ExistenceNode extends BaseNode exports.ExistenceNode: class ExistenceNode extends BaseNode
constructor: (expression) -> constructor: (expression) ->
@children: [@expression: expression] @expression: expression
compile_node: (o) -> compile_node: (o) ->
ExistenceNode.compile_test(o, @expression) ExistenceNode.compile_test(o, @expression)
@@ -1038,6 +1101,8 @@ exports.ExistenceNode: class ExistenceNode extends BaseNode
[first, second]: [first.compile(o), second.compile(o)] [first, second]: [first.compile(o), second.compile(o)]
"(typeof $first !== \"undefined\" && $second !== null)" "(typeof $first !== \"undefined\" && $second !== null)"
children ExistenceNode, 'expression'
#### ParentheticalNode #### ParentheticalNode
# An extra set of parentheses, specified explicitly in the source. At one time # An extra set of parentheses, specified explicitly in the source. At one time
@@ -1048,7 +1113,7 @@ exports.ExistenceNode: class ExistenceNode extends BaseNode
exports.ParentheticalNode: class ParentheticalNode extends BaseNode exports.ParentheticalNode: class ParentheticalNode extends BaseNode
constructor: (expression) -> constructor: (expression) ->
@children: [@expression: expression] @expression: expression
is_statement: -> is_statement: ->
@expression.is_statement() @expression.is_statement()
@@ -1063,6 +1128,8 @@ exports.ParentheticalNode: class ParentheticalNode extends BaseNode
code: code.substr(o, l-1) if code.substr(l-1, 1) is ';' code: code.substr(o, l-1) if code.substr(l-1, 1) is ';'
if @expression instanceof AssignNode then code else "($code)" if @expression instanceof AssignNode then code else "($code)"
children ParentheticalNode, 'expression'
#### ForNode #### ForNode
# CoffeeScript's replacement for the *for* loop is our array and object # CoffeeScript's replacement for the *for* loop is our array and object
@@ -1085,7 +1152,6 @@ exports.ForNode: class ForNode extends BaseNode
[@name, @index]: [@index, @name] if @object [@name, @index]: [@index, @name] if @object
@pattern: @name instanceof ValueNode @pattern: @name instanceof ValueNode
throw new Error('index cannot be a pattern matching expression') if @index instanceof ValueNode throw new Error('index cannot be a pattern matching expression') if @index instanceof ValueNode
@children: compact [@body, @source, @guard]
@returns: false @returns: false
top_sensitive: -> top_sensitive: ->
@@ -1146,6 +1212,7 @@ exports.ForNode: class ForNode extends BaseNode
"$set_result${source_part}for ($for_part) {\n$var_part$body\n$@tab$close$return_result" "$set_result${source_part}for ($for_part) {\n$var_part$body\n$@tab$close$return_result"
statement ForNode statement ForNode
children ForNode, 'body', 'source', 'guard'
#### IfNode #### IfNode
@@ -1160,15 +1227,11 @@ exports.IfNode: class IfNode extends BaseNode
@condition: condition @condition: condition
@body: body @body: body
@else_body: null @else_body: null
@populate_children()
@tags: tags or {} @tags: tags or {}
@multiple: true if @condition instanceof Array @multiple: true if @condition instanceof Array
@condition: new OpNode('!', new ParentheticalNode(@condition)) if @tags.invert @condition: new OpNode('!', new ParentheticalNode(@condition)) if @tags.invert
@is_chain: false @is_chain: false
populate_children: ->
@children: compact flatten [@condition, @body, @else_body]
body_node: -> @body?.unwrap() body_node: -> @body?.unwrap()
else_body_node: -> @else_body?.unwrap() else_body_node: -> @else_body?.unwrap()
@@ -1185,17 +1248,16 @@ exports.IfNode: class IfNode extends BaseNode
# Rewrite a chain of **IfNodes** with their switch condition for equality. # Rewrite a chain of **IfNodes** with their switch condition for equality.
# Ensure that the switch expression isn't evaluated more than once. # Ensure that the switch expression isn't evaluated more than once.
rewrite_switch: (o) -> rewrite_switch: (o) ->
assigner: @switch_subject @assigner: @switch_subject
unless (@switch_subject.unwrap() instanceof LiteralNode) unless (@switch_subject.unwrap() instanceof LiteralNode)
variable: literal(o.scope.free_variable()) variable: literal(o.scope.free_variable())
assigner: new AssignNode(variable, @switch_subject) @assigner: new AssignNode(variable, @switch_subject)
@children.push(assigner)
@switch_subject: variable @switch_subject: variable
@condition: if @multiple @condition: if @multiple
for cond, i in @condition for cond, i in @condition
new OpNode('==', (if i is 0 then assigner else @switch_subject), cond) new OpNode('==', (if i is 0 then @assigner else @switch_subject), cond)
else else
new OpNode('==', assigner, @condition) new OpNode('==', @assigner, @condition)
@else_body_node().switches_over(@switch_subject) if @is_chain @else_body_node().switches_over(@switch_subject) if @is_chain
# prevent this rewrite from happening again # prevent this rewrite from happening again
@switch_subject: undefined @switch_subject: undefined
@@ -1208,7 +1270,6 @@ exports.IfNode: class IfNode extends BaseNode
else else
@is_chain: else_body instanceof IfNode @is_chain: else_body instanceof IfNode
@else_body: @ensure_expressions else_body @else_body: @ensure_expressions else_body
@populate_children()
this this
# The **IfNode** only compiles into a statement if either of its bodies needs # The **IfNode** only compiles into a statement if either of its bodies needs
@@ -1257,6 +1318,10 @@ exports.IfNode: class IfNode extends BaseNode
else_part: if @else_body then @else_body_node().compile(o) else 'null' else_part: if @else_body then @else_body_node().compile(o) else 'null'
"$if_part : $else_part" "$if_part : $else_part"
children IfNode, 'condition', 'body', 'else_body', 'assigner'
# Faux-Nodes # Faux-Nodes
# ---------- # ----------