From 241f6f3068cdefc65016ba06783df6d0ae50963e Mon Sep 17 00:00:00 2001 From: matehat Date: Tue, 30 Mar 2010 09:02:51 -0400 Subject: [PATCH] Applied the utility factoring into a "Coffeescript" object to the core. All tests pass fast. --- lib/cake.js | 6 ++- lib/grammar.js | 6 ++- lib/helpers.js | 10 ++-- lib/nodes.js | 119 ++++++++++++++++++++----------------------- lib/rewriter.js | 79 ++++++++++------------------ lib/scope.js | 71 ++++++++++++++++++++++---- lib/utilities.js | 15 ++++++ src/nodes.coffee | 30 +++-------- src/scope.coffee | 36 +++++++++++-- src/utilities.coffee | 27 ++++++++++ 10 files changed, 240 insertions(+), 159 deletions(-) create mode 100644 lib/utilities.js create mode 100644 src/utilities.coffee diff --git a/lib/cake.js b/lib/cake.js index fa509517..c98a6355 100755 --- a/lib/cake.js +++ b/lib/cake.js @@ -1,6 +1,8 @@ (function(){ var CoffeeScript, fs, helpers, no_such_task, oparse, options, optparse, path, print_tasks, switches, tasks; - var __hasProp = Object.prototype.hasOwnProperty; + var __hasProp = Object.prototype.hasOwnProperty, Coffeescript = { + hasProp: Object.prototype.hasOwnProperty + }; // `cake` is a simplified version of [Make](http://www.gnu.org/software/make/) // ([Rake](http://rake.rubyforge.org/), [Jake](http://github.com/280north/jake)) // for CoffeeScript. You define tasks with names and descriptions in a Cakefile, @@ -75,7 +77,7 @@ var _a, _b, _c, _d, _e, i, name, spaces, task; puts(''); _a = tasks; - for (name in _a) { if (__hasProp.call(_a, name)) { + for (name in _a) { if (Coffeescript.hasProp.call(_a, name)) { task = _a[name]; spaces = 20 - name.length; spaces = spaces > 0 ? (function() { diff --git a/lib/grammar.js b/lib/grammar.js index b8ae934e..d3a87eae 100644 --- a/lib/grammar.js +++ b/lib/grammar.js @@ -1,6 +1,8 @@ (function(){ var Parser, _a, _b, _c, _d, _e, _f, _g, _h, alt, alternatives, grammar, name, o, operators, token, tokens, unwrap; - var __hasProp = Object.prototype.hasOwnProperty; + var __hasProp = Object.prototype.hasOwnProperty, Coffeescript = { + hasProp: Object.prototype.hasOwnProperty + }; // The CoffeeScript parser is generated by [Jison](http://github.com/zaach/jison) // from this grammar file. Jison is a bottom-up parser generator, similar in // style to [Bison](http://www.gnu.org/software/bison), implemented in JavaScript. @@ -664,7 +666,7 @@ // as "tokens". tokens = []; _a = grammar; - for (name in _a) { if (__hasProp.call(_a, name)) { + for (name in _a) { if (Coffeescript.hasProp.call(_a, name)) { alternatives = _a[name]; grammar[name] = (function() { _b = []; _d = alternatives; diff --git a/lib/helpers.js b/lib/helpers.js index d262227f..07923d60 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -1,6 +1,8 @@ (function(){ var balanced_string, compact, count, del, extend, flatten, helpers, include, merge, starts; - var __hasProp = Object.prototype.hasOwnProperty; + var __hasProp = Object.prototype.hasOwnProperty, Coffeescript = { + hasProp: Object.prototype.hasOwnProperty + }; // This file contains the common helper functions that we'd like to share among // the **Lexer**, **Rewriter**, and the **Nodes**. Merge objects, flatten // arrays, count characters, that sort of thing. @@ -45,13 +47,13 @@ var _a, _b, fresh, key, val; fresh = {}; _a = options; - for (key in _a) { if (__hasProp.call(_a, key)) { + for (key in _a) { if (Coffeescript.hasProp.call(_a, key)) { val = _a[key]; (fresh[key] = val); }} if (overrides) { _b = overrides; - for (key in _b) { if (__hasProp.call(_b, key)) { + for (key in _b) { if (Coffeescript.hasProp.call(_b, key)) { val = _b[key]; (fresh[key] = val); }} @@ -63,7 +65,7 @@ helpers.extend = (extend = function extend(object, properties) { var _a, _b, key, val; _a = []; _b = properties; - for (key in _b) { if (__hasProp.call(_b, key)) { + for (key in _b) { if (Coffeescript.hasProp.call(_b, key)) { val = _b[key]; _a.push((object[key] = val)); }} diff --git a/lib/nodes.js b/lib/nodes.js index 5fc7066c..12bc48b5 100644 --- a/lib/nodes.js +++ b/lib/nodes.js @@ -1,11 +1,21 @@ (function(){ var AccessorNode, ArrayNode, AssignNode, BaseNode, CallNode, ClassNode, ClosureNode, CodeNode, CommentNode, CurryNode, ExistenceNode, Expressions, ExtendsNode, ForNode, IDENTIFIER, IfNode, IndexNode, LiteralNode, ObjectNode, OpNode, ParentheticalNode, PushNode, RangeNode, ReturnNode, Scope, SliceNode, SplatNode, TAB, TRAILING_WHITESPACE, ThrowNode, TryNode, ValueNode, WhileNode, compact, del, flatten, helpers, literal, merge, statement; - var __extends = function(child, parent) { - var ctor = function(){ }; - ctor.prototype = parent.prototype; - child.__superClass__ = parent.prototype; - child.prototype = new ctor(); - child.prototype.constructor = child; + var Coffeescript = { + extend: function(child, parent) { + var ctor = function(){ }; + ctor.prototype = parent.prototype; + child.__superClass__ = parent.prototype; + child.prototype = new ctor(); + child.prototype.constructor = child; + }, + bind: function(func, obj, args) { + obj = obj || {}; + return (typeof args !== "undefined" && args !== null) ? function() { + return func.apply(obj, args.concat(Array.prototype.slice.call(arguments, 0))); + } : function() { + return func.apply(obj, arguments); + }; + } }; // `nodes.coffee` contains all of the node classes for the syntax tree. Most // nodes are created as the result of actions in the [grammar](grammar.html), @@ -189,7 +199,7 @@ this.children = (this.expressions = compact(flatten(nodes || []))); return this; }; - __extends(Expressions, BaseNode); + Coffeescript.extend(Expressions, BaseNode); Expressions.prototype.type = 'Expressions'; // Tack an expression on to the end of this expression list. Expressions.prototype.push = function push(node) { @@ -275,7 +285,7 @@ var code; code = this.compile_node(o); if (o.scope.has_assignments(this)) { - code = "" + (this.tab) + "var " + (o.scope.compiled_assignments()) + ";\n" + code; + code = "" + (this.tab) + "var " + (o.scope.compiled_assignments(this.tab)) + ";\n" + code; } if (o.scope.has_declarations(this)) { code = "" + (this.tab) + "var " + (o.scope.compiled_declarations()) + ";\n" + code; @@ -317,7 +327,7 @@ this.value = value; return this; }; - __extends(LiteralNode, BaseNode); + Coffeescript.extend(LiteralNode, BaseNode); LiteralNode.prototype.type = 'Literal'; // Break and continue must be treated as pure statements -- they lose their // meaning when wrapped in a closure. @@ -344,7 +354,7 @@ this.children = [(this.expression = expression)]; return this; }; - __extends(ReturnNode, BaseNode); + Coffeescript.extend(ReturnNode, BaseNode); ReturnNode.prototype.type = 'Return'; ReturnNode.prototype.top_sensitive = function top_sensitive() { return true; @@ -372,7 +382,7 @@ this.children = flatten([(this.base = base), (this.properties = (properties || []))]); return this; }; - __extends(ValueNode, BaseNode); + Coffeescript.extend(ValueNode, BaseNode); ValueNode.prototype.type = 'Value'; ValueNode.prototype.SOAK = " == undefined ? undefined : "; // A **ValueNode** has a base and a list of property accesses. @@ -466,7 +476,7 @@ this; return this; }; - __extends(CommentNode, BaseNode); + Coffeescript.extend(CommentNode, BaseNode); CommentNode.prototype.type = 'Comment'; CommentNode.prototype.make_return = function make_return() { return this; @@ -486,14 +496,10 @@ 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])); + this.compile_splat_arguments = Coffeescript.bind(SplatNode.compile_mixed_array, this, [this.args]); return this; }; - __extends(CallNode, BaseNode); + Coffeescript.extend(CallNode, BaseNode); CallNode.prototype.type = 'Call'; // Tag this invocation as creating a new instance. CallNode.prototype.new_instance = function new_instance() { @@ -560,14 +566,10 @@ exports.CurryNode = (function() { 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 func.apply(obj, args.concat(Array.prototype.slice.call(arguments, 0))); - }; - }(SplatNode.compile_mixed_array, this, [this.args])); + this.compile_splat_arguments = Coffeescript.bind(SplatNode.compile_mixed_array, this, [this.args]); return this; }; - __extends(CurryNode, CallNode); + Coffeescript.extend(CurryNode, CallNode); CurryNode.prototype.type = 'Curry'; CurryNode.prototype.body = 'func.apply(obj, args.concat(Array.prototype.slice.call(arguments, 0)))'; CurryNode.prototype.arguments = function arguments(o) { @@ -582,11 +584,9 @@ return (new ArrayNode(this.args)).compile(o); }; CurryNode.prototype.compile_node = function compile_node(o) { - var body, curried, curry; - body = Expressions.wrap([literal(this.body)]); - curried = new CodeNode([], body); - curry = new CodeNode([literal('func'), literal('obj'), literal('args')], Expressions.wrap([curried])); - return (new ParentheticalNode(new CallNode(curry, [this.meth, this.context, literal(this.arguments(o))]))).compile(o); + var ref; + ref = new ValueNode(literal(o.scope.utility('bind'))); + return (new CallNode(ref, [this.meth, this.context, literal(this.arguments(o))])).compile(o); }; return CurryNode; }).call(this); @@ -599,16 +599,13 @@ this.children = [(this.child = child), (this.parent = parent)]; return this; }; - __extends(ExtendsNode, BaseNode); + Coffeescript.extend(ExtendsNode, BaseNode); ExtendsNode.prototype.type = 'Extends'; - ExtendsNode.prototype.code = 'function(child, parent) {\n var ctor = function(){ };\n ctor.prototype = parent.prototype;\n child.__superClass__ = parent.prototype;\n child.prototype = new ctor();\n child.prototype.constructor = child;\n }'; // Hooks one constructor into another's prototype chain. ExtendsNode.prototype.compile_node = function compile_node(o) { - var call, ref; - o.scope.assign('__extends', this.code, true); - ref = new ValueNode(literal('__extends')); - call = new CallNode(ref, [this.child, this.parent]); - return call.compile(o); + var ref; + ref = new ValueNode(literal(o.scope.utility('extend'))); + return (new CallNode(ref, [this.child, this.parent])).compile(o); }; return ExtendsNode; }).call(this); @@ -623,7 +620,7 @@ this; return this; }; - __extends(AccessorNode, BaseNode); + Coffeescript.extend(AccessorNode, BaseNode); AccessorNode.prototype.type = 'Accessor'; AccessorNode.prototype.compile_node = function compile_node(o) { var proto_part; @@ -640,7 +637,7 @@ this.soak_node = tag === 'soak'; return this; }; - __extends(IndexNode, BaseNode); + Coffeescript.extend(IndexNode, BaseNode); IndexNode.prototype.type = 'Index'; IndexNode.prototype.compile_node = function compile_node(o) { var idx; @@ -659,7 +656,7 @@ this.exclusive = !!exclusive; return this; }; - __extends(RangeNode, BaseNode); + Coffeescript.extend(RangeNode, BaseNode); RangeNode.prototype.type = 'Range'; // Compiles the range's source variables -- where it starts and where it ends. RangeNode.prototype.compile_variables = function compile_variables(o) { @@ -715,7 +712,7 @@ this; return this; }; - __extends(SliceNode, BaseNode); + Coffeescript.extend(SliceNode, BaseNode); SliceNode.prototype.type = 'Slice'; SliceNode.prototype.compile_node = function compile_node(o) { var from, plus_part, to; @@ -733,7 +730,7 @@ this.children = (this.objects = (this.properties = props || [])); return this; }; - __extends(ObjectNode, BaseNode); + Coffeescript.extend(ObjectNode, BaseNode); ObjectNode.prototype.type = 'Object'; // All the mucking about with commas is to make sure that CommentNodes and // AssignNodes get interleaved correctly, with no trailing commas or @@ -780,14 +777,10 @@ exports.ArrayNode = (function() { ArrayNode = function ArrayNode(objects) { this.children = (this.objects = objects || []); - this.compile_splat_literal = (function(func, obj, args) { - return function() { - return func.apply(obj, args.concat(Array.prototype.slice.call(arguments, 0))); - }; - }(SplatNode.compile_mixed_array, this, [this.objects])); + this.compile_splat_literal = Coffeescript.bind(SplatNode.compile_mixed_array, this, [this.objects]); return this; }; - __extends(ArrayNode, BaseNode); + Coffeescript.extend(ArrayNode, BaseNode); ArrayNode.prototype.type = 'Array'; ArrayNode.prototype.compile_node = function compile_node(o) { var _a, _b, code, ending, i, obj, objects; @@ -821,7 +814,7 @@ this.returns = false; return this; }; - __extends(ClassNode, BaseNode); + Coffeescript.extend(ClassNode, BaseNode); ClassNode.prototype.type = 'Class'; // Initialize a **ClassNode** with its name, an optional superclass, and a // list of prototype property assignments. @@ -881,7 +874,7 @@ this.context = context; return this; }; - __extends(AssignNode, BaseNode); + Coffeescript.extend(AssignNode, BaseNode); AssignNode.prototype.type = 'Assign'; // Matchers for detecting prototype assignments. AssignNode.prototype.PROTO_ASSIGN = /^(\S+)\.prototype/; @@ -1005,7 +998,7 @@ this.bound = tag === 'boundfunc'; return this; }; - __extends(CodeNode, BaseNode); + Coffeescript.extend(CodeNode, BaseNode); CodeNode.prototype.type = 'Code'; // Compilation creates a new scope unless explicitly asked to share with the // outer scope. Handles splat parameters in the parameter list by peeking at @@ -1013,7 +1006,7 @@ // arrow, generates a wrapper that saves the current value of `this` through // a closure. CodeNode.prototype.compile_node = function compile_node(o) { - var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, code, func, i, inner, name_part, param, params, shared_scope, splat, top; + var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, code, func, i, name_part, param, params, ref, shared_scope, splat, top; shared_scope = del(o, 'shared_scope'); top = del(o, 'top'); o.scope = shared_scope || new Scope(o.scope, this.body, this); @@ -1062,8 +1055,8 @@ if (!(this.bound)) { return func; } - inner = "(function" + name_part + "() {\n" + (this.idt(2)) + "return __func.apply(__this, arguments);\n" + (this.idt(1)) + "});"; - return "(function(__this) {\n" + (this.idt(1)) + "var __func = " + func + ";\n" + (this.idt(1)) + "return " + inner + "\n" + this.tab + "})(this)"; + ref = new ValueNode(literal(o.scope.utility('bind'))); + return (new CallNode(ref, [literal(func), literal('this')])).compile(o); }; CodeNode.prototype.top_sensitive = function top_sensitive() { return true; @@ -1110,7 +1103,7 @@ this.children = [(this.name = name)]; return this; }; - __extends(SplatNode, BaseNode); + Coffeescript.extend(SplatNode, BaseNode); SplatNode.prototype.type = 'Splat'; SplatNode.prototype.compile_node = function compile_node(o) { var _a; @@ -1183,7 +1176,7 @@ this.filter = opts && opts.filter; return this; }; - __extends(WhileNode, BaseNode); + Coffeescript.extend(WhileNode, BaseNode); WhileNode.prototype.type = 'While'; WhileNode.prototype.add_body = function add_body(body) { this.children.push((this.body = body)); @@ -1239,7 +1232,7 @@ this.flip = !!flip; return this; }; - __extends(OpNode, BaseNode); + Coffeescript.extend(OpNode, BaseNode); OpNode.prototype.type = 'Op'; // The map of conversions from CoffeeScript to JavaScript symbols. OpNode.prototype.CONVERSIONS = { @@ -1340,7 +1333,7 @@ this; return this; }; - __extends(TryNode, BaseNode); + Coffeescript.extend(TryNode, BaseNode); TryNode.prototype.type = 'Try'; TryNode.prototype.make_return = function make_return() { if (this.attempt) { @@ -1373,7 +1366,7 @@ this.children = [(this.expression = expression)]; return this; }; - __extends(ThrowNode, BaseNode); + Coffeescript.extend(ThrowNode, BaseNode); ThrowNode.prototype.type = 'Throw'; // A **ThrowNode** is already a return, of sorts... ThrowNode.prototype.make_return = function make_return() { @@ -1394,7 +1387,7 @@ this.children = [(this.expression = expression)]; return this; }; - __extends(ExistenceNode, BaseNode); + Coffeescript.extend(ExistenceNode, BaseNode); ExistenceNode.prototype.type = 'Existence'; ExistenceNode.prototype.compile_node = function compile_node(o) { return ExistenceNode.compile_test(o, this.expression); @@ -1429,7 +1422,7 @@ this.children = [(this.expression = expression)]; return this; }; - __extends(ParentheticalNode, BaseNode); + Coffeescript.extend(ParentheticalNode, BaseNode); ParentheticalNode.prototype.type = 'Paren'; ParentheticalNode.prototype.is_statement = function is_statement() { return this.expression.is_statement(); @@ -1481,7 +1474,7 @@ this.returns = false; return this; }; - __extends(ForNode, BaseNode); + Coffeescript.extend(ForNode, BaseNode); ForNode.prototype.type = 'For'; ForNode.prototype.top_sensitive = function top_sensitive() { return true; @@ -1555,7 +1548,7 @@ 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 + ")"; + for_part = "" + ivar + " in " + svar + ") { if (" + (o.scope.utility('hasProp')) + ".call(" + svar + ", " + ivar + ")"; } body = body.compile(merge(o, { indent: body_dent, @@ -1588,7 +1581,7 @@ } return this; }; - __extends(IfNode, BaseNode); + Coffeescript.extend(IfNode, BaseNode); IfNode.prototype.type = 'If'; // Add a new *else* clause to this **IfNode**, or push it down to the bottom // of the chain recursively. diff --git a/lib/rewriter.js b/lib/rewriter.js index bb041198..7a6d58e8 100644 --- a/lib/rewriter.js +++ b/lib/rewriter.js @@ -1,6 +1,16 @@ (function(){ var BALANCED_PAIRS, EXPRESSION_CLOSE, EXPRESSION_END, EXPRESSION_START, IMPLICIT_BLOCK, IMPLICIT_CALL, IMPLICIT_END, IMPLICIT_FUNC, INVERSES, Rewriter, SINGLE_CLOSERS, SINGLE_LINERS, _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, helpers, include, pair; - var __hasProp = Object.prototype.hasOwnProperty; + var __hasProp = Object.prototype.hasOwnProperty, Coffeescript = { + bind: function(func, obj, args) { + obj = obj || {}; + return (typeof args !== "undefined" && args !== null) ? function() { + return func.apply(obj, args.concat(Array.prototype.slice.call(arguments, 0))); + } : function() { + return func.apply(obj, arguments); + }; + }, + hasProp: Object.prototype.hasOwnProperty + }; // The CoffeeScript language has a good deal of optional syntax, implicit syntax, // and shorthand syntax. This can greatly complicate a grammar and bloat // the resulting parse table. Instead of making the parser handle it all, we take @@ -57,8 +67,7 @@ // Massage newlines and indentations so that comments don't have to be // correctly indented, or appear on a line of their own. Rewriter.prototype.adjust_comments = function adjust_comments() { - return this.scan_tokens((function(__this) { - var __func = function(prev, token, post, i) { + return this.scan_tokens(Coffeescript.bind(function(prev, token, post, i) { var after; if (!(token[0] === 'COMMENT')) { return 1; @@ -74,11 +83,7 @@ } else { return 1; } - }; - return (function() { - return __func.apply(__this, arguments); - }); - })(this)); + }, this)); }; // Leading newlines would introduce an ambiguity in the grammar, so we // dispatch them here. @@ -93,18 +98,13 @@ // Some blocks occur in the middle of expressions -- when we're expecting // this, remove their trailing newlines. Rewriter.prototype.remove_mid_expression_newlines = function remove_mid_expression_newlines() { - return this.scan_tokens((function(__this) { - var __func = function(prev, token, post, i) { + return this.scan_tokens(Coffeescript.bind(function(prev, token, post, i) { if (!(post && include(EXPRESSION_CLOSE, post[0]) && token[0] === 'TERMINATOR')) { return 1; } this.tokens.splice(i, 1); return 0; - }; - return (function() { - return __func.apply(__this, arguments); - }); - })(this)); + }, this)); }; // The lexer has tagged the opening parenthesis of a method call, and the // opening bracket of an indexing operation. Match them with their paired @@ -113,8 +113,7 @@ var brackets, parens; parens = [0]; brackets = [0]; - return this.scan_tokens((function(__this) { - var __func = function(prev, token, post, i) { + return this.scan_tokens(Coffeescript.bind(function(prev, token, post, i) { var _a; if ((_a = token[0]) === 'CALL_START') { parens.push(0); @@ -140,11 +139,7 @@ } } return 1; - }; - return (function() { - return __func.apply(__this, arguments); - }); - })(this)); + }, this)); }; // Methods may be optionally called without parentheses, for simple cases. // Insert the implicit parentheses here, so that the parser doesn't have to @@ -154,8 +149,7 @@ stack = [0]; calls = 0; parens = 0; - return this.scan_tokens((function(__this) { - var __func = function(prev, token, post, i) { + return this.scan_tokens(Coffeescript.bind(function(prev, token, post, i) { var _a, _b, _c, idx, last, open, size, stack_pointer, tag, tmp; tag = token[0]; if (tag === 'CALL_START') { @@ -199,19 +193,14 @@ this.tokens.splice(i, 0, ['CALL_START', '(', token[2]]); stack[stack.length - 1] += 1; return 2; - }; - return (function() { - return __func.apply(__this, arguments); - }); - })(this)); + }, this)); }; // Because our grammar is LALR(1), it can't handle some single-line // expressions that lack ending delimiters. The **Rewriter** adds the implicit // blocks, so it doesn't need to. ')' can close a single-line block, // but we need to make sure it's balanced. Rewriter.prototype.add_implicit_indentation = function add_implicit_indentation() { - return this.scan_tokens((function(__this) { - var __func = function(prev, token, post, i) { + return this.scan_tokens(Coffeescript.bind(function(prev, token, post, i) { var idx, insertion, outdent, parens, pre, starter, tok; if (!(include(SINGLE_LINERS, token[0]) && post[0] !== 'INDENT' && !(token[0] === 'ELSE' && post[0] === 'IF'))) { return 1; @@ -243,11 +232,7 @@ } this.tokens.splice(i, 1); return 0; - }; - return (function() { - return __func.apply(__this, arguments); - }); - })(this)); + }, this)); }; // Ensure that all listed pairs of tokens are correctly balanced throughout // the course of the token stream. @@ -255,8 +240,7 @@ var _a, _b, key, levels, line, open, open_line, unclosed, value; levels = {}; open_line = {}; - this.scan_tokens((function(__this) { - var __func = function(prev, token, post, i) { + this.scan_tokens(Coffeescript.bind(function(prev, token, post, i) { var _a, _b, _c, _d, close, open, pair; _b = pairs; for (_a = 0, _c = _b.length; _a < _c; _a++) { @@ -279,14 +263,10 @@ } } return 1; - }; - return (function() { - return __func.apply(__this, arguments); - }); - })(this)); + }, this)); unclosed = (function() { _a = []; _b = levels; - for (key in _b) { if (__hasProp.call(_b, key)) { + for (key in _b) { if (Coffeescript.hasProp.call(_b, key)) { value = _b[key]; value > 0 ? _a.push(key) : null; }} @@ -314,12 +294,11 @@ stack = []; debt = {}; _a = INVERSES; - for (key in _a) { if (__hasProp.call(_a, key)) { + for (key in _a) { if (Coffeescript.hasProp.call(_a, key)) { val = _a[key]; (debt[key] = 0); }} - return this.scan_tokens((function(__this) { - var __func = function(prev, token, post, i) { + return this.scan_tokens(Coffeescript.bind(function(prev, token, post, i) { var inv, match, mtag, tag; tag = token[0]; inv = INVERSES[token[0]]; @@ -345,11 +324,7 @@ } else { return 1; } - }; - return (function() { - return __func.apply(__this, arguments); - }); - })(this)); + }, this)); }; return Rewriter; }).call(this); diff --git a/lib/scope.js b/lib/scope.js index aeded357..5692fa40 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -1,6 +1,8 @@ (function(){ - var Scope; - var __hasProp = Object.prototype.hasOwnProperty; + var Scope, utilities; + var __hasProp = Object.prototype.hasOwnProperty, Coffeescript = { + hasProp: Object.prototype.hasOwnProperty + }; // The **Scope** class regulates lexical scoping within CoffeeScript. As you // generate code, you create a tree of scopes in the same shape as the nested // function bodies. Each scope knows about the variables declared within it, @@ -11,6 +13,7 @@ if (!((typeof process !== "undefined" && process !== null))) { this.exports = this; } + utilities = (typeof process !== "undefined" && process !== null) ? require('./utilities').utilities : this.utilities; exports.Scope = (function() { Scope = function Scope(parent, expressions, method) { var _a; @@ -26,6 +29,14 @@ // as well as a reference to the **Expressions** node is belongs to, which is // where it should declare its variables, and a reference to the function that // it wraps. + // Find the top-most scope object, used for defined global variables + Scope.prototype.topmost = function topmost() { + if (this.parent) { + return this.parent.topmost(); + } else { + return this; + } + }; // Look up a variable name in lexical scope, and declare it if it does not // already exist. Scope.prototype.find = function find(name) { @@ -35,6 +46,18 @@ this.variables[name] = 'var'; return false; }; + // Test variables and return true the first time fn(v, k) returns true + Scope.prototype.any = function any(fn) { + var _a, k, v; + _a = this.variables; + for (v in _a) { if (Coffeescript.hasProp.call(_a, v)) { + k = _a[v]; + if (fn(v, k)) { + return true; + } + }} + return false; + }; // 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) { @@ -63,7 +86,7 @@ // (or at the top-level scope, if requested). Scope.prototype.assign = function assign(name, value, top_level) { if (top_level && this.parent) { - return this.parent.assign(name, value, top_level); + return this.topmost().assign(name, value); } this.variables[name] = { value: value, @@ -71,22 +94,52 @@ }; return this.variables[name]; }; + // Ensure the CoffeeScript utility object is included in the top level + // then return a CallNode curried constructor bound to the utility function + Scope.prototype.utility = function utility(name) { + if (this.parent) { + return this.topmost().utility(name); + } + this.utilities = this.utilities || {}; + this.utilities[name] = true; + return "Coffeescript." + name; + }; + Scope.prototype.included_utilities = function included_utilities(tab) { + var _a, _b, _c, _d, key, props; + if ((typeof (_d = this.utilities) !== "undefined" && _d !== null)) { + props = (function() { + _a = []; _b = this.utilities; + for (key in _b) { if (Coffeescript.hasProp.call(_b, key)) { + (typeof (_c = this.utilities[key]) !== "undefined" && _c !== null) ? _a.push(utilities.FORMAT(key, tab)) : null; + }} + return _a; + }).call(this); + return ["" + (utilities.KEY) + " = {" + (props.join(', ')) + "\n" + tab + "}"]; + } else { + return []; + } + }; // Does this scope reference any variables that need to be declared in the // given function body? Scope.prototype.has_declarations = function has_declarations(body) { - return body === this.expressions && this.declared_variables().length; + return body === this.expressions && this.any(function(k, val) { + return val === 'var'; + }); }; // Does this scope reference any assignments that need to be declared at the // top of the given function body? Scope.prototype.has_assignments = function has_assignments(body) { - return body === this.expressions && this.assigned_variables().length; + var _a; + return body === this.expressions && ((typeof (_a = this.utilities) !== "undefined" && _a !== null) || this.any(function(k, val) { + return val.assigned; + })); }; // Return the list of variables first declared in this scope. Scope.prototype.declared_variables = function declared_variables() { var _a, _b, key, val; return (function() { _a = []; _b = this.variables; - for (key in _b) { if (__hasProp.call(_b, key)) { + for (key in _b) { if (Coffeescript.hasProp.call(_b, key)) { val = _b[key]; val === 'var' ? _a.push(key) : null; }} @@ -98,7 +151,7 @@ Scope.prototype.assigned_variables = function assigned_variables() { var _a, _b, key, val; _a = []; _b = this.variables; - for (key in _b) { if (__hasProp.call(_b, key)) { + for (key in _b) { if (Coffeescript.hasProp.call(_b, key)) { val = _b[key]; val.assigned ? _a.push("" + key + " = " + (val.value)) : null; }} @@ -109,8 +162,8 @@ return this.declared_variables().join(', '); }; // Compile the JavaScript for all of the variable assignments in this scope. - Scope.prototype.compiled_assignments = function compiled_assignments() { - return this.assigned_variables().join(', '); + Scope.prototype.compiled_assignments = function compiled_assignments(tab) { + return this.assigned_variables().concat(this.included_utilities(tab)).join(', '); }; return Scope; }).call(this); diff --git a/lib/utilities.js b/lib/utilities.js new file mode 100644 index 00000000..784ef9ef --- /dev/null +++ b/lib/utilities.js @@ -0,0 +1,15 @@ +(function(){ + var utilities; + if (!((typeof process !== "undefined" && process !== null))) { + this.exports = this; + } + exports.utilities = (utilities = { + KEY: "Coffeescript", + FORMAT: function FORMAT(key, tab) { + return "\n " + tab + key + ": " + (utilities[key].replace(/\n/g, "\n" + tab + " ") || 'undefined'); + }, + extend: 'function(child, parent) {\n var ctor = function(){ };\n ctor.prototype = parent.prototype;\n child.__superClass__ = parent.prototype;\n child.prototype = new ctor();\n child.prototype.constructor = child;\n}', + bind: 'function(func, obj, args) {\n obj = obj || {};\n return (typeof args !== "undefined" && args !== null) ? function() {\n return func.apply(obj, args.concat(Array.prototype.slice.call(arguments, 0)));\n } : function() {\n return func.apply(obj, arguments);\n };\n}', + hasProp: "Object.prototype.hasOwnProperty" + }); +})(); diff --git a/src/nodes.coffee b/src/nodes.coffee index 841da0b6..2cae74f0 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -188,7 +188,7 @@ exports.Expressions: class Expressions extends BaseNode # declarations of all inner variables pushed up to the top. compile_with_declarations: (o) -> code: @compile_node(o) - code: "${@tab}var ${o.scope.compiled_assignments()};\n$code" if o.scope.has_assignments(this) + code: "${@tab}var ${o.scope.compiled_assignments(@tab)};\n$code" if o.scope.has_assignments(this) code: "${@tab}var ${o.scope.compiled_declarations()};\n$code" if o.scope.has_declarations(this) code @@ -420,10 +420,8 @@ exports.CurryNode: class CurryNode extends CallNode (new ArrayNode(@args)).compile o compile_node: (o) -> - body: Expressions.wrap([literal @body]) - curried: new CodeNode([], body) - curry: new CodeNode([literal('func'), literal('obj'), literal('args')], Expressions.wrap([curried])) - (new ParentheticalNode(new CallNode(curry, [@meth, @context, literal(@arguments(o))]))).compile o + ref: new ValueNode literal(o.scope.utility('bind')) + (new CallNode(ref, [@meth, @context, literal(@arguments(o))])).compile o #### ExtendsNode @@ -433,25 +431,13 @@ exports.CurryNode: class CurryNode extends CallNode exports.ExtendsNode: class ExtendsNode extends BaseNode type: 'Extends' - code: ''' - function(child, parent) { - var ctor = function(){ }; - ctor.prototype = parent.prototype; - child.__superClass__ = parent.prototype; - child.prototype = new ctor(); - child.prototype.constructor = child; - } - ''' - constructor: (child, parent) -> @children: [@child: child, @parent: parent] # Hooks one constructor into another's prototype chain. compile_node: (o) -> - o.scope.assign('__extends', @code, true) - ref: new ValueNode literal('__extends') - call: new CallNode ref, [@child, @parent] - call.compile(o) + ref: new ValueNode literal(o.scope.utility('extend')) + (new CallNode ref, [@child, @parent]).compile o #### AccessorNode @@ -791,8 +777,8 @@ exports.CodeNode: class CodeNode extends BaseNode func: "function${ if @bound then '' else name_part }(${ params.join(', ') }) {$code${@idt(if @bound then 1 else 0)}}" func: "($func)" if top and not @bound return func unless @bound - inner: "(function$name_part() {\n${@idt(2)}return __func.apply(__this, arguments);\n${@idt(1)}});" - "(function(__this) {\n${@idt(1)}var __func = $func;\n${@idt(1)}return $inner\n$@tab})(this)" + ref: new ValueNode literal(o.scope.utility('bind')) + (new CallNode ref, [literal(func), literal('this')]).compile o top_sensitive: -> true @@ -1161,7 +1147,7 @@ exports.ForNode: class ForNode extends BaseNode 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)" + for_part: "$ivar in $svar) { if (${o.scope.utility('hasProp')}.call($svar, $ivar)" 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' diff --git a/src/scope.coffee b/src/scope.coffee index 9511d795..8fe9f738 100644 --- a/src/scope.coffee +++ b/src/scope.coffee @@ -7,6 +7,7 @@ # Set up exported variables for both **Node.js** and the browser. this.exports: this unless process? +utilities: if process? then require('./utilities').utilities else this.utilities exports.Scope: class Scope @@ -19,12 +20,22 @@ exports.Scope: class Scope @variables: {} @temp_var: if @parent then @parent.temp_var else '_a' + # Find the top-most scope object, used for defined global variables + topmost: -> + if @parent then @parent.topmost() else @ + # Look up a variable name in lexical scope, and declare it if it does not # already exist. find: (name) -> return true if @check name @variables[name]: 'var' false + + # Test variables and return true the first time fn(v, k) returns true + any: (fn) -> + for v, k of @variables when fn(v, k) + return true + return false # Reserve a variable name as originating from a function parameter for this # scope. No `var` required for internal references. @@ -48,18 +59,33 @@ exports.Scope: class Scope # Ensure that an assignment is made at the top of this scope # (or at the top-level scope, if requested). assign: (name, value, top_level) -> - return @parent.assign(name, value, top_level) if top_level and @parent + return @topmost().assign(name, value) if top_level and @parent @variables[name]: {value: value, assigned: true} + # Ensure the CoffeeScript utility object is included in the top level + # then return a CallNode curried constructor bound to the utility function + utility: (name) -> + return @topmost().utility(name) if @parent + @utilities: or {} + @utilities[name]: true + "Coffeescript.$name" + + included_utilities: (tab) -> + if @utilities? + props: (utilities.FORMAT(key, tab) for key of @utilities when @utilities[key]?) + ["${utilities.KEY} = {${props.join(', ')}\n$tab}"] + else [] + + # Does this scope reference any variables that need to be declared in the # given function body? has_declarations: (body) -> - body is @expressions and @declared_variables().length + body is @expressions and @any (k, val) -> val is 'var' # Does this scope reference any assignments that need to be declared at the # top of the given function body? has_assignments: (body) -> - body is @expressions and @assigned_variables().length + body is @expressions and (@utilities? or @any (k, val) -> val.assigned) # Return the list of variables first declared in this scope. declared_variables: -> @@ -75,5 +101,5 @@ exports.Scope: class Scope @declared_variables().join ', ' # Compile the JavaScript for all of the variable assignments in this scope. - compiled_assignments: -> - @assigned_variables().join ', ' + compiled_assignments: (tab) -> + [@assigned_variables()..., @included_utilities(tab)...].join ', ' diff --git a/src/utilities.coffee b/src/utilities.coffee new file mode 100644 index 00000000..51c8a63b --- /dev/null +++ b/src/utilities.coffee @@ -0,0 +1,27 @@ +this.exports: this unless process? +exports.utilities: utilities: { + KEY: "Coffeescript" + FORMAT: (key, tab) -> + "\n $tab$key: ${utilities[key].replace(/\n/g, "\n$tab ") or 'undefined'}" + + extend: ''' + function(child, parent) { + var ctor = function(){ }; + ctor.prototype = parent.prototype; + child.__superClass__ = parent.prototype; + child.prototype = new ctor(); + child.prototype.constructor = child; + } + ''' + bind: ''' + function(func, obj, args) { + obj = obj || {}; + return (typeof args !== "undefined" && args !== null) ? function() { + return func.apply(obj, args.concat(Array.prototype.slice.call(arguments, 0))); + } : function() { + return func.apply(obj, arguments); + }; + } + ''' + hasProp: "Object.prototype.hasOwnProperty" +} \ No newline at end of file