From 80230414a2c82e596d8443492d9a52dde1fe7bde Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 21 Mar 2010 11:28:05 -0400 Subject: [PATCH] merging in gfxmonk's major refactor to the way that returns are pushed down into the interior of expressions --- Cakefile | 32 ++---- lib/cake.js | 3 - lib/coffee-script.js | 3 - lib/command.js | 10 -- lib/grammar.js | 1 - lib/helpers.js | 2 - lib/lexer.js | 9 +- lib/nodes.js | 259 +++++++++++++++---------------------------- lib/optparse.js | 3 - lib/rewriter.js | 13 +-- lib/scope.js | 3 - src/nodes.coffee | 144 ++++++++++-------------- 12 files changed, 162 insertions(+), 320 deletions(-) diff --git a/Cakefile b/Cakefile index 7203b8f8..07dd1d28 100644 --- a/Cakefile +++ b/Cakefile @@ -71,12 +71,11 @@ task 'doc:underscore', 'rebuild the Underscore.coffee documentation page', -> task 'test', 'run the CoffeeScript language test suite', -> helpers.extend global, require 'assert' - passed_test_count: 0 - failed_test_count: 0 + passed_tests: failed_tests: 0 start_time: new Date() - [original_ok, original_throws]: [ok, throws] + original_ok: ok helpers.extend global, { - ok: (args...) -> passed_test_count += 1; original_ok(args...) + ok: (args...) -> passed_tests += 1; original_ok(args...) CoffeeScript: CoffeeScript } red: '\033[0;31m' @@ -84,24 +83,17 @@ task 'test', 'run the CoffeeScript language test suite', -> reset: '\033[0m' on_exit: -> time: ((new Date() - start_time) / 1000).toFixed(2) - if failed_test_count > 0 - puts "${red}FAILED " + failed_test_count + " and passed " + passed_test_count + " tests in " + time + " seconds${reset}" - else - puts "${green}passed " + passed_test_count + " tests in " + time + " seconds${reset}" + message: "passed $passed_tests tests in $time seconds$reset" + puts(if failed_tests then "${red}failed $failed_tests and $message" else "$green$message") process.addListener 'exit', on_exit fs.readdir 'test', (err, files) -> files.forEach (file) -> return unless file.match(/\.coffee$/i) source: path.join 'test', file - print " $file " - code = fs.readFileSync source - try - CoffeeScript.run code, {source: source} - puts "${green}ok${reset}" - catch err - failed_test_count += 1 - puts "${red}FAILED!${reset}\n" - puts "Failed test: $source" - puts err - puts '' - process.exit(if failed_test_count == 0 then 0 else 1) + fs.readFile source, (err, code) -> + try + CoffeeScript.run code, {source: source} + catch err + failed_tests += 1 + puts "${red}failed:${reset} $source" + puts err.stack diff --git a/lib/cake.js b/lib/cake.js index aba51535..058f0081 100755 --- a/lib/cake.js +++ b/lib/cake.js @@ -68,7 +68,6 @@ _a.push(invoke(arg)); } return _a; - return null; }); }; // Display the list of Cake tasks in a format similar to `rake -T` @@ -85,14 +84,12 @@ _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) { diff --git a/lib/coffee-script.js b/lib/coffee-script.js index 51e24aad..af5e4a61 100644 --- a/lib/coffee-script.js +++ b/lib/coffee-script.js @@ -37,9 +37,7 @@ 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) { @@ -103,7 +101,6 @@ 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); diff --git a/lib/command.js b/lib/command.js index ab2e9135..3365eb3b 100644 --- a/lib/command.js +++ b/lib/command.js @@ -74,7 +74,6 @@ _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 @@ -100,18 +99,14 @@ } 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**. @@ -123,7 +118,6 @@ if (string) { return code += string; } - return null; }); return process.stdio.addListener('close', function() { return compile_script('stdio', code); @@ -153,7 +147,6 @@ _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 @@ -174,13 +167,11 @@ 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(); @@ -200,7 +191,6 @@ }).call(this)); } return _a; - return null; }).call(this); return puts(strings.join(' ')); }; diff --git a/lib/grammar.js b/lib/grammar.js index 934e8571..efae7f0f 100644 --- a/lib/grammar.js +++ b/lib/grammar.js @@ -645,7 +645,6 @@ }).call(this)); } return _b; - return null; }).call(this); }} // Initialize the **Parser** with our list of terminal **tokens**, our **grammar** diff --git a/lib/helpers.js b/lib/helpers.js index 5b40b39a..2e13105a 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -26,7 +26,6 @@ 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) { @@ -69,7 +68,6 @@ _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. diff --git a/lib/lexer.js b/lib/lexer.js index 1f59d5e7..d64cd111 100644 --- a/lib/lexer.js +++ b/lib/lexer.js @@ -27,9 +27,7 @@ // tokens. Some potential ambiguity in the grammar has been avoided by // pushing some extra smarts into the Lexer. exports.Lexer = (function() { - Lexer = function Lexer() { - return null; - }; + Lexer = function Lexer() { }; // **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 @@ -388,7 +386,6 @@ 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. @@ -430,13 +427,11 @@ // 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) @@ -519,7 +514,6 @@ } return tokens; } - return null; }; // Helpers // ------- @@ -573,7 +567,6 @@ return this.value() && this.value().match && this.value().match(NO_NEWLINE) && prev && (prev[0] !== '.') && !this.value().match(CODE); }; return Lexer; - return null; }).call(this); // There are no exensions to the core lexer by default. Lexer.extensions = []; diff --git a/lib/nodes.js b/lib/nodes.js index d3026d66..50dc0907 100644 --- a/lib/nodes.js +++ b/lib/nodes.js @@ -40,7 +40,6 @@ }; return klass.prototype.is_pure_statement; } - return null; }; //### BaseNode // The **BaseNode** is the abstract base class for all nodes in the syntax tree. @@ -53,9 +52,7 @@ // being requested by the surrounding function), information about the current // scope, and indentation level. exports.BaseNode = (function() { - BaseNode = function BaseNode() { - return null; - }; + BaseNode = function BaseNode() { }; // 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 @@ -73,10 +70,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) && !(this.contains(function(n) { - return n.is_pure_statement(); - })); - 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) && !this.contains_pure_statement(); + 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. @@ -104,13 +103,10 @@ } 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) + // Construct a node that returns the current node's result. + // Note that this is overridden for smarter behavior 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? @@ -131,6 +127,13 @@ } return false; }; + // Convenience for the most common use of contains. Does the node contain + // a pure statement? + BaseNode.prototype.contains_pure_statement = function contains_pure_statement() { + return this.is_pure_statement() || this.contains(function(n) { + return n.is_pure_statement(); + }); + }; // Perform an in-order traversal of the AST. Crosses scope boundaries. BaseNode.prototype.traverse = function traverse(block) { var _a, _b, _c, _d, node; @@ -142,11 +145,9 @@ 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. @@ -160,7 +161,6 @@ _a.push(child.toString(idt + TAB)); } return _a; - return null; }).call(this).join(''); }; // Default implementations of the common node identification methods. Nodes @@ -179,7 +179,6 @@ return false; }; return BaseNode; - return null; }).call(this); //### Expressions // The expressions body is the list of expressions that forms the body of an @@ -215,41 +214,24 @@ Expressions.prototype.empty = function empty() { return this.expressions.length === 0; }; - // make a copy of this node + // 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 + // 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; - } + var idx, last; + idx = this.expressions.length - 1; + last = this.expressions[idx]; + if (last instanceof CommentNode) { + last = this.expressions[idx -= 1]; } - 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 - } + if (!last || last instanceof ReturnNode) { + return this; + } + if (!(last.contains_pure_statement())) { + this.expressions[idx] = last.make_return(); } return this; }; @@ -271,7 +253,6 @@ _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 @@ -305,19 +286,18 @@ // 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 compiled_node, stmt; + var compiled_node; this.tab = o.indent; - stmt = node.is_statement(); compiled_node = node.compile(merge(o, { top: true })); - if (stmt) { + if (node.is_statement()) { return compiled_node; + } else { + return '' + (this.idt()) + compiled_node + ";"; } - 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. @@ -355,7 +335,6 @@ return " \"" + this.value + "\""; }; return LiteralNode; - return null; }).call(this); //### ReturnNode // A `return` is a *pure_statement* -- wrapping it in a closure wouldn't @@ -368,28 +347,12 @@ __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.compile_statement(o); - } else { - compiled_expr = this.expression.compile(o); - return '' + (this.tab) + "return " + (compiled_expr) + ";"; + o.as_statement = true; } - 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 '' + (this.tab) + "return " + (this.expression.compile(o)) + ";"; }; return ReturnNode; - return null; }).call(this); statement(ReturnNode, true); //### ValueNode @@ -424,12 +387,11 @@ 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 { + if (this.has_properties()) { return ValueNode.__superClass__.make_return.call(this); + } else { + return this.base.make_return(); } - return null; }; // The value can be unwrapped as its inner node, if there are no attached // properties. @@ -485,7 +447,6 @@ } }; return ValueNode; - return null; }).call(this); //### CommentNode // CoffeeScript passes through comments as JavaScript comments at the @@ -498,11 +459,13 @@ }; __extends(CommentNode, BaseNode); CommentNode.prototype.type = 'Comment'; + CommentNode.prototype.make_return = function make_return() { + return this; + }; CommentNode.prototype.compile_node = function compile_node(o) { return '' + this.tab + "//" + this.lines.join("\n" + this.tab + "//"); }; return CommentNode; - return null; }).call(this); statement(CommentNode); //### CallNode @@ -510,8 +473,15 @@ // calls against the prototype's function of the same name. exports.CallNode = (function() { CallNode = function CallNode(variable, args) { - this.children = flatten([(this.variable = variable), (this.args = (args || []))]); this.is_new = false; + this.is_super = variable === 'super'; + this.variable = this.is_super ? null : variable; + this.children = compact(flatten([this.variable, (this.args = (args || []))])); + this.compile_splat_arguments = (function(func, obj, args) { + return function() { + return func.apply(obj, args.concat(Array.prototype.slice.call(arguments, 0))); + }; + }(SplatNode.compile_mixed_array, this, [this.args])); return this; }; __extends(CallNode, BaseNode); @@ -545,9 +515,8 @@ _d.push(arg.compile(o)); } return _d; - return null; }).call(this).join(', '); - if (this.variable === 'super') { + if (this.is_super) { return this.compile_super(args, o); } return '' + (this.prefix()) + (this.variable.compile(o)) + "(" + args + ")"; @@ -574,7 +543,6 @@ return '' + (this.prefix()) + (meth) + ".apply(" + obj + ", " + (this.compile_splat_arguments(o)) + ")"; }; return CallNode; - return null; }).call(this); //### CurryNode // Node to bind an context and/or some arguments to a function, returning a new function @@ -583,9 +551,9 @@ CurryNode = function CurryNode(meth, args) { this.children = flatten([(this.meth = meth), (this.context = args[0]), (this.args = (args.slice(1) || []))]); this.compile_splat_arguments = (function(func, obj, args) { - return (function() { + return function() { return func.apply(obj, args.concat(Array.prototype.slice.call(arguments, 0))); - }); + }; }(SplatNode.compile_mixed_array, this, [this.args])); return this; }; @@ -611,7 +579,6 @@ 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. @@ -634,7 +601,6 @@ return call.compile(o); }; return ExtendsNode; - return null; }).call(this); //### AccessorNode // A `.` accessor into a property of a value, or the `::` shorthand for @@ -655,7 +621,6 @@ return "." + proto_part + (this.name.compile(o)); }; return AccessorNode; - return null; }).call(this); //### IndexNode // A `[ ... ]` indexed accessor into an array or object. @@ -673,7 +638,6 @@ return "[" + idx + "]"; }; return IndexNode; - return null; }).call(this); //### RangeNode // A range literal. Ranges can be used to extract portions (slices) of arrays, @@ -730,7 +694,6 @@ 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 @@ -752,7 +715,6 @@ return ".slice(" + from + ", " + to + plus_part + ")"; }; return SliceNode; - return null; }).call(this); //### ObjectNode // An object literal, nothing fancy. @@ -777,7 +739,6 @@ !(prop instanceof CommentNode) ? _a.push(prop) : null; } return _a; - return null; }).call(this); last_noncom = non_comments[non_comments.length - 1]; props = (function() { @@ -797,14 +758,12 @@ }).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. @@ -812,9 +771,9 @@ ArrayNode = function ArrayNode(objects) { this.children = (this.objects = objects || []); this.compile_splat_literal = (function(func, obj, args) { - return (function() { + return function() { return func.apply(obj, args.concat(Array.prototype.slice.call(arguments, 0))); - }); + }; }(SplatNode.compile_mixed_array, this, [this.objects])); return this; }; @@ -823,38 +782,33 @@ ArrayNode.prototype.compile_node = function compile_node(o) { var _a, _b, code, ending, i, obj, objects; o.indent = this.idt(1); - objects = (function() { - _a = []; _b = this.objects; - for (i = 0, _c = _b.length; i < _c; i++) { - obj = _b[i]; - _a.push((function() { - code = obj.compile(o); - if (obj instanceof CommentNode) { - return "\n" + code + "\n" + o.indent; - } else if (i === this.objects.length - 1) { - return code; - } else { - return '' + code + ", "; - } - return null; - }).call(this)); + objects = []; + _a = this.objects; + for (i = 0, _b = _a.length; i < _b; i++) { + obj = _a[i]; + code = obj.compile(o); + if (obj instanceof SplatNode) { + return this.compile_splat_literal(this.objects, o); + } else if (obj instanceof CommentNode) { + objects.push("\n" + code + "\n" + o.indent); + } else if (i === this.objects.length - 1) { + objects.push(code); + } else { + objects.push('' + code + ", "); } - 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; + this.returns = false; return this; }; __extends(ClassNode, BaseNode); @@ -862,7 +816,7 @@ // 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; + this.returns = true; return this; }; // Instead of generating the JavaScript string directly, we build up the @@ -897,14 +851,13 @@ 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 = this.returns ? new ReturnNode(this.variable).compile(o) : ''; return '' + construct + extension + props + returns; }; return ClassNode; - return null; }).call(this); statement(ClassNode); //### AssignNode @@ -970,10 +923,11 @@ if (stmt) { return '' + this.tab + val + ";"; } - if (!top) { - val = "(" + val + ")"; + if (top) { + return val; + } else { + return "(" + val + ")"; } - 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. @@ -1025,7 +979,6 @@ 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. @@ -1079,7 +1032,6 @@ _d.push(param.compile(o)); } return _d; - return null; }).call(this); this.body.make_return(); _h = params; @@ -1117,7 +1069,6 @@ _a.push(child.traverse(block)); } return _a; - return null; }; CodeNode.prototype.toString = function toString(idt) { var _a, _b, _c, _d, child, children; @@ -1129,12 +1080,10 @@ _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, @@ -1178,7 +1127,6 @@ return "Array.prototype.slice.call(" + name + ", " + index + ")"; }; return SplatNode; - return null; }).call(this); // Utility function that converts arbitrary number of elements, mixed with // splats, to a proper array @@ -1224,7 +1172,7 @@ return this; }; WhileNode.prototype.make_return = function make_return() { - this.do_return = true; + this.returns = true; return this; }; WhileNode.prototype.top_sensitive = function top_sensitive() { @@ -1235,7 +1183,7 @@ // return an array containing the computed result of each iteration. WhileNode.prototype.compile_node = function compile_node(o) { var cond, post, pre, rvar, set, top; - top = del(o, 'top') && !this.do_return; + top = del(o, 'top') && !this.returns; o.indent = this.idt(1); o.top = true; cond = this.condition.compile(o); @@ -1254,13 +1202,12 @@ if (this.filter) { this.body = Expressions.wrap([new IfNode(this.filter, this.body)]); } - this.do_return ? (post = new ReturnNode(literal(rvar)).compile(merge(o, { + this.returns ? (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 @@ -1370,7 +1317,6 @@ return parts.join(''); }; return OpNode; - return null; }).call(this); //### TryNode // A classic *try/catch/finally* block. @@ -1405,7 +1351,6 @@ return '' + (this.tab) + "try {\n" + attempt_part + "\n" + this.tab + "}" + catch_part + finally_part; }; return TryNode; - return null; }).call(this); statement(TryNode); //### ThrowNode @@ -1417,15 +1362,14 @@ }; __extends(ThrowNode, BaseNode); ThrowNode.prototype.type = 'Throw'; + // A **ThrowNode** is already a return, of sorts... 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 @@ -1443,7 +1387,6 @@ 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. @@ -1494,7 +1437,6 @@ return "(" + code + ")"; }; return ParentheticalNode; - return null; }).call(this); //### ForNode // CoffeeScript's replacement for the *for* loop is our array and object @@ -1519,7 +1461,7 @@ this.index = _a[1]; } this.children = compact([this.body, this.source, this.filter]); - this.do_return = false; + this.returns = false; return this; }; __extends(ForNode, BaseNode); @@ -1528,16 +1470,14 @@ return true; }; ForNode.prototype.make_return = function make_return() { - this.do_return = true; + this.returns = 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 || ''; + ForNode.prototype.compile_return_value = function compile_return_value(val, o) { + if (this.returns) { + return new ReturnNode(literal(val)).compile(o); } - return null; + return val || ''; }; // Welcome to the hairiest method in all of CoffeeScript. Handles the inner // loop, filtering, stepping, and result saving for array, object, and range @@ -1545,7 +1485,7 @@ // 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') && !this.do_return; + top_level = del(o, 'top') && !this.returns; range = this.source instanceof ValueNode && this.source.base instanceof RangeNode && !this.source.properties.length; source = range ? this.source.base : this.source; scope = o.scope; @@ -1609,7 +1549,6 @@ 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 @@ -1622,7 +1561,7 @@ this.condition = condition; this.body = body && body.unwrap(); this.else_body = else_body && else_body.unwrap(); - this.children = compact([this.condition, this.body, this.else_body]); + this.children = compact(flatten([this.condition, this.body, this.else_body])); this.tags = tags || {}; if (this.condition instanceof Array) { this.multiple = true; @@ -1673,7 +1612,6 @@ } else { return new OpNode('is', assigner, this.condition); } - return null; }).call(this); if (this.is_chain()) { this.else_body.rewrite_condition(this.switcher); @@ -1710,7 +1648,6 @@ _a.push(cond.compile(o)); } return _a; - return null; }).call(this).join(' || '); }; IfNode.prototype.compile_node = function compile_node(o) { @@ -1721,15 +1658,8 @@ } }; 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(); - } - } + this.body = this.body && this.body.make_return(); + this.else_body = this.else_body && this.else_body.make_return(); return this; }; // Compile the **IfNode** as a regular *if-else* statement. Flattened chains @@ -1765,7 +1695,6 @@ return '' + if_part + " : " + else_part; }; return IfNode; - return null; }).call(this); // Faux-Nodes // ---------- @@ -1778,9 +1707,7 @@ wrap: function wrap(array, expressions) { var expr; expr = expressions.unwrap(); - if (expr.is_pure_statement() || expr.contains(function(n) { - return n.is_pure_statement(); - })) { + if (expr.is_pure_statement() || expr.contains_pure_statement()) { return expressions; } return Expressions.wrap([new CallNode(new ValueNode(literal(array), [new AccessorNode(literal('push'))]), [expr])]); @@ -1793,9 +1720,7 @@ // in which case, no dice. wrap: function wrap(expressions, statement) { var call, func; - if (expressions.contains(function(n) { - return n.is_pure_statement(); - })) { + if (expressions.contains_pure_statement()) { return expressions; } func = new ParentheticalNode(new CodeNode([], Expressions.wrap([expressions]))); diff --git a/lib/optparse.js b/lib/optparse.js index f8105588..a88ad049 100755 --- a/lib/optparse.js +++ b/lib/optparse.js @@ -63,7 +63,6 @@ _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)); @@ -71,7 +70,6 @@ return "\n" + (lines.join('\n')) + "\n"; }; return OptionParser; - return null; }).call(this); // Helpers // ------- @@ -95,7 +93,6 @@ }).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. diff --git a/lib/rewriter.js b/lib/rewriter.js index 42120840..c3e77745 100644 --- a/lib/rewriter.js +++ b/lib/rewriter.js @@ -19,9 +19,7 @@ // The **Rewriter** class is used by the [Lexer](lexer.html), directly against // its internal array of tokens. exports.Rewriter = (function() { - Rewriter = function Rewriter() { - return null; - }; + Rewriter = function Rewriter() { }; // 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 @@ -76,7 +74,6 @@ } else { return 1; } - return null; }; return (function() { return __func.apply(__this, arguments); @@ -92,7 +89,6 @@ _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. @@ -290,15 +286,12 @@ 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) -> @@ -347,7 +340,6 @@ } else { return 1; } - return null; }; return (function() { return __func.apply(__this, arguments); @@ -355,7 +347,6 @@ })(this)); }; return Rewriter; - return null; }).call(this); // Constants // --------- @@ -378,7 +369,6 @@ _d.push(pair[0]); } return _d; - return null; }).call(this); // The tokens that signal the end of a balanced pair. EXPRESSION_END = (function() { @@ -388,7 +378,6 @@ _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); diff --git a/lib/scope.js b/lib/scope.js index c6085542..efa2d64c 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -91,7 +91,6 @@ 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 @@ -104,7 +103,6 @@ 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,6 +113,5 @@ return this.assigned_variables().join(', '); }; return Scope; - return null; }).call(this); })(); diff --git a/src/nodes.coffee b/src/nodes.coffee index f8d974d5..5fbcb88c 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -57,7 +57,7 @@ exports.BaseNode: class BaseNode 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.as_statement and not (this instanceof CommentNode) and - not (@contains (n) -> n.is_pure_statement()) + not @contains_pure_statement() if closure then @compile_closure(@options) else @compile_node(@options) # Statements converted into expressions via closure-wrapping share a scope @@ -82,13 +82,11 @@ 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) + # Construct a node that returns the current node's result. + # Note that this is overridden for smarter behavior 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) + 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 @@ -100,6 +98,11 @@ exports.BaseNode: class BaseNode return true if node.contains and node.contains block false + # Convenience for the most common use of contains. Does the node contain + # a pure statement? + contains_pure_statement: -> + @is_pure_statement() or @contains (n) -> n.is_pure_statement() + # Perform an in-order traversal of the AST. Crosses scope boundaries. traverse: (block) -> for node in @children @@ -150,36 +153,19 @@ exports.Expressions: class Expressions extends BaseNode empty: -> @expressions.length is 0 - # make a copy of this node + # Make a copy of this node. copy: -> - new Expressions(@children.slice()) + new Expressions @children.slice() - # an Expressions node does not return its entire body, rather it - # ensures that the final expression is returned + # 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 - + idx: @expressions.length - 1 + last: @expressions[idx] + last: @expressions[idx -= 1] if last instanceof CommentNode + return this if not last or last instanceof ReturnNode + @expressions[idx]: last.make_return() unless last.contains_pure_statement() + this # An **Expressions** is the only node that can serve as the root. compile: (o) -> @@ -211,10 +197,8 @@ exports.Expressions: class Expressions extends BaseNode # statement, ask the statement to do so. compile_expression: (node, o) -> @tab: o.indent - stmt: node.is_statement() - compiled_node = node.compile(merge(o, {top:true})) - return compiled_node if stmt - return "${@idt()}${compiled_node};" + compiled_node: node.compile merge o, {top: true} + if node.is_statement() then compiled_node else "${@idt()}$compiled_node;" # Wrap up the given nodes as an **Expressions**, unless it already happens # to be one. @@ -260,18 +244,8 @@ exports.ReturnNode: class ReturnNode extends BaseNode @children: [@expression: expression] compile_node: (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") + o.as_statement: true if @expression.is_statement() + "${@tab}return ${@expression.compile(o)};" statement ReturnNode, true @@ -309,10 +283,7 @@ exports.ValueNode: class ValueNode extends BaseNode @has_properties() and @properties[@properties.length - 1] instanceof SliceNode make_return: -> - if not @has_properties() - return @base.make_return() - else - return super() + if @has_properties() then super() else @base.make_return() # The value can be unwrapped as its inner node, if there are no attached # properties. @@ -364,6 +335,9 @@ exports.CommentNode: class CommentNode extends BaseNode @lines: lines this + make_return: -> + this + compile_node: (o) -> "$@tab//" + @lines.join("\n$@tab//") @@ -377,9 +351,11 @@ exports.CallNode: class CallNode extends BaseNode type: 'Call' constructor: (variable, args) -> - @children: flatten [@variable: variable, @args: (args or [])] + @is_new: false + @is_super: variable is 'super' + @variable: if @is_super then null else variable + @children: compact flatten [@variable, @args: (args or [])] @compile_splat_arguments: SplatNode.compile_mixed_array <- @, @args - @is_new: false # Tag this invocation as creating a new instance. new_instance: -> @@ -394,7 +370,7 @@ exports.CallNode: class CallNode extends BaseNode 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' + return @compile_super(args, o) if @is_super "${@prefix()}${@variable.compile(o)}($args)" # `super()` is converted into a call against the superclass's implementation @@ -629,10 +605,10 @@ 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 + @returns: false make_return: -> - @do_return: true + @returns: true this # Instead of generating the JavaScript string directly, we build up the @@ -664,14 +640,10 @@ 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 @returns then new ReturnNode(@variable).compile(o) else '' "$construct$extension$props$returns" statement ClassNode @@ -698,7 +670,7 @@ exports.AssignNode: class AssignNode extends BaseNode @variable instanceof ValueNode make_return: -> - return new Expressions([this, new ReturnNode(@variable)]) + return new Expressions [this, new ReturnNode(@variable)] is_statement: -> @is_value() and (@variable.is_array() or @variable.is_object()) @@ -724,8 +696,7 @@ 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 - return val + if top then val else "($val)" # Brief implementation of recursive pattern matching, when assigning array or # object literals to a value. Peeks at their properties to assign inner names. @@ -899,7 +870,7 @@ exports.WhileNode: class WhileNode extends BaseNode this make_return: -> - @do_return: true + @returns: true this top_sensitive: -> @@ -909,7 +880,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) -> - top: del(o, 'top') and not @do_return + top: del(o, 'top') and not @returns o.indent: @idt(1) o.top: true cond: @condition.compile(o) @@ -921,7 +892,7 @@ exports.WhileNode: class WhileNode extends BaseNode pre: "$set${@tab}while ($cond)" return "$pre null;$post" if not @body @body: Expressions.wrap([new IfNode(@filter, @body)]) if @filter - if @do_return + if @returns post: new ReturnNode(literal(rvar)).compile(merge(o, {indent: @idt()})) else post: '' @@ -1049,8 +1020,8 @@ exports.ThrowNode: class ThrowNode extends BaseNode constructor: (expression) -> @children: [@expression: expression] + # A **ThrowNode** is already a return, of sorts... make_return: -> - # a throw is already a return... return this compile_node: (o) -> @@ -1098,7 +1069,8 @@ exports.ParentheticalNode: class ParentheticalNode extends BaseNode is_statement: -> @expression.is_statement() - make_return: -> @expression.make_return() + make_return: -> + @expression.make_return() compile_node: (o) -> code: @expression.compile(o) @@ -1129,27 +1101,25 @@ exports.ForNode: class ForNode extends BaseNode @object: !!source.object [@name, @index]: [@index, @name] if @object @children: compact [@body, @source, @filter] - @do_return: false + @returns: false top_sensitive: -> true make_return: -> - @do_return: true + @returns: true this - compile_return_value: (retvar, o) -> - if @do_return - return new ReturnNode(literal(retvar)).compile(o) - else - return retvar or '' + compile_return_value: (val, o) -> + return new ReturnNode(literal(val)).compile(o) if @returns + val 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 @do_return + top_level: del(o, 'top') and not @returns 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 @@ -1207,7 +1177,7 @@ exports.IfNode: class IfNode extends BaseNode @condition: condition @body: body and body.unwrap() @else_body: else_body and else_body.unwrap() - @children: compact [@condition, @body, @else_body] + @children: compact flatten [@condition, @body, @else_body] @tags: tags or {} @multiple: true if @condition instanceof Array @condition: new OpNode('!', new ParentheticalNode(@condition)) if @tags.invert @@ -1270,11 +1240,9 @@ exports.IfNode: class IfNode extends BaseNode 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 + @body &&= @body.make_return() + @else_body &&= @else_body.make_return() + this # Compile the **IfNode** as a regular *if-else* statement. Flattened chains # force inner *else* bodies into statement form. @@ -1315,7 +1283,7 @@ PushNode: exports.PushNode: { wrap: (array, expressions) -> expr: expressions.unwrap() - return expressions if expr.is_pure_statement() or expr.contains (n) -> n.is_pure_statement() + return expressions if expr.is_pure_statement() or expr.contains_pure_statement() Expressions.wrap([new CallNode( new ValueNode(literal(array), [new AccessorNode(literal('push'))]), [expr] )]) @@ -1330,7 +1298,7 @@ ClosureNode: exports.ClosureNode: { # Wrap the expressions body, unless it contains a pure statement, # in which case, no dice. wrap: (expressions, statement) -> - return expressions if expressions.contains (n) -> n.is_pure_statement() + return expressions if expressions.contains_pure_statement() func: new ParentheticalNode(new CodeNode([], Expressions.wrap([expressions]))) call: new CallNode(new ValueNode(func, [new AccessorNode(literal('call'))]), [literal('this')]) if statement then Expressions.wrap([call]) else call