OOP love for nodes.coffee

This commit is contained in:
Stan Angeloff 2010-05-19 23:24:55 +03:00
parent 57231ae75d
commit 4c3271728d
2 changed files with 174 additions and 216 deletions

View File

@ -1,5 +1,5 @@
(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, index_of, literal, merge, register, 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, compact, del, flatten, helpers, index_of, literal, merge, utility;
var __extends = function(child, parent) {
var ctor = function(){ };
ctor.prototype = parent.prototype;
@ -32,29 +32,6 @@
merge = _a.merge;
del = _a.del;
index_of = _a.index_of;
// Helper function that registers a node class. If `is_statement` is passed,
// marks the node as a JavaScript *statement*, or as a *pure_statement*.
// Statements must be wrapped in a closure when used as an expression, and
// nodes tagged as *pure_statement* cannot be closure-wrapped without losing
// their meaning.
register = function(klass, o) {
o = o || {};
klass.prototype.constructor_name = o.name;
if (o.is_statement) {
(klass.prototype.is_statement = function() {
return true;
});
}
if (o.is_pure_statement) {
(klass.prototype.is_pure_statement = function() {
return true;
});
}
if (o.children) {
klass.prototype.children_attributes = o.children;
return klass.prototype.children_attributes;
}
};
//### BaseNode
// The **BaseNode** is the abstract base class for all nodes in the syntax tree.
// Each subclass implements the `compile_node` method, which performs the
@ -66,7 +43,30 @@
// being requested by the surrounding function), information about the current
// scope, and indentation level.
exports.BaseNode = (function() {
BaseNode = function() { };
BaseNode = function(o) {
o = o || {};
if (o.name) {
this.constructor_name = o.name;
}
if (o.children) {
this.children_attributes = o.children;
}
if (o.is_statement) {
(this.is_statement = function() {
return true;
});
}
if (o.is_pure_statement) {
(this.is_pure_statement = function() {
return true;
});
}
return this;
};
// If `is_statement` is passed, marks the node as a JavaScript *statement*,
// or as a *pure_statement* if `is_pure_statement`. Statements must be
// wrapped in a closure when used as an expression, and nodes tagged as
// *pure_statement* cannot be closure-wrapped without losing their meaning.
// Common logic for determining whether to wrap this node in a closure before
// compiling it, or to compile directly. We need to wrap if this node is a
// *statement*, and it's not a *pure_statement*, and we're not at
@ -225,15 +225,17 @@
};
return BaseNode;
})();
register(BaseNode, {
name: 'BaseNode'
});
//### Expressions
// The expressions body is the list of expressions that forms the body of an
// indented block of code -- the implementation of a function, a clause in an
// `if`, `switch`, or `try`, and so on...
exports.Expressions = (function() {
Expressions = function(nodes) {
Expressions.__superClass__.constructor.call(this, {
name: 'Expressions',
is_statement: true,
children: ['expressions']
});
this.expressions = compact(flatten(nodes || []));
return this;
};
@ -350,17 +352,15 @@
}
return new Expressions(nodes);
};
register(Expressions, {
name: 'Expressions',
is_statement: true,
children: ['expressions']
});
//### LiteralNode
// Literals are static values that can be passed through directly into
// JavaScript without translation, such as: strings, numbers,
// `true`, `false`, `null`...
exports.LiteralNode = (function() {
LiteralNode = function(value) {
LiteralNode.__superClass__.constructor.call(this, {
name: 'LiteralNode'
});
this.value = value;
return this;
};
@ -382,14 +382,17 @@
};
return LiteralNode;
})();
register(LiteralNode, {
name: 'LiteralNode'
});
//### ReturnNode
// A `return` is a *pure_statement* -- wrapping it in a closure wouldn't
// make sense.
exports.ReturnNode = (function() {
ReturnNode = function(expression) {
ReturnNode.__superClass__.constructor.call(this, {
name: 'ReturnNode',
is_statement: true,
is_pure_statement: true,
children: ['expression']
});
this.expression = expression;
return this;
};
@ -411,17 +414,15 @@
};
return ReturnNode;
})();
register(ReturnNode, {
name: 'ReturnNode',
is_statement: true,
is_pure_statement: true,
children: ['expression']
});
//### ValueNode
// A value, variable or literal or parenthesized, indexed or dotted into,
// or vanilla.
exports.ValueNode = (function() {
ValueNode = function(base, properties) {
ValueNode.__superClass__.constructor.call(this, {
name: 'ValueNode',
children: ['base', 'properties']
});
this.base = base;
this.properties = (properties || []);
return this;
@ -522,18 +523,17 @@
};
return ValueNode;
})();
register(ValueNode, {
name: 'ValueNode',
children: ['base', 'properties']
});
//### CommentNode
// CoffeeScript passes through comments as JavaScript comments at the
// same position.
exports.CommentNode = (function() {
CommentNode = function(lines, type) {
CommentNode.__superClass__.constructor.call(this, {
name: 'CommentNode',
is_statement: true
});
this.lines = lines;
this.type = type;
this;
return this;
};
__extends(CommentNode, BaseNode);
@ -551,15 +551,15 @@
};
return CommentNode;
})();
register(CommentNode, {
name: 'CommentNode',
is_statement: true
});
//### CallNode
// Node for a function invocation. Takes care of converting `super()` calls into
// calls against the prototype's function of the same name.
exports.CallNode = (function() {
CallNode = function(variable, args) {
CallNode.__superClass__.constructor.call(this, {
name: 'CallNode',
children: ['variable', 'args']
});
this.is_new = false;
this.is_super = variable === 'super';
this.variable = this.is_super ? null : variable;
@ -643,16 +643,16 @@
};
return CallNode;
})();
register(CallNode, {
name: 'CallNode',
children: ['variable', 'args']
});
//### CurryNode
// Binds a context object and a list of arguments to a function,
// returning the bound function. After ECMAScript 5, Prototype.js, and
// Underscore's `bind` functions.
exports.CurryNode = (function() {
CurryNode = function(meth, args) {
CurryNode.__superClass__.constructor.call(this, {
name: 'CurryNode',
children: ['meth', 'context', 'args']
});
this.meth = meth;
this.context = args[0];
this.args = (args.slice(1) || []);
@ -679,16 +679,16 @@
};
return CurryNode;
}).apply(this, arguments);
register(CurryNode, {
name: 'CurryNode',
children: ['meth', 'context', 'args']
});
//### ExtendsNode
// Node to extend an object's prototype with an ancestor object.
// After `goog.inherits` from the
// [Closure Library](http://closure-library.googlecode.com/svn/docs/closure_goog_base.js.html).
exports.ExtendsNode = (function() {
ExtendsNode = function(child, parent) {
ExtendsNode.__superClass__.constructor.call(this, {
name: 'ExtendsNode',
children: ['child', 'parent']
});
this.child = child;
this.parent = parent;
return this;
@ -702,19 +702,18 @@
};
return ExtendsNode;
})();
register(ExtendsNode, {
name: 'ExtendsNode',
children: ['child', 'parent']
});
//### AccessorNode
// A `.` accessor into a property of a value, or the `::` shorthand for
// an accessor into the object's prototype.
exports.AccessorNode = (function() {
AccessorNode = function(name, tag) {
AccessorNode.__superClass__.constructor.call(this, {
name: 'AccessorNode',
children: ['name']
});
this.name = name;
this.prototype = tag === 'prototype';
this.soak_node = tag === 'soak';
this;
return this;
};
__extends(AccessorNode, BaseNode);
@ -726,14 +725,14 @@
};
return AccessorNode;
})();
register(AccessorNode, {
name: 'AccessorNode',
children: ['name']
});
//### IndexNode
// A `[ ... ]` indexed accessor into an array or object.
exports.IndexNode = (function() {
IndexNode = function(index, tag) {
IndexNode.__superClass__.constructor.call(this, {
name: 'IndexNode',
children: ['index']
});
this.index = index;
this.soak_node = tag === 'soak';
return this;
@ -747,16 +746,16 @@
};
return IndexNode;
})();
register(IndexNode, {
name: 'IndexNode',
children: ['index']
});
//### RangeNode
// 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
// corresponding array of integers at runtime.
exports.RangeNode = (function() {
RangeNode = function(from, to, exclusive) {
RangeNode.__superClass__.constructor.call(this, {
name: 'RangeNode',
children: ['from', 'to']
});
this.from = from;
this.to = to;
this.exclusive = !!exclusive;
@ -808,18 +807,17 @@
};
return RangeNode;
})();
register(RangeNode, {
name: 'RangeNode',
children: ['from', 'to']
});
//### SliceNode
// 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
// is the index of the beginning.
exports.SliceNode = (function() {
SliceNode = function(range) {
SliceNode.__superClass__.constructor.call(this, {
name: 'SliceNode',
children: ['range']
});
this.range = range;
this;
return this;
};
__extends(SliceNode, BaseNode);
@ -832,14 +830,14 @@
};
return SliceNode;
})();
register(SliceNode, {
name: 'SliceNode',
children: ['range']
});
//### ObjectNode
// An object literal, nothing fancy.
exports.ObjectNode = (function() {
ObjectNode = function(props) {
ObjectNode.__superClass__.constructor.call(this, {
name: 'ObjectNode',
children: ['properties']
});
this.objects = (this.properties = props || []);
return this;
};
@ -886,14 +884,14 @@
};
return ObjectNode;
})();
register(ObjectNode, {
name: 'ObjectNode',
children: ['properties']
});
//### ArrayNode
// An array literal.
exports.ArrayNode = (function() {
ArrayNode = function(objects) {
ArrayNode.__superClass__.constructor.call(this, {
name: 'ArrayNode',
children: ['objects']
});
this.objects = objects || [];
this.compile_splat_literal = __bind(SplatNode.compile_mixed_array, this, [this.objects]);
return this;
@ -926,14 +924,15 @@
};
return ArrayNode;
})();
register(ArrayNode, {
name: 'ArrayNode',
children: ['objects']
});
//### ClassNode
// The CoffeeScript class definition.
exports.ClassNode = (function() {
ClassNode = function(variable, parent, props) {
ClassNode.__superClass__.constructor.call(this, {
name: 'ClassNode',
is_statement: true,
children: ['variable', 'parent', 'properties']
});
this.variable = variable;
this.parent = parent;
this.properties = props || [];
@ -990,16 +989,15 @@
};
return ClassNode;
})();
register(ClassNode, {
name: 'ClassNode',
is_statement: true,
children: ['variable', 'parent', 'properties']
});
//### AssignNode
// The **AssignNode** is used to assign a local variable to value, or to set the
// property of an object -- including within object literals.
exports.AssignNode = (function() {
AssignNode = function(variable, value, context) {
AssignNode.__superClass__.constructor.call(this, {
name: 'AssignNode',
children: ['variable', 'value']
});
this.variable = variable;
this.value = value;
this.context = context;
@ -1128,16 +1126,16 @@
};
return AssignNode;
})();
register(AssignNode, {
name: 'AssignNode',
children: ['variable', 'value']
});
//### CodeNode
// 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
// has no *children* -- they're within the inner scope.
exports.CodeNode = (function() {
CodeNode = function(params, body, tag) {
CodeNode.__superClass__.constructor.call(this, {
name: 'CodeNode',
children: ['params', 'body']
});
this.params = params || [];
this.body = body || new Expressions();
this.bound = tag === 'boundfunc';
@ -1228,15 +1226,15 @@
};
return CodeNode;
})();
register(CodeNode, {
name: 'CodeNode',
children: ['params', 'body']
});
//### SplatNode
// A splat, either as a parameter to a function, an argument to a call,
// or as part of a destructuring assignment.
exports.SplatNode = (function() {
SplatNode = function(name) {
SplatNode.__superClass__.constructor.call(this, {
name: 'SplatNode',
children: ['name']
});
if (!(name.compile)) {
name = literal(name);
}
@ -1306,16 +1304,17 @@
};
return SplatNode;
}).call(this);
register(SplatNode, {
name: 'SplatNode',
children: ['name']
});
//### WhileNode
// 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
// flexibility or more speed than a comprehension can provide.
exports.WhileNode = (function() {
WhileNode = function(condition, opts) {
WhileNode.__superClass__.constructor.call(this, {
name: 'WhileNode',
is_statement: true,
children: ['condition', 'guard', 'body']
});
if (opts && opts.invert) {
if (condition instanceof OpNode) {
condition = new ParentheticalNode(condition);
@ -1366,17 +1365,15 @@
};
return WhileNode;
})();
register(WhileNode, {
name: 'WhileNode',
is_statement: true,
children: ['condition', 'guard', 'body']
});
//### OpNode
// Simple Arithmetic and logical operations. Performs some conversion from
// CoffeeScript operations into their JavaScript equivalents.
exports.OpNode = (function() {
OpNode = function(operator, first, second, flip) {
this.constructor_name += ' ' + operator;
OpNode.__superClass__.constructor.call(this, {
name: ("OpNode " + operator),
children: ['first', 'second']
});
this.first = first;
this.second = second;
this.operator = this.CONVERSIONS[operator] || operator;
@ -1474,19 +1471,19 @@
};
return OpNode;
})();
register(OpNode, {
name: 'OpNode',
children: ['first', 'second']
});
//### TryNode
// A classic *try/catch/finally* block.
exports.TryNode = (function() {
TryNode = function(attempt, error, recovery, ensure) {
TryNode.__superClass__.constructor.call(this, {
name: 'TryNode',
is_statement: true,
children: ['attempt', 'recovery', 'ensure']
});
this.attempt = attempt;
this.recovery = recovery;
this.ensure = ensure;
this.error = error;
this;
return this;
};
__extends(TryNode, BaseNode);
@ -1513,15 +1510,15 @@
};
return TryNode;
})();
register(TryNode, {
name: 'TryNode',
is_statement: true,
children: ['attempt', 'recovery', 'ensure']
});
//### ThrowNode
// Simple node to throw an exception.
exports.ThrowNode = (function() {
ThrowNode = function(expression) {
ThrowNode.__superClass__.constructor.call(this, {
name: 'ThrowNode',
is_statement: true,
children: ['expression']
});
this.expression = expression;
return this;
};
@ -1535,17 +1532,16 @@
};
return ThrowNode;
})();
register(ThrowNode, {
name: 'ThrowNode',
is_statement: true,
children: ['expression']
});
//### ExistenceNode
// 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
// table.
exports.ExistenceNode = (function() {
ExistenceNode = function(expression) {
ExistenceNode.__superClass__.constructor.call(this, {
name: 'ExistenceNode',
children: ['expression']
});
this.expression = expression;
return this;
};
@ -1573,10 +1569,6 @@
};
return ExistenceNode;
}).call(this);
register(ExistenceNode, {
name: 'ExistenceNode',
children: ['expression']
});
//### ParentheticalNode
// 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
@ -1584,6 +1576,10 @@
// Parentheses are a good way to force any statement to become an expression.
exports.ParentheticalNode = (function() {
ParentheticalNode = function(expression) {
ParentheticalNode.__superClass__.constructor.call(this, {
name: 'ParentheticalNode',
children: ['expression']
});
this.expression = expression;
return this;
};
@ -1612,10 +1608,6 @@
};
return ParentheticalNode;
})();
register(ParentheticalNode, {
name: 'ParentheticalNode',
children: ['expression']
});
//### ForNode
// CoffeeScript's replacement for the *for* loop is our array and object
// comprehensions, that compile into *for* loops here. They also act as an
@ -1626,6 +1618,11 @@
exports.ForNode = (function() {
ForNode = function(body, source, name, index) {
var _b;
ForNode.__superClass__.constructor.call(this, {
name: 'ForNode',
is_statement: true,
children: ['body', 'source', 'guard']
});
this.body = body;
this.name = name;
this.index = index || null;
@ -1734,11 +1731,6 @@
};
return ForNode;
})();
register(ForNode, {
name: 'ForNode',
is_statement: true,
children: ['body', 'source', 'guard']
});
//### IfNode
// *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.
@ -1746,6 +1738,10 @@
// because ternaries are already proper expressions, and don't need conversion.
exports.IfNode = (function() {
IfNode = function(condition, body, tags) {
IfNode.__superClass__.constructor.call(this, {
name: 'IfNode',
children: ['condition', 'body', 'else_body', 'assigner']
});
this.condition = condition;
this.body = body;
this.else_body = null;
@ -1881,10 +1877,6 @@
};
return IfNode;
})();
register(IfNode, {
name: 'IfNode',
children: ['condition', 'body', 'else_body', 'assigner']
});
// Faux-Nodes
// ----------
//### PushNode

View File

@ -16,18 +16,6 @@ else
# Import the helpers we plan to use.
{compact, flatten, merge, del, index_of}: helpers
# Helper function that registers a node class. If `is_statement` is passed,
# marks the node as a JavaScript *statement*, or as a *pure_statement*.
# Statements must be wrapped in a closure when used as an expression, and
# nodes tagged as *pure_statement* cannot be closure-wrapped without losing
# their meaning.
register: (klass, o) ->
o: or {}
klass::constructor_name: o.name
(klass::is_statement: -> true) if o.is_statement
(klass::is_pure_statement: -> true) if o.is_pure_statement
(klass::children_attributes: o.children) if o.children
#### BaseNode
# The **BaseNode** is the abstract base class for all nodes in the syntax tree.
@ -41,6 +29,17 @@ register: (klass, o) ->
# scope, and indentation level.
exports.BaseNode: class BaseNode
# If `is_statement` is passed, marks the node as a JavaScript *statement*,
# or as a *pure_statement* if `is_pure_statement`. Statements must be
# wrapped in a closure when used as an expression, and nodes tagged as
# *pure_statement* cannot be closure-wrapped without losing their meaning.
constructor: (o) ->
o: or {}
@constructor_name: o.name if o.name
@children_attributes: o.children if o.children
(@is_statement: -> true) if o.is_statement
(@is_pure_statement: -> true) if o.is_pure_statement
# Common logic for determining whether to wrap this node in a closure before
# compiling it, or to compile directly. We need to wrap if this node is a
# *statement*, and it's not a *pure_statement*, and we're not at
@ -147,8 +146,6 @@ exports.BaseNode: class BaseNode
is_pure_statement: -> false
top_sensitive: -> false
register BaseNode, {name: 'BaseNode'}
#### Expressions
# The expressions body is the list of expressions that forms the body of an
@ -157,6 +154,7 @@ register BaseNode, {name: 'BaseNode'}
exports.Expressions: class Expressions extends BaseNode
constructor: (nodes) ->
super {name: 'Expressions', is_statement: yes, children: ['expressions']}
@expressions: compact flatten nodes or []
# Tack an expression on to the end of this expression list.
@ -227,8 +225,6 @@ Expressions.wrap: (nodes) ->
return nodes[0] if nodes.length is 1 and nodes[0] instanceof Expressions
new Expressions(nodes)
register Expressions, {name: 'Expressions', is_statement: yes, children: ['expressions']}
#### LiteralNode
# Literals are static values that can be passed through directly into
@ -237,6 +233,7 @@ register Expressions, {name: 'Expressions', is_statement: yes, children: ['expre
exports.LiteralNode: class LiteralNode extends BaseNode
constructor: (value) ->
super {name: 'LiteralNode'}
@value: value
# Break and continue must be treated as pure statements -- they lose their
@ -253,8 +250,6 @@ exports.LiteralNode: class LiteralNode extends BaseNode
toString: (idt) ->
" \"$@value\""
register LiteralNode, {name: 'LiteralNode'}
#### ReturnNode
# A `return` is a *pure_statement* -- wrapping it in a closure wouldn't
@ -262,6 +257,7 @@ register LiteralNode, {name: 'LiteralNode'}
exports.ReturnNode: class ReturnNode extends BaseNode
constructor: (expression) ->
super {name: 'ReturnNode', is_statement: yes, is_pure_statement: yes, children: ['expression']}
@expression: expression
top_sensitive: ->
@ -274,8 +270,6 @@ exports.ReturnNode: class ReturnNode extends BaseNode
o.as_statement: true if @expression.is_statement()
"${@tab}return ${@expression.compile(o)};"
register ReturnNode, {name: 'ReturnNode', is_statement: yes, is_pure_statement: yes, children: ['expression']}
#### ValueNode
# A value, variable or literal or parenthesized, indexed or dotted into,
@ -286,6 +280,7 @@ exports.ValueNode: class ValueNode extends BaseNode
# A **ValueNode** has a base and a list of property accesses.
constructor: (base, properties) ->
super {name: 'ValueNode', children: ['base', 'properties']}
@base: base
@properties: (properties or [])
@ -356,8 +351,6 @@ exports.ValueNode: class ValueNode extends BaseNode
if op and @wrapped then "($complete)" else complete
register ValueNode, {name: 'ValueNode', children: ['base', 'properties']}
#### CommentNode
# CoffeeScript passes through comments as JavaScript comments at the
@ -365,9 +358,9 @@ register ValueNode, {name: 'ValueNode', children: ['base', 'properties']}
exports.CommentNode: class CommentNode extends BaseNode
constructor: (lines, type) ->
super {name: 'CommentNode', is_statement: yes}
@lines: lines
@type: type
this
make_return: ->
this
@ -379,8 +372,6 @@ exports.CommentNode: class CommentNode extends BaseNode
else
"$@tab//" + @lines.join("\n$@tab//")
register CommentNode, {name: 'CommentNode', is_statement: yes}
#### CallNode
# Node for a function invocation. Takes care of converting `super()` calls into
@ -388,6 +379,7 @@ register CommentNode, {name: 'CommentNode', is_statement: yes}
exports.CallNode: class CallNode extends BaseNode
constructor: (variable, args) ->
super {name: 'CallNode', children: ['variable', 'args']}
@is_new: false
@is_super: variable is 'super'
@variable: if @is_super then null else variable
@ -438,8 +430,6 @@ exports.CallNode: class CallNode extends BaseNode
meth: "($temp = ${ @variable.source })${ @variable.last }"
"${@prefix()}${meth}.apply($obj, ${ @compile_splat_arguments(o) })"
register CallNode, {name: 'CallNode', children: ['variable', 'args']}
#### CurryNode
# Binds a context object and a list of arguments to a function,
@ -448,6 +438,7 @@ register CallNode, {name: 'CallNode', children: ['variable', 'args']}
exports.CurryNode: class CurryNode extends CallNode
constructor: (meth, args) ->
super {name: 'CurryNode', children: ['meth', 'context', 'args']}
@meth: meth
@context: args[0]
@args: (args.slice(1) or [])
@ -463,8 +454,6 @@ exports.CurryNode: class CurryNode extends CallNode
ref: new ValueNode literal utility 'bind'
(new CallNode(ref, [@meth, @context, literal(@arguments(o))])).compile o
register CurryNode, {name: 'CurryNode', children: ['meth', 'context', 'args']}
#### ExtendsNode
# Node to extend an object's prototype with an ancestor object.
@ -473,6 +462,7 @@ register CurryNode, {name: 'CurryNode', children: ['meth', 'context', 'args']}
exports.ExtendsNode: class ExtendsNode extends BaseNode
constructor: (child, parent) ->
super {name: 'ExtendsNode', children: ['child', 'parent']}
@child: child
@parent: parent
@ -481,8 +471,6 @@ exports.ExtendsNode: class ExtendsNode extends BaseNode
ref: new ValueNode literal utility 'extends'
(new CallNode ref, [@child, @parent]).compile o
register ExtendsNode, {name: 'ExtendsNode', children: ['child', 'parent']}
#### AccessorNode
# A `.` accessor into a property of a value, or the `::` shorthand for
@ -490,24 +478,23 @@ register ExtendsNode, {name: 'ExtendsNode', children: ['child', 'parent']}
exports.AccessorNode: class AccessorNode extends BaseNode
constructor: (name, tag) ->
super {name: 'AccessorNode', children: ['name']}
@name: name
@prototype: tag is 'prototype'
@soak_node: tag is 'soak'
this
compile_node: (o) ->
o.chain_root.wrapped: or @soak_node
proto_part: if @prototype then 'prototype.' else ''
".$proto_part${@name.compile(o)}"
register AccessorNode, {name: 'AccessorNode', children: ['name']}
#### IndexNode
# A `[ ... ]` indexed accessor into an array or object.
exports.IndexNode: class IndexNode extends BaseNode
constructor: (index, tag) ->
super {name: 'IndexNode', children: ['index']}
@index: index
@soak_node: tag is 'soak'
@ -516,8 +503,6 @@ exports.IndexNode: class IndexNode extends BaseNode
idx: @index.compile o
"[$idx]"
register IndexNode, {name: 'IndexNode', children: ['index']}
#### RangeNode
# A range literal. Ranges can be used to extract portions (slices) of arrays,
@ -526,6 +511,7 @@ register IndexNode, {name: 'IndexNode', children: ['index']}
exports.RangeNode: class RangeNode extends BaseNode
constructor: (from, to, exclusive) ->
super {name: 'RangeNode', children: ['from', 'to']}
@from: from
@to: to
@exclusive: !!exclusive
@ -560,8 +546,6 @@ exports.RangeNode: class RangeNode extends BaseNode
arr: Expressions.wrap([new ForNode(body, {source: (new ValueNode(this))}, literal(name))])
(new ParentheticalNode(new CallNode(new CodeNode([], arr.make_return())))).compile(o)
register RangeNode, {name: 'RangeNode', children: ['from', 'to']}
#### SliceNode
# An array slice literal. Unlike JavaScript's `Array#slice`, the second parameter
@ -570,8 +554,8 @@ register RangeNode, {name: 'RangeNode', children: ['from', 'to']}
exports.SliceNode: class SliceNode extends BaseNode
constructor: (range) ->
super {name: 'SliceNode', children: ['range']}
@range: range
this
compile_node: (o) ->
from: @range.from.compile(o)
@ -579,15 +563,14 @@ exports.SliceNode: class SliceNode extends BaseNode
plus_part: if @range.exclusive then '' else ' + 1'
".slice($from, $to$plus_part)"
register SliceNode, {name: 'SliceNode', children: ['range']}
#### ObjectNode
# An object literal, nothing fancy.
exports.ObjectNode: class ObjectNode extends BaseNode
constructor: (props) ->
@objects: @properties: props or []
super {name: 'ObjectNode', children: ['properties']}
@objects: @properties: props or []
# All the mucking about with commas is to make sure that CommentNodes and
# AssignNodes get interleaved correctly, with no trailing commas or
@ -607,14 +590,13 @@ exports.ObjectNode: class ObjectNode extends BaseNode
inner: if props then '\n' + props + '\n' + @idt() else ''
"{$inner}"
register ObjectNode, {name: 'ObjectNode', children: ['properties']}
#### ArrayNode
# An array literal.
exports.ArrayNode: class ArrayNode extends BaseNode
constructor: (objects) ->
super {name: 'ArrayNode', children: ['objects']}
@objects: objects or []
@compile_splat_literal: SplatNode.compile_mixed_array <- @, @objects
@ -637,8 +619,6 @@ exports.ArrayNode: class ArrayNode extends BaseNode
else
"[$objects]"
register ArrayNode, {name: 'ArrayNode', children: ['objects']}
#### ClassNode
# The CoffeeScript class definition.
@ -647,6 +627,7 @@ exports.ClassNode: class ClassNode extends BaseNode
# Initialize a **ClassNode** with its name, an optional superclass, and a
# list of prototype property assignments.
constructor: (variable, parent, props) ->
super {name: 'ClassNode', is_statement: yes, children: ['variable', 'parent', 'properties']}
@variable: variable
@parent: parent
@properties: props or []
@ -692,8 +673,6 @@ exports.ClassNode: class ClassNode extends BaseNode
returns: if @returns then new ReturnNode(@variable).compile(o) else ''
"$construct$extension$props$returns"
register ClassNode, {name: 'ClassNode', is_statement: yes, children: ['variable', 'parent', 'properties']}
#### AssignNode
# The **AssignNode** is used to assign a local variable to value, or to set the
@ -705,6 +684,7 @@ exports.AssignNode: class AssignNode extends BaseNode
LEADING_DOT: /^\.(prototype\.)?/
constructor: (variable, value, context) ->
super {name: 'AssignNode', children: ['variable', 'value']}
@variable: variable
@value: value
@context: context
@ -793,8 +773,6 @@ exports.AssignNode: class AssignNode extends BaseNode
val: @value.compile(o)
"${name}.splice.apply($name, [$from, $to].concat($val))"
register AssignNode, {name: 'AssignNode', children: ['variable', 'value']}
#### CodeNode
# A function definition. This is the only node that creates a new Scope.
@ -803,6 +781,7 @@ register AssignNode, {name: 'AssignNode', children: ['variable', 'value']}
exports.CodeNode: class CodeNode extends BaseNode
constructor: (params, body, tag) ->
super {name: 'CodeNode', children: ['params', 'body']}
@params: params or []
@body: body or new Expressions()
@bound: tag is 'boundfunc'
@ -858,8 +837,6 @@ exports.CodeNode: class CodeNode extends BaseNode
children: (child.toString(idt + TAB) for child in @children()).join('')
"\n$idt$children"
register CodeNode, {name: 'CodeNode', children: ['params', 'body']}
#### SplatNode
# A splat, either as a parameter to a function, an argument to a call,
@ -867,6 +844,7 @@ register CodeNode, {name: 'CodeNode', children: ['params', 'body']}
exports.SplatNode: class SplatNode extends BaseNode
constructor: (name) ->
super {name: 'SplatNode', children: ['name']}
name: literal(name) unless name.compile
@name: name
@ -914,8 +892,6 @@ exports.SplatNode: class SplatNode extends BaseNode
i: + 1
args.join('')
register SplatNode, {name: 'SplatNode', children: ['name']}
#### WhileNode
# A while loop, the only sort of low-level loop exposed by CoffeeScript. From
@ -924,6 +900,7 @@ register SplatNode, {name: 'SplatNode', children: ['name']}
exports.WhileNode: class WhileNode extends BaseNode
constructor: (condition, opts) ->
super {name: 'WhileNode', is_statement: yes, children: ['condition', 'guard', 'body']}
if opts and opts.invert
condition: new ParentheticalNode condition if condition instanceof OpNode
condition: new OpNode('!', condition)
@ -962,8 +939,6 @@ exports.WhileNode: class WhileNode extends BaseNode
post: ''
"$pre {\n${ @body.compile(o) }\n$@tab}$post"
register WhileNode, {name: 'WhileNode', is_statement: yes, children: ['condition', 'guard', 'body']}
#### OpNode
# Simple Arithmetic and logical operations. Performs some conversion from
@ -987,7 +962,7 @@ exports.OpNode: class OpNode extends BaseNode
PREFIX_OPERATORS: ['typeof', 'delete']
constructor: (operator, first, second, flip) ->
@constructor_name: + ' ' + operator
super {name: "OpNode $operator", children: ['first', 'second']}
@first: first
@second: second
@operator: @CONVERSIONS[operator] or operator
@ -1041,19 +1016,17 @@ exports.OpNode: class OpNode extends BaseNode
parts: parts.reverse() if @flip
parts.join('')
register OpNode, {name: 'OpNode', children: ['first', 'second']}
#### TryNode
# A classic *try/catch/finally* block.
exports.TryNode: class TryNode extends BaseNode
constructor: (attempt, error, recovery, ensure) ->
super {name: 'TryNode', is_statement: true, children: ['attempt', 'recovery', 'ensure']}
@attempt: attempt
@recovery: recovery
@ensure: ensure
@error: error
this
make_return: ->
@attempt: @attempt.make_return() if @attempt
@ -1071,14 +1044,13 @@ exports.TryNode: class TryNode extends BaseNode
finally_part: (@ensure or '') and ' finally {\n' + @ensure.compile(merge(o)) + "\n$@tab}"
"${@tab}try {\n$attempt_part\n$@tab}$catch_part$finally_part"
register TryNode, {name: 'TryNode', is_statement: true, children: ['attempt', 'recovery', 'ensure']}
#### ThrowNode
# Simple node to throw an exception.
exports.ThrowNode: class ThrowNode extends BaseNode
constructor: (expression) ->
super {name: 'ThrowNode', is_statement: true, children: ['expression']}
@expression: expression
# A **ThrowNode** is already a return, of sorts...
@ -1088,8 +1060,6 @@ exports.ThrowNode: class ThrowNode extends BaseNode
compile_node: (o) ->
"${@tab}throw ${@expression.compile(o)};"
register ThrowNode, {name: 'ThrowNode', is_statement: true, children: ['expression']}
#### ExistenceNode
# Checks a variable for existence -- not *null* and not *undefined*. This is
@ -1098,6 +1068,7 @@ register ThrowNode, {name: 'ThrowNode', is_statement: true, children: ['expressi
exports.ExistenceNode: class ExistenceNode extends BaseNode
constructor: (expression) ->
super {name: 'ExistenceNode', children: ['expression']}
@expression: expression
compile_node: (o) ->
@ -1113,8 +1084,6 @@ exports.ExistenceNode: class ExistenceNode extends BaseNode
[first, second]: [first.compile(o), second.compile(o)]
"(typeof $first !== \"undefined\" && $second !== null)"
register ExistenceNode, {name: 'ExistenceNode', children: ['expression']}
#### ParentheticalNode
# An extra set of parentheses, specified explicitly in the source. At one time
@ -1125,6 +1094,7 @@ register ExistenceNode, {name: 'ExistenceNode', children: ['expression']}
exports.ParentheticalNode: class ParentheticalNode extends BaseNode
constructor: (expression) ->
super {name: 'ParentheticalNode', children: ['expression']}
@expression: expression
is_statement: ->
@ -1140,8 +1110,6 @@ exports.ParentheticalNode: class ParentheticalNode extends BaseNode
code: code.substr(o, l-1) if code.substr(l-1, 1) is ';'
if @expression instanceof AssignNode then code else "($code)"
register ParentheticalNode, {name: 'ParentheticalNode', children: ['expression']}
#### ForNode
# CoffeeScript's replacement for the *for* loop is our array and object
@ -1154,6 +1122,7 @@ register ParentheticalNode, {name: 'ParentheticalNode', children: ['expression']
exports.ForNode: class ForNode extends BaseNode
constructor: (body, source, name, index) ->
super {name: 'ForNode', is_statement: yes, children: ['body', 'source', 'guard']}
@body: body
@name: name
@index: index or null
@ -1224,8 +1193,6 @@ exports.ForNode: class ForNode extends BaseNode
close: if @object then '}}' else '}'
"$set_result${source_part}for ($for_part) {\n$var_part$body\n$@tab$close$return_result"
register ForNode, {name: 'ForNode', is_statement: yes, children: ['body', 'source', 'guard']}
#### IfNode
# *If/else* statements. Our *switch/when* will be compiled into this. Acts as an
@ -1236,6 +1203,7 @@ register ForNode, {name: 'ForNode', is_statement: yes, children: ['body', 'sourc
exports.IfNode: class IfNode extends BaseNode
constructor: (condition, body, tags) ->
super {name: 'IfNode', children: ['condition', 'body', 'else_body', 'assigner']}
@condition: condition
@body: body
@else_body: null
@ -1327,8 +1295,6 @@ exports.IfNode: class IfNode extends BaseNode
else_part: if @else_body then @else_body_node().compile(o) else 'null'
"$if_part : $else_part"
register IfNode, {name: 'IfNode', children: ['condition', 'body', 'else_body', 'assigner']}
# Faux-Nodes
# ----------