Cleaned up return logic

- ReturnNodes are explicitly added during compilation
 - ReturnNode is used instead of scattering "return" throughout
   code compilation snippets
 - nodes gain a make_return method in order to do the most useful
   thing when a return is requested
This commit is contained in:
gfxmonk 2010-03-21 22:17:58 +11:00
parent 8553a89af2
commit cc3c314988
11 changed files with 458 additions and 145 deletions

View File

@ -23,11 +23,12 @@
// Define a Cake task with a short name, a sentence description,
// and the function to run as the action itself.
task: function task(name, description, action) {
return tasks[name] = {
tasks[name] = {
name: name,
description: description,
action: action
};
return tasks[name];
},
// Define an option that the Cakefile accepts. The parsed options hash,
// containing all of the command-line options passed, will be made available
@ -67,6 +68,7 @@
_a.push(invoke(arg));
}
return _a;
return null;
});
};
// Display the list of Cake tasks in a format similar to `rake -T`
@ -83,12 +85,14 @@
_b.push(' ');
}
return _b;
return null;
}).call(this).join('') : '';
puts("cake " + name + spaces + " # " + (task.description));
}}
if (switches.length) {
return puts(oparse.help());
}
return null;
};
// Print an error and exit when attempting to all an undefined task.
no_such_task = function no_such_task(task) {

View File

@ -37,7 +37,9 @@
err.message = "In " + (options.source) + ", " + (err.message);
}
throw err;
return null;
}
return null;
});
// Tokenize a string of CoffeeScript code, and return the array of tokens.
exports.tokens = function tokens(code) {
@ -78,7 +80,8 @@
},
setInput: function setInput(tokens) {
this.tokens = tokens;
return this.pos = 0;
this.pos = 0;
return this.pos;
},
upcomingInput: function upcomingInput() {
return "";
@ -97,11 +100,10 @@
_a = []; _b = document.getElementsByTagName('script');
for (_c = 0, _d = _b.length; _c < _d; _c++) {
tag = _b[_c];
if (tag.type === 'text/coffeescript') {
_a.push(eval(exports.compile(tag.innerHTML)));
}
tag.type === 'text/coffeescript' ? _a.push(eval(exports.compile(tag.innerHTML))) : null;
}
return _a;
return null;
};
if (window.addEventListener) {
window.addEventListener('load', process_scripts, false);

View File

@ -74,6 +74,7 @@
_a.push(compile(source));
}
return _a;
return null;
};
// Compile a single source script, containing the given code, according to the
// requested options. Both compile_scripts and watch_scripts share this method
@ -99,14 +100,18 @@
} else if (o.lint) {
return lint(js);
}
return null;
}
return null;
} catch (err) {
if (o.watch) {
return puts(err.message);
} else {
throw err;
}
return null;
}
return null;
};
// Attach the appropriate listeners to compile scripts incoming over **stdin**,
// and write them back to **stdout**.
@ -118,6 +123,7 @@
if (string) {
return code += string;
}
return null;
});
return process.stdio.addListener('close', function() {
return compile_script('stdio', code);
@ -147,6 +153,7 @@
_a.push(watch(source));
}
return _a;
return null;
};
// Write out a JavaScript source file with the compiled code. By default, files
// are written out in `cwd` as `.js` files with the same name, but the output
@ -167,11 +174,13 @@
if (result) {
return puts(result.replace(/\n/g, ''));
}
return null;
});
jsl.addListener('error', function(result) {
if (result) {
return puts(result);
}
return null;
});
jsl.write(js);
return jsl.close();
@ -191,6 +200,7 @@
}).call(this));
}
return _a;
return null;
}).call(this);
return puts(strings.join(' '));
};
@ -202,7 +212,8 @@
o = (options = option_parser.parse(process.argv));
options.run = !(o.compile || o.print || o.lint);
options.print = !!(o.print || (o.eval || o.stdio && o.compile));
return sources = options.arguments.slice(2, options.arguments.length);
sources = options.arguments.slice(2, options.arguments.length);
return sources;
};
// The compile-time options to pass to the CoffeeScript compiler.
compile_options = function compile_options(source) {

View File

@ -348,7 +348,11 @@
// this to be separate from the **ArgList** for use in **Switch** blocks, where
// having the newlines wouldn't make sense.
SimpleArgs: [o("Expression"), o("SimpleArgs , Expression", function() {
return $1 instanceof Array ? $1.concat([$3]) : [$1].concat([$3]);
if ($1 instanceof Array) {
return $1.concat([$3]);
} else {
return [$1].concat([$3]);
}
})
],
// The variants of *try/catch/finally* exception handling blocks.
@ -641,6 +645,7 @@
}).call(this));
}
return _b;
return null;
}).call(this);
}}
// Initialize the **Parser** with our list of terminal **tokens**, our **grammar**

View File

@ -23,11 +23,10 @@
_a = []; _b = array;
for (_c = 0, _d = _b.length; _c < _d; _c++) {
item = _b[_c];
if (item) {
_a.push(item);
}
item ? _a.push(item) : null;
}
return _a;
return null;
});
// Count the number of occurences of a character in a string.
helpers.count = (count = function count(string, letter) {
@ -70,6 +69,7 @@
_a.push(((object[key] = val)));
}}
return _a;
return null;
});
// Return a completely flattened version of an array. Handy for getting a
// list of `children` from the nodes.
@ -136,6 +136,10 @@
}
throw new Error("SyntaxError: Unterminated " + (levels.pop()[0]) + " starting on line " + (this.line + 1));
}
return !i ? false : str.substring(0, i);
if (!i) {
return false;
} else {
return str.substring(0, i);
}
});
})();

View File

@ -27,7 +27,9 @@
// tokens. Some potential ambiguity in the grammar has been avoided by
// pushing some extra smarts into the Lexer.
exports.Lexer = (function() {
Lexer = function Lexer() { };
Lexer = function Lexer() {
return null;
};
// **tokenize** is the Lexer's main method. Scan by attempting to match tokens
// one at a time, using a regular expression anchored at the start of the
// remaining code, or a custom recursive token-matching method
@ -381,6 +383,7 @@
return this.tag(1, 'PROPERTY_ACCESS');
}
}
return null;
};
// Sanitize a heredoc by escaping internal double quotes and erasing all
// external indentation on the left-hand side.
@ -422,11 +425,13 @@
// an identifier.
Lexer.prototype.identifier_error = function identifier_error(word) {
throw new Error("SyntaxError: Reserved word \"" + word + "\" on line " + (this.line + 1));
return null;
};
// The error for when you try to assign to a reserved word in JavaScript,
// like "function" or "default".
Lexer.prototype.assignment_error = function assignment_error() {
throw new Error("SyntaxError: Reserved word \"" + (this.value()) + "\" on line " + (this.line + 1) + " can't be assigned");
return null;
};
// Expand variables and expressions inside double-quoted strings using
// [ECMA Harmony's interpolation syntax](http://wiki.ecmascript.org/doku.php?id=strawman:string_interpolation)
@ -509,6 +514,7 @@
}
return tokens;
}
return null;
};
// Helpers
// -------
@ -549,9 +555,14 @@
if (!((m = this.chunk.match(regex)))) {
return false;
}
return m ? m[index] : false;
if (m) {
return m[index];
} else {
return false;
}
};
return Lexer;
return null;
}).call(this);
// There are no exensions to the core lexer by default.
Lexer.extensions = [];

View File

@ -35,10 +35,12 @@
return true;
};
if (only) {
return ((klass.prototype.is_pure_statement = function is_pure_statement() {
klass.prototype.is_pure_statement = function is_pure_statement() {
return true;
}));
};
return klass.prototype.is_pure_statement;
}
return null;
};
//### BaseNode
// The **BaseNode** is the abstract base class for all nodes in the syntax tree.
@ -51,7 +53,9 @@
// being requested by the surrounding function), information about the current
// scope, and indentation level.
exports.BaseNode = (function() {
BaseNode = function BaseNode() { };
BaseNode = function BaseNode() {
return null;
};
// 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
@ -69,8 +73,12 @@
del(this.options, 'operation');
}
top = this.top_sensitive() ? this.options.top : del(this.options, 'top');
closure = this.is_statement() && !this.is_pure_statement() && !top && !this.options.returns && !(this instanceof CommentNode);
return closure ? this.compile_closure(this.options) : this.compile_node(this.options);
closure = this.is_statement() && !this.is_pure_statement() && !top && !this.options.as_statement && !(this instanceof CommentNode);
if (closure) {
return this.compile_closure(this.options);
} else {
return this.compile_node(this.options);
}
};
// Statements converted into expressions via closure-wrapping share a scope
// object with their parent closure, to preserve the expected lexical scope.
@ -98,6 +106,15 @@
}
return idt;
};
// construct a node that returns the current node's result
// note that this is overridden for smarter behaviour for
// many statement nodes (eg IfNode, ForNode)
BaseNode.prototype.make_return = function make_return() {
if (this.is_statement()) {
throw new Error("Can't convert statement " + (this) + " into a return value!");
}
return new ReturnNode(this);
};
// Does this node, or any of its children, contain a node of a certain kind?
// Recursively traverses down the *children* of the nodes, yielding to a block
// and returning true when the block finds a match. `contains` does not cross
@ -127,9 +144,11 @@
if (node.traverse) {
return node.traverse(block);
}
return null;
}).call(this));
}
return _a;
return null;
};
// `toString` representation of the node, for inspecting the parse tree.
// This is what `coffee --nodes` prints out.
@ -143,6 +162,7 @@
_a.push(child.toString(idt + TAB));
}
return _a;
return null;
}).call(this).join('');
};
// Default implementations of the common node identification methods. Nodes
@ -161,6 +181,7 @@
return false;
};
return BaseNode;
return null;
}).call(this);
//### Expressions
// The expressions body is the list of expressions that forms the body of an
@ -186,23 +207,62 @@
// If this Expressions consists of just a single node, unwrap it by pulling
// it back out.
Expressions.prototype.unwrap = function unwrap() {
return this.expressions.length === 1 ? this.expressions[0] : this;
if (this.expressions.length === 1) {
return this.expressions[0];
} else {
return this;
}
};
// Is this an empty block of code?
Expressions.prototype.empty = function empty() {
return this.expressions.length === 0;
};
// Is the given node the last one in this block of expressions?
Expressions.prototype.is_last = function is_last(node) {
var l, last_index;
l = this.expressions.length;
last_index = this.expressions[l - 1] instanceof CommentNode ? 2 : 1;
return node === this.expressions[l - last_index];
// make a copy of this node
Expressions.prototype.copy = function copy() {
return new Expressions(this.children.slice());
};
// an Expressions node does not return its entire body, rather it
// ensures that the final expression is returned
Expressions.prototype.make_return = function make_return() {
var _a, _b, _c, _d, converted, i, last_expr, last_expr_idx;
last_expr_idx = -1;
_c = this.expressions.length - 1; _d = 0;
for (_b = 0, i = _c; (_c <= _d ? i <= _d : i >= _d); (_c <= _d ? i += 1 : i -= 1), _b++) {
if (!(this.expressions[i] instanceof CommentNode)) {
last_expr_idx = i;
last_expr = this.expressions[i];
break;
}
}
if (last_expr_idx < 0) {
// just add a return null to ensure return is always called
this.push(new ReturnNode(literal(null)));
} else {
if ((last_expr instanceof ReturnNode)) {
return this;
}
if (last_expr.is_statement()) {
this.push(new ReturnNode(literal(null)));
}
// we still make an attempt at converting statements,
// since many are able to be returned in some fashion
try {
converted = last_expr.make_return();
this.expressions[last_expr_idx] = converted;
} catch (e) {
// ignore
}
}
return this;
};
// An **Expressions** is the only node that can serve as the root.
Expressions.prototype.compile = function compile(o) {
o = o || {};
return o.scope ? Expressions.__superClass__.compile.call(this, o) : this.compile_root(o);
if (o.scope) {
return Expressions.__superClass__.compile.call(this, o);
} else {
return this.compile_root(o);
}
};
Expressions.prototype.compile_node = function compile_node(o) {
var _a, _b, _c, _d, node;
@ -213,6 +273,7 @@
_a.push(this.compile_expression(node, merge(o)));
}
return _a;
return null;
}).call(this).join("\n");
};
// If we happen to be the top-level **Expressions**, wrap everything in
@ -223,7 +284,11 @@
o.scope = new Scope(null, this, null);
code = o.globals ? this.compile_node(o) : this.compile_with_declarations(o);
code = code.replace(TRAILING_WHITESPACE, '');
return o.no_wrap ? code : "(function(){\n" + code + "\n})();\n";
if (o.no_wrap) {
return code;
} else {
return "(function(){\n" + code + "\n})();\n";
}
};
// Compile the expressions body for the contents of a function, with
// declarations of all inner variables pushed up to the top.
@ -242,23 +307,19 @@
// return the result, and it's an expression, simply return it. If it's a
// statement, ask the statement to do so.
Expressions.prototype.compile_expression = function compile_expression(node, o) {
var returns, stmt;
var compiled_node, stmt;
this.tab = o.indent;
stmt = node.is_statement();
returns = del(o, 'returns') && this.is_last(node) && !node.is_pure_statement();
if (!(returns)) {
return (stmt ? '' : this.idt()) + node.compile(merge(o, {
top: true
})) + (stmt ? '' : ';');
compiled_node = node.compile(merge(o, {
top: true
}));
if (stmt) {
return compiled_node;
}
if (node.is_statement()) {
return node.compile(merge(o, {
returns: true
}));
}
return '' + (this.tab) + "return " + (node.compile(o)) + ";";
return '' + (this.idt()) + (compiled_node) + ";";
};
return Expressions;
return null;
}).call(this);
// Wrap up the given nodes as an **Expressions**, unless it already happens
// to be one.
@ -296,6 +357,7 @@
return " \"" + this.value + "\"";
};
return LiteralNode;
return null;
}).call(this);
//### ReturnNode
// A `return` is a *pure_statement* -- wrapping it in a closure wouldn't
@ -308,14 +370,28 @@
__extends(ReturnNode, BaseNode);
ReturnNode.prototype.type = 'Return';
ReturnNode.prototype.compile_node = function compile_node(o) {
var compiled_expr;
if (this.expression.is_statement()) {
return this.expression.compile(merge(o, {
returns: true
}));
return this.compile_statement(o);
} else {
compiled_expr = this.expression.compile(o);
return '' + (this.tab) + "return " + (compiled_expr) + ";";
}
return '' + (this.tab) + "return " + (this.expression.compile(o)) + ";";
return null;
};
ReturnNode.prototype.compile_statement = function compile_statement(o) {
var assign, ret, temp_var;
// split statement into computation and return, via a temporary variable
temp_var = literal(o.scope.free_variable());
assign = new AssignNode(temp_var, this.expression);
ret = new ReturnNode(temp_var);
return [assign.compile(merge(o, {
as_statement: true
})), ret.compile(o)
].join("\n");
};
return ReturnNode;
return null;
}).call(this);
statement(ReturnNode, true);
//### ValueNode
@ -349,10 +425,22 @@
ValueNode.prototype.is_splice = function is_splice() {
return this.has_properties() && this.properties[this.properties.length - 1] instanceof SliceNode;
};
ValueNode.prototype.make_return = function make_return() {
if (!this.has_properties()) {
return this.base.make_return();
} else {
return ValueNode.__superClass__.make_return.call(this);
}
return null;
};
// The value can be unwrapped as its inner node, if there are no attached
// properties.
ValueNode.prototype.unwrap = function unwrap() {
return this.properties.length ? this : this.base;
if (this.properties.length) {
return this;
} else {
return this.base;
}
};
// Values are considered to be statements if their base is a statement.
ValueNode.prototype.is_statement = function is_statement() {
@ -392,9 +480,14 @@
this.last = part;
}
}
return op && soaked ? "(" + complete + ")" : complete;
if (op && soaked) {
return "(" + complete + ")";
} else {
return complete;
}
};
return ValueNode;
return null;
}).call(this);
//### CommentNode
// CoffeeScript passes through comments as JavaScript comments at the
@ -411,6 +504,7 @@
return '' + this.tab + "//" + this.lines.join("\n" + this.tab + "//");
};
return CommentNode;
return null;
}).call(this);
statement(CommentNode);
//### CallNode
@ -419,16 +513,23 @@
exports.CallNode = (function() {
CallNode = function CallNode(variable, args) {
this.children = flatten([(this.variable = variable), (this.args = (args || []))]);
this.prefix = '';
this.is_new = false;
return this;
};
__extends(CallNode, BaseNode);
CallNode.prototype.type = 'Call';
// Tag this invocation as creating a new instance.
CallNode.prototype.new_instance = function new_instance() {
this.prefix = 'new ';
this.is_new = true;
return this;
};
CallNode.prototype.prefix = function prefix() {
if (this.is_new) {
return 'new ';
} else {
return '';
}
};
// Compile a vanilla function call.
CallNode.prototype.compile_node = function compile_node(o) {
var _a, _b, _c, _d, _e, _f, _g, arg, args;
@ -446,11 +547,12 @@
_d.push(arg.compile(o));
}
return _d;
return null;
}).call(this).join(', ');
if (this.variable === 'super') {
return this.compile_super(args, o);
}
return '' + this.prefix + (this.variable.compile(o)) + "(" + args + ")";
return '' + (this.prefix()) + (this.variable.compile(o)) + "(" + args + ")";
};
// `super()` is converted into a call against the superclass's implementation
// of the current function.
@ -471,7 +573,7 @@
obj = temp;
meth = "(" + temp + " = " + (this.variable.source) + ")" + (this.variable.last);
}
return '' + this.prefix + (meth) + ".apply(" + obj + ", " + (this.compile_splat_arguments(o)) + ")";
return '' + (this.prefix()) + (meth) + ".apply(" + obj + ", " + (this.compile_splat_arguments(o)) + ")";
};
// Converts arbitrary number of arguments, mixed with splats, to
// a proper array to pass to an `.apply()` call
@ -501,6 +603,7 @@
return args.join('');
};
return CallNode;
return null;
}).call(this);
//### CurryNode
// Node to bind an context and/or some arguments to a function, returning a new function
@ -532,6 +635,7 @@
return (new ParentheticalNode(new CallNode(curry, [this.meth, this.context, literal(this.arguments(o))]))).compile(o);
};
return CurryNode;
return null;
}).call(this);
//### ExtendsNode
// Node to extend an object's prototype with an ancestor object.
@ -554,6 +658,7 @@
return call.compile(o);
};
return ExtendsNode;
return null;
}).call(this);
//### AccessorNode
// A `.` accessor into a property of a value, or the `::` shorthand for
@ -574,6 +679,7 @@
return "." + proto_part + (this.name.compile(o));
};
return AccessorNode;
return null;
}).call(this);
//### IndexNode
// A `[ ... ]` indexed accessor into an array or object.
@ -591,6 +697,7 @@
return "[" + idx + "]";
};
return IndexNode;
return null;
}).call(this);
//### RangeNode
// A range literal. Ranges can be used to extract portions (slices) of arrays,
@ -644,9 +751,10 @@
source: (new ValueNode(this))
}, literal(name))
]);
return (new ParentheticalNode(new CallNode(new CodeNode([], arr)))).compile(o);
return (new ParentheticalNode(new CallNode(new CodeNode([], arr.make_return())))).compile(o);
};
return RangeNode;
return null;
}).call(this);
//### SliceNode
// An array slice literal. Unlike JavaScript's `Array#slice`, the second parameter
@ -668,6 +776,7 @@
return ".slice(" + from + ", " + to + plus_part + ")";
};
return SliceNode;
return null;
}).call(this);
//### ObjectNode
// An object literal, nothing fancy.
@ -689,11 +798,10 @@
_a = []; _b = this.properties;
for (_c = 0, _d = _b.length; _c < _d; _c++) {
prop = _b[_c];
if (!(prop instanceof CommentNode)) {
_a.push(prop);
}
!(prop instanceof CommentNode) ? _a.push(prop) : null;
}
return _a;
return null;
}).call(this);
last_noncom = non_comments[non_comments.length - 1];
props = (function() {
@ -713,12 +821,14 @@
}).call(this));
}
return _e;
return null;
}).call(this);
props = props.join('');
inner = props ? '\n' + props + '\n' + this.idt() : '';
return "{" + inner + "}";
};
return ObjectNode;
return null;
}).call(this);
//### ArrayNode
// An array literal.
@ -745,37 +855,44 @@
} else {
return '' + code + ", ";
}
return null;
}).call(this));
}
return _a;
return null;
}).call(this);
objects = objects.join('');
ending = objects.indexOf('\n') >= 0 ? "\n" + this.tab + "]" : ']';
return "[" + objects + ending;
};
return ArrayNode;
return null;
}).call(this);
//### ClassNode
// The CoffeeScript class definition.
exports.ClassNode = (function() {
ClassNode = function ClassNode(variable, parent, props) {
this.children = compact(flatten([(this.variable = variable), (this.parent = parent), (this.properties = props || [])]));
this.do_return = false;
return this;
};
__extends(ClassNode, BaseNode);
ClassNode.prototype.type = 'Class';
// Initialize a **ClassNode** with its name, an optional superclass, and a
// list of prototype property assignments.
ClassNode.prototype.make_return = function make_return() {
this.do_return = true;
return this;
};
// Instead of generating the JavaScript string directly, we build up the
// equivalent syntax tree and compile that, in pieces. You can see the
// constructor, property assignments, and inheritance getting built out below.
ClassNode.prototype.compile_node = function compile_node(o) {
var _a, _b, _c, applied, construct, extension, func, prop, props, ret, returns, val;
var _a, _b, _c, applied, construct, extension, func, prop, props, returns, val;
extension = this.parent && new ExtendsNode(this.variable, this.parent);
constructor = null;
props = new Expressions();
o.top = true;
ret = del(o, 'returns');
_a = this.properties;
for (_b = 0, _c = _a.length; _b < _c; _b++) {
prop = _a[_b];
@ -799,13 +916,14 @@
constructor = new AssignNode(this.variable, new CodeNode());
}
}
this.do_return ? (returns = new ReturnNode(this.variable).compile(o)) : (returns = '');
construct = this.idt() + constructor.compile(o) + ';\n';
props = props.empty() ? '' : props.compile(o) + '\n';
extension = extension ? this.idt() + extension.compile(o) + ';\n' : '';
returns = ret ? '\n' + this.idt() + 'return ' + this.variable.compile(o) + ';' : '';
return '' + construct + extension + props + returns;
};
return ClassNode;
return null;
}).call(this);
statement(ClassNode);
//### AssignNode
@ -828,6 +946,9 @@
AssignNode.prototype.is_value = function is_value() {
return this.variable instanceof ValueNode;
};
AssignNode.prototype.make_return = function make_return() {
return new Expressions([this, new ReturnNode(this.variable)]);
};
AssignNode.prototype.is_statement = function is_statement() {
return this.is_value() && (this.variable.is_array() || this.variable.is_object());
};
@ -868,12 +989,9 @@
if (stmt) {
return '' + this.tab + val + ";";
}
if (!top || o.returns) {
if (!top) {
val = "(" + val + ")";
}
if (o.returns) {
val = '' + (this.tab) + "return " + val;
}
return val;
};
// Brief implementation of recursive pattern matching, when assigning array or
@ -908,9 +1026,6 @@
assigns.push(new AssignNode(obj, val).compile(o));
}
code = assigns.join("\n");
if (o.returns) {
code += "\n" + (this.tab) + "return " + (this.variable.compile(o)) + ";";
}
return code;
};
// Compile the assignment from an array splice literal, using JavaScript's
@ -929,6 +1044,7 @@
return '' + (name) + ".splice.apply(" + name + ", [" + from + ", " + to + "].concat(" + val + "))";
};
return AssignNode;
return null;
}).call(this);
//### CodeNode
// A function definition. This is the only node that creates a new Scope.
@ -953,7 +1069,6 @@
shared_scope = del(o, 'shared_scope');
top = del(o, 'top');
o.scope = shared_scope || new Scope(o.scope, this.body, this);
o.returns = true;
o.top = true;
o.indent = this.idt(this.bound ? 2 : 1);
del(o, 'no_wrap');
@ -983,7 +1098,9 @@
_d.push(param.compile(o));
}
return _d;
return null;
}).call(this);
this.body.make_return();
_h = params;
for (_i = 0, _j = _h.length; _i < _j; _i++) {
param = _h[_i];
@ -1019,6 +1136,7 @@
_a.push(child.traverse(block));
}
return _a;
return null;
};
CodeNode.prototype.toString = function toString(idt) {
var _a, _b, _c, _d, child, children;
@ -1030,10 +1148,12 @@
_a.push(child.toString(idt + TAB));
}
return _a;
return null;
}).call(this).join('');
return "\n" + idt + children;
};
return CodeNode;
return null;
}).call(this);
//### SplatNode
// A splat, either as a parameter to a function, an argument to a call,
@ -1050,7 +1170,11 @@
SplatNode.prototype.type = 'Splat';
SplatNode.prototype.compile_node = function compile_node(o) {
var _a;
return (typeof (_a = this.index) !== "undefined" && _a !== null) ? this.compile_param(o) : this.name.compile(o);
if ((typeof (_a = this.index) !== "undefined" && _a !== null)) {
return this.compile_param(o);
} else {
return this.name.compile(o);
}
};
// Compiling a parameter splat means recovering the parameters that succeed
// the splat in the parameter list, by slicing the arguments object.
@ -1073,6 +1197,7 @@
return "Array.prototype.slice.call(" + name + ", " + index + ")";
};
return SplatNode;
return null;
}).call(this);
//### WhileNode
// A while loop, the only sort of low-level loop exposed by CoffeeScript. From
@ -1090,6 +1215,10 @@
this.children.push((this.body = body));
return this;
};
WhileNode.prototype.make_return = function make_return() {
this.do_return = true;
return this;
};
WhileNode.prototype.top_sensitive = function top_sensitive() {
return true;
};
@ -1097,9 +1226,8 @@
// *while* can be used as a part of a larger expression -- while loops may
// return an array containing the computed result of each iteration.
WhileNode.prototype.compile_node = function compile_node(o) {
var cond, post, pre, returns, rvar, set, top;
returns = del(o, 'returns');
top = del(o, 'top') && !returns;
var cond, post, pre, rvar, set, top;
top = del(o, 'top') && !this.do_return;
o.indent = this.idt(1);
o.top = true;
cond = this.condition.compile(o);
@ -1111,7 +1239,6 @@
this.body = PushNode.wrap(rvar, this.body);
}
}
post = returns ? "\n" + (this.tab) + "return " + rvar + ";" : '';
pre = '' + set + (this.tab) + "while (" + cond + ")";
if (!this.body) {
return '' + pre + " null;" + post;
@ -1119,9 +1246,13 @@
if (this.filter) {
this.body = Expressions.wrap([new IfNode(this.filter, this.body)]);
}
return '' + pre + " {\n" + (this.body.compile(o)) + "\n" + this.tab + "}" + post;
this.do_return ? (post = new ReturnNode(literal(rvar)).compile(merge(o, {
indent: this.idt()
}))) : (post = '');
return '' + pre + " {\n" + (this.body.compile(o)) + "\n" + this.tab + "}\n" + post;
};
return WhileNode;
return null;
}).call(this);
statement(WhileNode);
//### OpNode
@ -1231,6 +1362,7 @@
return parts.join('');
};
return OpNode;
return null;
}).call(this);
//### TryNode
// A classic *try/catch/finally* block.
@ -1243,6 +1375,15 @@
};
__extends(TryNode, BaseNode);
TryNode.prototype.type = 'Try';
TryNode.prototype.make_return = function make_return() {
if (this.attempt) {
this.attempt = this.attempt.make_return();
}
if (this.recovery) {
this.recovery = this.recovery.make_return();
}
return this;
};
// Compilation is more or less as you would expect -- the *finally* clause
// is optional, the *catch* is not.
TryNode.prototype.compile_node = function compile_node(o) {
@ -1252,12 +1393,11 @@
attempt_part = this.attempt.compile(o);
error_part = this.error ? " (" + (this.error.compile(o)) + ") " : ' ';
catch_part = this.recovery ? " catch" + error_part + "{\n" + (this.recovery.compile(o)) + "\n" + this.tab + "}" : '';
finally_part = (this.ensure || '') && ' finally {\n' + this.ensure.compile(merge(o, {
returns: null
})) + "\n" + this.tab + "}";
finally_part = (this.ensure || '') && ' finally {\n' + this.ensure.compile(merge(o)) + "\n" + this.tab + "}";
return '' + (this.tab) + "try {\n" + attempt_part + "\n" + this.tab + "}" + catch_part + finally_part;
};
return TryNode;
return null;
}).call(this);
statement(TryNode);
//### ThrowNode
@ -1269,10 +1409,15 @@
};
__extends(ThrowNode, BaseNode);
ThrowNode.prototype.type = 'Throw';
ThrowNode.prototype.make_return = function make_return() {
// a throw is already a return...
return this;
};
ThrowNode.prototype.compile_node = function compile_node(o) {
return '' + (this.tab) + "throw " + (this.expression.compile(o)) + ";";
};
return ThrowNode;
return null;
}).call(this);
statement(ThrowNode);
//### ExistenceNode
@ -1290,6 +1435,7 @@
return ExistenceNode.compile_test(o, this.expression);
};
return ExistenceNode;
return null;
}).call(this);
// The meat of the **ExistenceNode** is in this static `compile_test` method
// because other nodes like to check the existence of their variables as well.
@ -1324,6 +1470,9 @@
ParentheticalNode.prototype.is_statement = function is_statement() {
return this.expression.is_statement();
};
ParentheticalNode.prototype.make_return = function make_return() {
return this.expression.make_return();
};
ParentheticalNode.prototype.compile_node = function compile_node(o) {
var code, l;
code = this.expression.compile(o);
@ -1337,6 +1486,7 @@
return "(" + code + ")";
};
return ParentheticalNode;
return null;
}).call(this);
//### ForNode
// CoffeeScript's replacement for the *for* loop is our array and object
@ -1361,6 +1511,7 @@
this.index = _a[1];
}
this.children = compact([this.body, this.source, this.filter]);
this.do_return = false;
return this;
};
__extends(ForNode, BaseNode);
@ -1368,13 +1519,25 @@
ForNode.prototype.top_sensitive = function top_sensitive() {
return true;
};
ForNode.prototype.make_return = function make_return() {
this.do_return = true;
return this;
};
ForNode.prototype.compile_return_value = function compile_return_value(retvar, o) {
if (this.do_return) {
return new ReturnNode(literal(retvar)).compile(o);
} else {
return retvar || '';
}
return null;
};
// Welcome to the hairiest method in all of CoffeeScript. Handles the inner
// loop, filtering, stepping, and result saving for array, object, and range
// comprehensions. Some of the generated code can be shared in common, and
// some cannot.
ForNode.prototype.compile_node = function compile_node(o) {
var body, body_dent, close, for_part, index, index_var, ivar, lvar, name, range, return_result, rvar, scope, set_result, source, source_part, step_part, svar, top_level, var_part, vars;
top_level = del(o, 'top') && !o.returns;
top_level = del(o, 'top') && !this.do_return;
range = this.source instanceof ValueNode && this.source.base instanceof RangeNode && !this.source.properties.length;
source = range ? this.source.base : this.source;
scope = o.scope;
@ -1415,7 +1578,7 @@
}
}
set_result = rvar ? this.idt() + rvar + ' = []; ' : this.idt();
return_result = rvar || '';
return_result = this.compile_return_value(rvar, o);
if (top_level && body.contains(function(n) {
return n instanceof CodeNode;
})) {
@ -1424,33 +1587,21 @@
if (!(top_level)) {
body = PushNode.wrap(rvar, body);
}
if (o.returns) {
return_result = 'return ' + return_result;
del(o, 'returns');
if (this.filter) {
body = new IfNode(this.filter, body, null, {
statement: true
});
}
} else if (this.filter) {
body = Expressions.wrap([new IfNode(this.filter, body)]);
}
this.filter ? (body = Expressions.wrap([new IfNode(this.filter, body)])) : null;
if (this.object) {
o.scope.assign('__hasProp', 'Object.prototype.hasOwnProperty', true);
for_part = '' + ivar + " in " + svar + ") { if (__hasProp.call(" + svar + ", " + ivar + ")";
}
if (!(top_level)) {
return_result = "\n" + this.tab + return_result + ";";
}
body = body.compile(merge(o, {
indent: body_dent,
top: true
}));
vars = range ? name : '' + name + ", " + ivar;
close = this.object ? '}}\n' : '}\n';
return '' + set_result + (source_part) + "for (" + for_part + ") {\n" + var_part + body + "\n" + this.tab + close + this.tab + return_result;
return '' + set_result + (source_part) + "for (" + for_part + ") {\n" + var_part + body + "\n" + this.tab + close + return_result;
};
return ForNode;
return null;
}).call(this);
statement(ForNode);
//### IfNode
@ -1514,6 +1665,7 @@
} else {
return new OpNode('is', assigner, this.condition);
}
return null;
}).call(this);
if (this.is_chain()) {
this.else_body.rewrite_condition(this.switcher);
@ -1550,10 +1702,27 @@
_a.push(cond.compile(o));
}
return _a;
return null;
}).call(this).join(' || ');
};
IfNode.prototype.compile_node = function compile_node(o) {
return this.is_statement() ? this.compile_statement(o) : this.compile_ternary(o);
if (this.is_statement()) {
return this.compile_statement(o);
} else {
return this.compile_ternary(o);
}
};
IfNode.prototype.make_return = function make_return() {
try {
if (this.body) {
this.body = this.body.make_return();
}
} finally {
if (this.else_body) {
this.else_body = this.else_body.make_return();
}
}
return this;
};
// Compile the **IfNode** as a regular *if-else* statement. Flattened chains
// force inner *else* bodies into statement form.
@ -1564,7 +1733,6 @@
}
child = del(o, 'chain_child');
cond_o = merge(o);
del(cond_o, 'returns');
o.indent = this.idt(1);
o.top = true;
if_dent = child ? '' : this.idt();
@ -1589,6 +1757,7 @@
return '' + if_part + " : " + else_part;
};
return IfNode;
return null;
}).call(this);
// Faux-Nodes
// ----------
@ -1623,7 +1792,11 @@
}
func = new ParentheticalNode(new CodeNode([], Expressions.wrap([expressions])));
call = new CallNode(new ValueNode(func, [new AccessorNode(literal('call'))]), [literal('this')]);
return statement ? Expressions.wrap([call]) : call;
if (statement) {
return Expressions.wrap([call]);
} else {
return call;
}
}
});
// Constants

View File

@ -63,6 +63,7 @@
_d.push(' ');
}
return _d;
return null;
}).call(this).join('') : '';
let_part = rule.short_flag ? rule.short_flag + ', ' : ' ';
lines.push(" " + let_part + (rule.long_flag) + spaces + (rule.description));
@ -70,6 +71,7 @@
return "\n" + (lines.join('\n')) + "\n";
};
return OptionParser;
return null;
}).call(this);
// Helpers
// -------
@ -93,6 +95,7 @@
}).call(this));
}
return _a;
return null;
};
// Build a rule from a `-o` short flag, a `--output [DIR]` long flag, and the
// description of what the option does.

View File

@ -19,7 +19,9 @@
// The **Rewriter** class is used by the [Lexer](lexer.html), directly against
// its internal array of tokens.
exports.Rewriter = (function() {
Rewriter = function Rewriter() { };
Rewriter = function Rewriter() {
return null;
};
// Rewrite the token stream in multiple passes, one logical filter at
// a time. This could certainly be changed into a single pass through the
// stream, with a big ol' efficient switch, but it's much nicer to work with
@ -74,6 +76,7 @@
} else {
return 1;
}
return null;
};
return (function() {
return __func.apply(__this, arguments);
@ -89,6 +92,7 @@
_a.push(this.tokens.shift());
}
return _a;
return null;
};
// Some blocks occur in the middle of expressions -- when we're expecting
// this, remove their trailing newlines.
@ -283,17 +287,18 @@
_a = []; _b = levels;
for (key in _b) { if (__hasProp.call(_b, key)) {
value = _b[key];
if (value > 0) {
_a.push(key);
}
value > 0 ? _a.push(key) : null;
}}
return _a;
return null;
}).call(this);
if (unclosed.length) {
open = unclosed[0];
line = open_line[open] + 1;
throw new Error("unclosed " + open + " on line " + line);
return null;
}
return null;
};
// We'd like to support syntax like this:
// el.click((event) ->
@ -342,6 +347,7 @@
} else {
return 1;
}
return null;
};
return (function() {
return __func.apply(__this, arguments);
@ -349,6 +355,7 @@
})(this));
};
return Rewriter;
return null;
}).call(this);
// Constants
// ---------
@ -371,6 +378,7 @@
_d.push(pair[0]);
}
return _d;
return null;
}).call(this);
// The tokens that signal the end of a balanced pair.
EXPRESSION_END = (function() {
@ -380,6 +388,7 @@
_h.push(pair[1]);
}
return _h;
return null;
}).call(this);
// Tokens that indicate the close of a clause of an expression.
EXPRESSION_CLOSE = ['CATCH', 'WHEN', 'ELSE', 'FINALLY'].concat(EXPRESSION_END);

View File

@ -38,7 +38,8 @@
// Reserve a variable name as originating from a function parameter for this
// scope. No `var` required for internal references.
Scope.prototype.parameter = function parameter(name) {
return this.variables[name] = 'param';
this.variables[name] = 'param';
return this.variables[name];
};
// Just check to see if a variable has already been declared, without reserving.
Scope.prototype.check = function check(name) {
@ -64,10 +65,11 @@
if (top_level && this.parent) {
return this.parent.assign(name, value, top_level);
}
return this.variables[name] = {
this.variables[name] = {
value: value,
assigned: true
};
return this.variables[name];
};
// Does this scope reference any variables that need to be declared in the
// given function body?
@ -86,11 +88,10 @@
_a = []; _b = this.variables;
for (key in _b) { if (__hasProp.call(_b, key)) {
val = _b[key];
if (val === 'var') {
_a.push(key);
}
val === 'var' ? _a.push(key) : null;
}}
return _a;
return null;
}).call(this).sort();
};
// Return the list of assignments that are supposed to be made at the top
@ -100,11 +101,10 @@
_a = []; _b = this.variables;
for (key in _b) { if (__hasProp.call(_b, key)) {
val = _b[key];
if (val.assigned) {
_a.push('' + key + " = " + (val.value));
}
val.assigned ? _a.push('' + key + " = " + (val.value)) : null;
}}
return _a;
return null;
};
// Compile the JavaScript for all of the variable declarations in this scope.
Scope.prototype.compiled_declarations = function compiled_declarations() {
@ -115,5 +115,6 @@
return this.assigned_variables().join(', ');
};
return Scope;
return null;
}).call(this);
})();

View File

@ -56,7 +56,7 @@ exports.BaseNode: class BaseNode
del @options, 'operation' unless this instanceof ValueNode
top: if @top_sensitive() then @options.top else del @options, 'top'
closure: @is_statement() and not @is_pure_statement() and not top and
not @options.returns and not (this instanceof CommentNode)
not @options.as_statement and not (this instanceof CommentNode)
if closure then @compile_closure(@options) else @compile_node(@options)
# Statements converted into expressions via closure-wrapping share a scope
@ -81,6 +81,14 @@ exports.BaseNode: class BaseNode
idt += TAB while num -= 1
idt
# construct a node that returns the current node's result
# note that this is overridden for smarter behaviour for
# many statement nodes (eg IfNode, ForNode)
make_return: ->
if @is_statement()
throw new Error("Can't convert statement ${this} into a return value!")
return new ReturnNode(this)
# Does this node, or any of its children, contain a node of a certain kind?
# Recursively traverses down the *children* of the nodes, yielding to a block
# and returning true when the block finds a match. `contains` does not cross
@ -141,11 +149,36 @@ exports.Expressions: class Expressions extends BaseNode
empty: ->
@expressions.length is 0
# Is the given node the last one in this block of expressions?
is_last: (node) ->
l: @expressions.length
last_index: if @expressions[l - 1] instanceof CommentNode then 2 else 1
node is @expressions[l - last_index]
# make a copy of this node
copy: ->
new Expressions(@children.slice())
# an Expressions node does not return its entire body, rather it
# ensures that the final expression is returned
make_return: ->
last_expr_idx: -1
for i in [@expressions.length-1 .. 0]
if not (@expressions[i] instanceof CommentNode)
last_expr_idx: i
last_expr: @expressions[i]
break
if last_expr_idx < 0
# just add a return null to ensure return is always called
@push(new ReturnNode(literal(null)))
else
return this if (last_expr instanceof ReturnNode)
@push(new ReturnNode(literal(null))) if last_expr.is_statement()
# we still make an attempt at converting statements,
# since many are able to be returned in some fashion
try
converted = last_expr.make_return()
@expressions[last_expr_idx] = converted
catch e
# ignore
return this
# An **Expressions** is the only node that can serve as the root.
compile: (o) ->
@ -178,10 +211,9 @@ exports.Expressions: class Expressions extends BaseNode
compile_expression: (node, o) ->
@tab: o.indent
stmt: node.is_statement()
returns: del(o, 'returns') and @is_last(node) and not node.is_pure_statement()
return (if stmt then '' else @idt()) + node.compile(merge(o, {top: true})) + (if stmt then '' else ';') unless returns
return node.compile(merge(o, {returns: true})) if node.is_statement()
"${@tab}return ${node.compile(o)};"
compiled_node = node.compile(merge(o, {top:true}))
return compiled_node if stmt
return "${@idt()}${compiled_node};"
# Wrap up the given nodes as an **Expressions**, unless it already happens
# to be one.
@ -227,8 +259,18 @@ exports.ReturnNode: class ReturnNode extends BaseNode
@children: [@expression: expression]
compile_node: (o) ->
return @expression.compile(merge(o, {returns: true})) if @expression.is_statement()
"${@tab}return ${@expression.compile(o)};"
if @expression.is_statement()
return @compile_statement(o)
else
compiled_expr: @expression.compile(o)
return "${@tab}return ${compiled_expr};"
compile_statement: (o) ->
# split statement into computation and return, via a temporary variable
temp_var: literal(o.scope.free_variable())
assign: new AssignNode(temp_var, @expression)
ret: new ReturnNode(temp_var)
[assign.compile(merge(o, {as_statement:true})), ret.compile(o)].join("\n")
statement ReturnNode, true
@ -265,6 +307,12 @@ exports.ValueNode: class ValueNode extends BaseNode
is_splice: ->
@has_properties() and @properties[@properties.length - 1] instanceof SliceNode
make_return: ->
if not @has_properties()
return @base.make_return()
else
return super()
# The value can be unwrapped as its inner node, if there are no attached
# properties.
unwrap: ->
@ -329,20 +377,23 @@ exports.CallNode: class CallNode extends BaseNode
constructor: (variable, args) ->
@children: flatten [@variable: variable, @args: (args or [])]
@prefix: ''
@is_new: false
# Tag this invocation as creating a new instance.
new_instance: ->
@prefix: 'new '
@is_new: true
this
prefix: ->
if @is_new then 'new ' else ''
# Compile a vanilla function call.
compile_node: (o) ->
for arg in @args
return @compile_splat(o) if arg instanceof SplatNode
args: (arg.compile(o) for arg in @args).join(', ')
return @compile_super(args, o) if @variable is 'super'
"$@prefix${@variable.compile(o)}($args)"
"${@prefix()}${@variable.compile(o)}($args)"
# `super()` is converted into a call against the superclass's implementation
# of the current function.
@ -363,7 +414,7 @@ exports.CallNode: class CallNode extends BaseNode
temp: o.scope.free_variable()
obj: temp
meth: "($temp = ${ @variable.source })${ @variable.last }"
"$@prefix${meth}.apply($obj, ${ @compile_splat_arguments(o) })"
"${@prefix()}${meth}.apply($obj, ${ @compile_splat_arguments(o) })"
# Converts arbitrary number of arguments, mixed with splats, to
# a proper array to pass to an `.apply()` call
@ -511,7 +562,7 @@ exports.RangeNode: class RangeNode extends BaseNode
name: o.scope.free_variable()
body: Expressions.wrap([literal(name)])
arr: Expressions.wrap([new ForNode(body, {source: (new ValueNode(this))}, literal(name))])
(new ParentheticalNode(new CallNode(new CodeNode([], arr)))).compile(o)
(new ParentheticalNode(new CallNode(new CodeNode([], arr.make_return())))).compile(o)
#### SliceNode
@ -592,6 +643,11 @@ exports.ClassNode: class ClassNode extends BaseNode
# list of prototype property assignments.
constructor: (variable, parent, props) ->
@children: compact flatten [@variable: variable, @parent: parent, @properties: props or []]
@do_return: false
make_return: ->
@do_return: true
this
# Instead of generating the JavaScript string directly, we build up the
# equivalent syntax tree and compile that, in pieces. You can see the
@ -601,7 +657,6 @@ exports.ClassNode: class ClassNode extends BaseNode
constructor: null
props: new Expressions()
o.top: true
ret: del o, 'returns'
for prop in @properties
if prop.variable and prop.variable.base.value is 'constructor'
@ -623,10 +678,14 @@ exports.ClassNode: class ClassNode extends BaseNode
else
constructor: new AssignNode(@variable, new CodeNode())
if @do_return
returns: new ReturnNode(@variable).compile(o)
else
returns: ''
construct: @idt() + constructor.compile(o) + ';\n'
props: if props.empty() then '' else props.compile(o) + '\n'
extension: if extension then @idt() + extension.compile(o) + ';\n' else ''
returns: if ret then '\n' + @idt() + 'return ' + @variable.compile(o) + ';' else ''
"$construct$extension$props$returns"
statement ClassNode
@ -652,6 +711,9 @@ exports.AssignNode: class AssignNode extends BaseNode
is_value: ->
@variable instanceof ValueNode
make_return: ->
return new Expressions([this, new ReturnNode(@variable)])
is_statement: ->
@is_value() and (@variable.is_array() or @variable.is_object())
@ -676,9 +738,8 @@ exports.AssignNode: class AssignNode extends BaseNode
o.scope.find name unless @is_value() and @variable.has_properties()
val: "$name = $val"
return "$@tab$val;" if stmt
val: "($val)" if not top or o.returns
val: "${@tab}return $val" if o.returns
val
val: "($val)" if not top
return val
# Brief implementation of recursive pattern matching, when assigning array or
# object literals to a value. Peeks at their properties to assign inner names.
@ -701,7 +762,6 @@ exports.AssignNode: class AssignNode extends BaseNode
val: new ValueNode(literal(val_var), [new access_class(idx)])
assigns.push(new AssignNode(obj, val).compile(o))
code: assigns.join("\n")
code += "\n${@tab}return ${ @variable.compile(o) };" if o.returns
code
# Compile the assignment from an array splice literal, using JavaScript's
@ -738,7 +798,6 @@ exports.CodeNode: class CodeNode extends BaseNode
shared_scope: del o, 'shared_scope'
top: del o, 'top'
o.scope: shared_scope or new Scope(o.scope, @body, this)
o.returns: true
o.top: true
o.indent: @idt(if @bound then 2 else 1)
del o, 'no_wrap'
@ -758,6 +817,7 @@ exports.CodeNode: class CodeNode extends BaseNode
params.push(param)
i += 1
params: (param.compile(o) for param in params)
@body.make_return()
(o.scope.parameter(param)) for param in params
code: if @body.expressions.length then "\n${ @body.compile_with_declarations(o) }\n" else ''
name_part: if @name then ' ' + @name else ''
@ -831,6 +891,10 @@ exports.WhileNode: class WhileNode extends BaseNode
@children.push @body: body
this
make_return: ->
@do_return: true
this
top_sensitive: ->
true
@ -838,8 +902,7 @@ exports.WhileNode: class WhileNode extends BaseNode
# *while* can be used as a part of a larger expression -- while loops may
# return an array containing the computed result of each iteration.
compile_node: (o) ->
returns: del(o, 'returns')
top: del(o, 'top') and not returns
top: del(o, 'top') and not @do_return
o.indent: @idt(1)
o.top: true
cond: @condition.compile(o)
@ -848,11 +911,14 @@ exports.WhileNode: class WhileNode extends BaseNode
rvar: o.scope.free_variable()
set: "$@tab$rvar = [];\n"
@body: PushNode.wrap(rvar, @body) if @body
post: if returns then "\n${@tab}return $rvar;" else ''
pre: "$set${@tab}while ($cond)"
return "$pre null;$post" if not @body
return "$pre null;$post" if not @body
@body: Expressions.wrap([new IfNode(@filter, @body)]) if @filter
"$pre {\n${ @body.compile(o) }\n$@tab}$post"
if @do_return
post: new ReturnNode(literal(rvar)).compile(merge(o, {indent: @idt()}))
else
post: ''
"$pre {\n${ @body.compile(o) }\n$@tab}\n$post"
statement WhileNode
@ -949,6 +1015,11 @@ exports.TryNode: class TryNode extends BaseNode
@error: error
this
make_return: ->
@attempt: @attempt.make_return() if @attempt
@recovery: @recovery.make_return() if @recovery
this
# Compilation is more or less as you would expect -- the *finally* clause
# is optional, the *catch* is not.
compile_node: (o) ->
@ -957,7 +1028,7 @@ exports.TryNode: class TryNode extends BaseNode
attempt_part: @attempt.compile(o)
error_part: if @error then " (${ @error.compile(o) }) " else ' '
catch_part: if @recovery then " catch$error_part{\n${ @recovery.compile(o) }\n$@tab}" else ''
finally_part: (@ensure or '') and ' finally {\n' + @ensure.compile(merge(o, {returns: null})) + "\n$@tab}"
finally_part: (@ensure or '') and ' finally {\n' + @ensure.compile(merge(o)) + "\n$@tab}"
"${@tab}try {\n$attempt_part\n$@tab}$catch_part$finally_part"
statement TryNode
@ -971,6 +1042,10 @@ exports.ThrowNode: class ThrowNode extends BaseNode
constructor: (expression) ->
@children: [@expression: expression]
make_return: ->
# a throw is already a return...
return this
compile_node: (o) ->
"${@tab}throw ${@expression.compile(o)};"
@ -1016,6 +1091,8 @@ exports.ParentheticalNode: class ParentheticalNode extends BaseNode
is_statement: ->
@expression.is_statement()
make_return: -> @expression.make_return()
compile_node: (o) ->
code: @expression.compile(o)
return code if @is_statement()
@ -1045,16 +1122,27 @@ exports.ForNode: class ForNode extends BaseNode
@object: !!source.object
[@name, @index]: [@index, @name] if @object
@children: compact [@body, @source, @filter]
@do_return: false
top_sensitive: ->
true
make_return: ->
@do_return: true
this
compile_return_value: (retvar, o) ->
if @do_return
return new ReturnNode(literal(retvar)).compile(o)
else
return retvar or ''
# Welcome to the hairiest method in all of CoffeeScript. Handles the inner
# loop, filtering, stepping, and result saving for array, object, and range
# comprehensions. Some of the generated code can be shared in common, and
# some cannot.
compile_node: (o) ->
top_level: del(o, 'top') and not o.returns
top_level: del(o, 'top') and not @do_return
range: @source instanceof ValueNode and @source.base instanceof RangeNode and not @source.properties.length
source: if range then @source.base else @source
scope: o.scope
@ -1082,23 +1170,19 @@ exports.ForNode: class ForNode extends BaseNode
step_part: if @step then "$ivar += ${ @step.compile(o) }" else "$ivar++"
for_part: "$ivar = 0, $lvar = ${svar}.length; $ivar < $lvar; $step_part"
set_result: if rvar then @idt() + rvar + ' = []; ' else @idt()
return_result: rvar or ''
return_result: @compile_return_value(rvar, o)
body: ClosureNode.wrap(body, true) if top_level and body.contains (n) -> n instanceof CodeNode
body: PushNode.wrap(rvar, body) unless top_level
if o.returns
return_result: 'return ' + return_result
del o, 'returns'
body: new IfNode(@filter, body, null, {statement: true}) if @filter
else if @filter
if @filter
body: Expressions.wrap([new IfNode(@filter, body)])
if @object
o.scope.assign('__hasProp', 'Object.prototype.hasOwnProperty', true)
for_part: "$ivar in $svar) { if (__hasProp.call($svar, $ivar)"
return_result: "\n$@tab$return_result;" unless top_level
body: body.compile(merge(o, {indent: body_dent, top: true}))
vars: if range then name else "$name, $ivar"
close: if @object then '}}\n' else '}\n'
"$set_result${source_part}for ($for_part) {\n$var_part$body\n$@tab$close$@tab$return_result"
"$set_result${source_part}for ($for_part) {\n$var_part$body\n$@tab$close$return_result"
statement ForNode
@ -1178,13 +1262,19 @@ exports.IfNode: class IfNode extends BaseNode
compile_node: (o) ->
if @is_statement() then @compile_statement(o) else @compile_ternary(o)
make_return: ->
try
@body: @body.make_return() if @body
finally
@else_body: @else_body.make_return() if @else_body
return this
# Compile the **IfNode** as a regular *if-else* statement. Flattened chains
# force inner *else* bodies into statement form.
compile_statement: (o) ->
@rewrite_switch(o) if @switcher
child: del o, 'chain_child'
cond_o: merge o
del cond_o, 'returns'
o.indent: @idt(1)
o.top: true
if_dent: if child then '' else @idt()