From 75ffb9dc84a53e073bb7c5f78260b860d2369bb0 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Sun, 14 Mar 2010 14:48:43 -0700 Subject: [PATCH] stop mixing in helpers (t(ticket #250) -- with a test --- lib/helpers.js | 19 ++++---- lib/lexer.js | 37 ++++++++-------- lib/nodes.js | 89 +++++++++++++++++++------------------- lib/rewriter.js | 21 ++++----- src/helpers.coffee | 17 ++++---- src/lexer.coffee | 38 ++++++++-------- src/nodes.coffee | 88 +++++++++++++++++++------------------ src/rewriter.coffee | 20 +++++---- test/test_functions.coffee | 5 +++ 9 files changed, 175 insertions(+), 159 deletions(-) diff --git a/lib/helpers.js b/lib/helpers.js index 7f464621..ddaeb12b 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -1,5 +1,5 @@ (function(){ - var balanced_string, compact, count, del, flatten, include, merge, starts; + var balanced_string, compact, count, del, flatten, helpers, include, merge, starts; var __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 @@ -8,16 +8,17 @@ if (!((typeof process !== "undefined" && process !== null))) { this.exports = this; } + helpers = (exports.helpers = {}); // Does a list include a value? - exports.include = (include = function include(list, value) { + helpers.include = (include = function include(list, value) { return list.indexOf(value) >= 0; }); // Peek at the beginning of a given string to see if it matches a sequence. - exports.starts = (starts = function starts(string, literal, start) { + helpers.starts = (starts = function starts(string, literal, start) { return string.substring(start, (start || 0) + literal.length) === literal; }); // Trim out all falsy values from an array. - exports.compact = (compact = function compact(array) { + helpers.compact = (compact = function compact(array) { var _a, _b, _c, _d, item; _a = []; _b = array; for (_c = 0, _d = _b.length; _c < _d; _c++) { @@ -29,7 +30,7 @@ return _a; }); // Count the number of occurences of a character in a string. - exports.count = (count = function count(string, letter) { + helpers.count = (count = function count(string, letter) { var num, pos; num = 0; pos = string.indexOf(letter); @@ -42,7 +43,7 @@ // Merge objects, returning a fresh copy with attributes from both sides. // Used every time `BaseNode#compile` is called, to allow properties in the // options hash to propagate down the tree without polluting other branches. - exports.merge = (merge = function merge(options, overrides) { + helpers.merge = (merge = function merge(options, overrides) { var _a, _b, fresh, key, val; fresh = {}; _a = options; @@ -61,7 +62,7 @@ }); // Return a completely flattened version of an array. Handy for getting a // list of `children` from the nodes. - exports.flatten = (flatten = function flatten(array) { + helpers.flatten = (flatten = function flatten(array) { var _a, _b, _c, item, memo; memo = []; _a = array; @@ -73,7 +74,7 @@ }); // Delete a key from an object, returning the value. Useful when a node is // looking for a particular method in an options hash. - exports.del = (del = function del(obj, key) { + helpers.del = (del = function del(obj, key) { var val; val = obj[key]; delete obj[key]; @@ -83,7 +84,7 @@ // a series of delimiters, all of which must be nested correctly within the // contents of the string. This method allows us to have strings within // interpolations within strings, ad infinitum. - exports.balanced_string = (balanced_string = function balanced_string(str, delimited, options) { + helpers.balanced_string = (balanced_string = function balanced_string(str, delimited, options) { var _a, _b, _c, _d, close, i, levels, open, pair, slash; options = options || {}; slash = delimited[0][0] === '/'; diff --git a/lib/lexer.js b/lib/lexer.js index 3e64a712..7da20429 100644 --- a/lib/lexer.js +++ b/lib/lexer.js @@ -1,5 +1,5 @@ (function(){ - var ACCESSORS, ASSIGNMENT, BEFORE_WHEN, CALLABLE, CODE, COFFEE_KEYWORDS, COMMENT, COMMENT_CLEANER, HEREDOC, HEREDOC_INDENT, IDENTIFIER, INTERPOLATION, JS_CLEANER, JS_FORBIDDEN, JS_KEYWORDS, KEYWORDS, LAST_DENT, LAST_DENTS, Lexer, MULTILINER, MULTI_DENT, NOT_REGEX, NO_NEWLINE, NUMBER, OPERATOR, REGEX_ESCAPE, REGEX_FLAGS, REGEX_INTERPOLATION, REGEX_START, RESERVED, Rewriter, STRING_NEWLINES, WHITESPACE; + var ACCESSORS, ASSIGNMENT, BEFORE_WHEN, CALLABLE, CODE, COFFEE_KEYWORDS, COMMENT, COMMENT_CLEANER, HEREDOC, HEREDOC_INDENT, IDENTIFIER, INTERPOLATION, JS_CLEANER, JS_FORBIDDEN, JS_KEYWORDS, KEYWORDS, LAST_DENT, LAST_DENTS, Lexer, MULTILINER, MULTI_DENT, NOT_REGEX, NO_NEWLINE, NUMBER, OPERATOR, REGEX_ESCAPE, REGEX_FLAGS, REGEX_INTERPOLATION, REGEX_START, RESERVED, Rewriter, STRING_NEWLINES, WHITESPACE, h, helpers; // The CoffeeScript Lexer. Uses a series of token-matching regexes to attempt // matches against the beginning of the source code. When a match is found, // a token is produced, we consume the match, and start again. Tokens are in the @@ -9,11 +9,12 @@ // Set up the Lexer for both Node.js and the browser, depending on where we are. if ((typeof process !== "undefined" && process !== null)) { Rewriter = require('rewriter').Rewriter; - process.mixin(require('helpers')); + helpers = require('helpers').helpers; } else { this.exports = this; Rewriter = this.Rewriter; } + h = helpers; // The Lexer Class // --------------- // The Lexer class reads a stream of CoffeeScript and divvys it up into tagged @@ -121,13 +122,13 @@ } this.name_access_type(); tag = 'IDENTIFIER'; - if (include(KEYWORDS, id) && !(include(ACCESSORS, this.tag(0)) && !this.prev().spaced)) { + if (h.include(KEYWORDS, id) && !(h.include(ACCESSORS, this.tag(0)) && !this.prev().spaced)) { tag = id.toUpperCase(); } - if (include(RESERVED, id)) { + if (h.include(RESERVED, id)) { this.identifier_error(id); } - if (tag === 'WHEN' && include(BEFORE_WHEN, this.tag())) { + if (tag === 'WHEN' && h.include(BEFORE_WHEN, this.tag())) { tag = 'LEADING_WHEN'; } this.token(tag, id); @@ -148,14 +149,14 @@ // are balanced within the string's contents, and within nested interpolations. Lexer.prototype.string_token = function string_token() { var string; - if (!(starts(this.chunk, '"') || starts(this.chunk, "'"))) { + if (!(h.starts(this.chunk, '"') || h.starts(this.chunk, "'"))) { return false; } if (!((string = this.balanced_token(['"', '"'], ['${', '}']) || this.balanced_token(["'", "'"])))) { return false; } this.interpolate_string(string.replace(STRING_NEWLINES, " \\\n")); - this.line += count(string, "\n"); + this.line += h.count(string, "\n"); this.i += string.length; return true; }; @@ -168,14 +169,14 @@ } doc = this.sanitize_heredoc(match[2] || match[4]); this.token('STRING', "\"" + doc + "\""); - this.line += count(match[1], "\n"); + this.line += h.count(match[1], "\n"); this.i += match[1].length; return true; }; // Matches JavaScript interpolated directly into the source via backticks. Lexer.prototype.js_token = function js_token() { var script; - if (!(starts(this.chunk, '`'))) { + if (!(h.starts(this.chunk, '`'))) { return false; } if (!((script = this.balanced_token(['`', '`'])))) { @@ -194,7 +195,7 @@ if (!(this.chunk.match(REGEX_START))) { return false; } - if (include(NOT_REGEX, this.tag())) { + if (h.include(NOT_REGEX, this.tag())) { return false; } if (!((regex = this.balanced_token(['/', '/'])))) { @@ -220,7 +221,7 @@ Lexer.prototype.balanced_token = function balanced_token() { var delimited; delimited = Array.prototype.slice.call(arguments, 0); - return balanced_string(this.chunk, delimited); + return h.balanced_string(this.chunk, delimited); }; // Matches and conumes comments. We pass through comments into JavaScript, // so they're treated as real tokens, like any other part of the language. @@ -231,7 +232,7 @@ } this.line += (comment.match(MULTILINER) || []).length; lines = comment.replace(COMMENT_CLEANER, '').split(MULTILINER); - this.token('COMMENT', compact(lines)); + this.token('COMMENT', h.compact(lines)); this.token('TERMINATOR', "\n"); this.i += comment.length; return true; @@ -333,7 +334,7 @@ tag = value; if (value.match(ASSIGNMENT)) { tag = 'ASSIGN'; - if (include(JS_FORBIDDEN, this.value)) { + if (h.include(JS_FORBIDDEN, this.value)) { this.assignment_error(); } } else if (value === ';') { @@ -345,7 +346,7 @@ } else if (value === ']' && this.soaked_index) { tag = 'SOAKED_INDEX_END'; this.soaked_index = false; - } else if (include(CALLABLE, this.tag()) && not_spaced) { + } else if (h.include(CALLABLE, this.tag()) && not_spaced) { if (value === '(') { tag = 'CALL_START'; } @@ -430,7 +431,7 @@ // token stream. Lexer.prototype.interpolate_string = function interpolate_string(str, escape_quotes) { var _a, _b, _c, _d, _e, escaped, expr, group, i, inner, interp, lexer, match, nested, pi, quote, tag, token, tokens, value; - if (str.length < 3 || !starts(str, '"')) { + if (str.length < 3 || !h.starts(str, '"')) { return this.token('STRING', str); } else { lexer = new Lexer(); @@ -440,13 +441,13 @@ i = _a[0]; pi = _a[1]; while (i < str.length - 1) { - if (starts(str, '\\', i)) { + if (h.starts(str, '\\', i)) { i += 1; } else if ((match = str.substring(i).match(INTERPOLATION))) { _b = match; group = _b[0]; interp = _b[1]; - if (starts(interp, '@')) { + if (h.starts(interp, '@')) { interp = "this." + (interp.substring(1)); } if (pi < i) { @@ -455,7 +456,7 @@ tokens.push(['IDENTIFIER', interp]); i += group.length - 1; pi = i + 1; - } else if (((expr = balanced_string(str.substring(i), [['${', '}']])))) { + } else if (((expr = h.balanced_string(str.substring(i), [['${', '}']])))) { if (pi < i) { tokens.push(['STRING', '' + quote + (str.substring(pi, i)) + quote]); } diff --git a/lib/nodes.js b/lib/nodes.js index ddb5f1fd..a52899b1 100644 --- a/lib/nodes.js +++ b/lib/nodes.js @@ -1,5 +1,5 @@ (function(){ - var AccessorNode, ArrayNode, AssignNode, BaseNode, CallNode, ClassNode, ClosureNode, CodeNode, CommentNode, ExistenceNode, Expressions, ExtendsNode, ForNode, IDENTIFIER, IfNode, IndexNode, LiteralNode, ObjectNode, OpNode, ParentheticalNode, PushNode, RangeNode, ReturnNode, SliceNode, SplatNode, TAB, TRAILING_WHITESPACE, ThrowNode, TryNode, ValueNode, WhileNode, literal, statement; + var AccessorNode, ArrayNode, AssignNode, BaseNode, CallNode, ClassNode, ClosureNode, CodeNode, CommentNode, ExistenceNode, Expressions, ExtendsNode, ForNode, IDENTIFIER, IfNode, IndexNode, LiteralNode, ObjectNode, OpNode, ParentheticalNode, PushNode, RangeNode, ReturnNode, Scope, SliceNode, SplatNode, TAB, TRAILING_WHITESPACE, ThrowNode, TryNode, ValueNode, WhileNode, h, helpers, literal, statement; var __extends = function(child, parent) { var ctor = function(){ }; ctor.prototype = parent.prototype; @@ -14,11 +14,12 @@ // Set up for both **Node.js** and the browser, by // including the [Scope](scope.html) class. if ((typeof process !== "undefined" && process !== null)) { - process.mixin(require('scope')); - process.mixin(require('helpers')); + Scope = require('scope').Scope; + helpers = require('helpers').helpers; } else { this.exports = this; } + h = helpers; // Helper function that marks a node as a JavaScript *statement*, or as a // *pure_statement*. Statements must be wrapped in a closure when used as an // expression, and nodes tagged as *pure_statement* cannot be closure-wrapped @@ -56,12 +57,12 @@ // top-level statement within the function body. BaseNode.prototype.compile = function compile(o) { var closure, top; - this.options = merge(o || {}); + this.options = h.merge(o || {}); this.tab = o.indent; if (!(this instanceof ValueNode)) { - del(this.options, 'operation'); + h.del(this.options, 'operation'); } - top = this.top_sensitive() ? this.options.top : del(this.options, 'top'); + top = this.top_sensitive() ? this.options.top : h.del(this.options, 'top'); closure = this.is_statement() && !this.is_pure_statement() && !top && !this.options.returns && !(this instanceof CommentNode) && !this.contains(function(node) { return node.is_pure_statement(); }); @@ -163,7 +164,7 @@ // `if`, `switch`, or `try`, and so on... exports.Expressions = (function() { Expressions = function Expressions(nodes) { - this.children = (this.expressions = compact(flatten(nodes || []))); + this.children = (this.expressions = h.compact(h.flatten(nodes || []))); return this; }; __extends(Expressions, BaseNode); @@ -205,7 +206,7 @@ _a = []; _b = this.expressions; for (_c = 0, _d = _b.length; _c < _d; _c++) { node = _b[_c]; - _a.push(this.compile_expression(node, merge(o))); + _a.push(this.compile_expression(node, h.merge(o))); } return _a; }).call(this).join("\n"); @@ -246,14 +247,14 @@ var returns, stmt; this.tab = o.indent; stmt = node.is_statement(); - returns = del(o, 'returns') && this.is_last(node) && !node.is_pure_statement(); + returns = h.del(o, 'returns') && this.is_last(node) && !node.is_pure_statement(); if (!(returns)) { - return (stmt ? '' : this.idt()) + node.compile(merge(o, { + return (stmt ? '' : this.idt()) + node.compile(h.merge(o, { top: true })) + (stmt ? '' : ';'); } if (node.is_statement()) { - return node.compile(merge(o, { + return node.compile(h.merge(o, { returns: true })); } @@ -310,7 +311,7 @@ ReturnNode.prototype.type = 'Return'; ReturnNode.prototype.compile_node = function compile_node(o) { if (this.expression.is_statement()) { - return this.expression.compile(merge(o, { + return this.expression.compile(h.merge(o, { returns: true })); } @@ -324,7 +325,7 @@ // or vanilla. exports.ValueNode = (function() { ValueNode = function ValueNode(base, properties) { - this.children = flatten([(this.base = base), (this.properties = (properties || []))]); + this.children = h.flatten([(this.base = base), (this.properties = (properties || []))]); return this; }; __extends(ValueNode, BaseNode); @@ -369,8 +370,8 @@ ValueNode.prototype.compile_node = function compile_node(o) { var _a, _b, _c, baseline, complete, only, op, part, prop, props, soaked, temp; soaked = false; - only = del(o, 'only_first'); - op = del(o, 'operation'); + only = h.del(o, 'only_first'); + op = h.del(o, 'operation'); props = only ? this.properties.slice(0, this.properties.length - 1) : this.properties; baseline = this.base.compile(o); if (this.base instanceof ObjectNode && this.has_properties()) { @@ -422,7 +423,7 @@ // 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.children = h.flatten([(this.variable = variable), (this.args = (args || []))]); this.prefix = ''; return this; }; @@ -577,8 +578,8 @@ if (!(o.index)) { return this.compile_array(o); } - idx = del(o, 'index'); - step = del(o, 'step'); + idx = h.del(o, 'index'); + step = h.del(o, 'step'); vars = '' + idx + " = " + this.from_var; step = step ? step.compile(o) : '1'; equals = this.exclusive ? '' : '='; @@ -713,7 +714,7 @@ // 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.children = h.compact(h.flatten([(this.variable = variable), (this.parent = parent), (this.properties = props || [])])); return this; }; __extends(ClassNode, BaseNode); @@ -729,7 +730,7 @@ constructor = null; props = new Expressions(); o.top = true; - ret = del(o, 'returns'); + ret = h.del(o, 'returns'); _a = this.properties; for (_b = 0, _c = _a.length; _b < _c; _b++) { prop = _a[_b]; @@ -791,14 +792,14 @@ // has not been seen yet within the current scope, declare it. AssignNode.prototype.compile_node = function compile_node(o) { var last, match, name, proto, stmt, top, val; - top = del(o, 'top'); + top = h.del(o, 'top'); if (this.is_statement()) { return this.compile_pattern_match(o); } if (this.is_value() && this.variable.is_splice()) { return this.compile_splice(o); } - stmt = del(o, 'as_statement'); + stmt = h.del(o, 'as_statement'); name = this.variable.compile(o); last = this.is_value() ? this.variable.last.replace(this.LEADING_DOT, '') : name; match = name.match(this.PROTO_ASSIGN); @@ -871,7 +872,7 @@ // `Array#splice` method. AssignNode.prototype.compile_splice = function compile_splice(o) { var from, l, name, plus, range, to, val; - name = this.variable.compile(merge(o, { + name = this.variable.compile(h.merge(o, { only_first: true })); l = this.variable.properties.length; @@ -904,14 +905,14 @@ // a closure. CodeNode.prototype.compile_node = function compile_node(o) { var _a, _b, _c, _d, _e, _f, _g, code, func, inner, name_part, param, params, shared_scope, splat, top; - shared_scope = del(o, 'shared_scope'); - top = del(o, 'top'); + shared_scope = h.del(o, 'shared_scope'); + top = h.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'); - del(o, 'globals'); + h.del(o, 'no_wrap'); + h.del(o, 'globals'); if (this.params[this.params.length - 1] instanceof SplatNode) { splat = this.params.pop(); splat.index = this.params.length; @@ -948,7 +949,7 @@ // When traversing (for printing or inspecting), return the real children of // the function -- the parameters and body of expressions. CodeNode.prototype.real_children = function real_children() { - return flatten([this.params, this.body.expressions]); + return h.flatten([this.params, this.body.expressions]); }; // Custom `traverse` implementation that uses the `real_children`. CodeNode.prototype.traverse = function traverse(block) { @@ -1032,8 +1033,8 @@ // 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; + returns = h.del(o, 'returns'); + top = h.del(o, 'top') && !returns; o.indent = this.idt(1); o.top = true; cond = this.condition.compile(o); @@ -1064,7 +1065,7 @@ exports.OpNode = (function() { OpNode = function OpNode(operator, first, second, flip) { this.type += ' ' + operator; - this.children = compact([(this.first = first), (this.second = second)]); + this.children = h.compact([(this.first = first), (this.second = second)]); this.operator = this.CONVERSIONS[operator] || operator; this.flip = !!flip; return this; @@ -1170,7 +1171,7 @@ // A classic *try/catch/finally* block. exports.TryNode = (function() { TryNode = function TryNode(attempt, error, recovery, ensure) { - this.children = compact([(this.attempt = attempt), (this.recovery = recovery), (this.ensure = ensure)]); + this.children = h.compact([(this.attempt = attempt), (this.recovery = recovery), (this.ensure = ensure)]); this.error = error; this; return this; @@ -1186,7 +1187,7 @@ 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, { + finally_part = (this.ensure || '') && ' finally {\n' + this.ensure.compile(h.merge(o, { returns: null })) + "\n" + this.tab + "}"; return '' + (this.tab) + "try {\n" + attempt_part + "\n" + this.tab + "}" + catch_part + finally_part; @@ -1294,7 +1295,7 @@ this.name = _a[0]; this.index = _a[1]; } - this.children = compact([this.body, this.source, this.filter]); + this.children = h.compact([this.body, this.source, this.filter]); return this; }; __extends(ForNode, BaseNode); @@ -1308,7 +1309,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') && !o.returns; + top_level = h.del(o, 'top') && !o.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; @@ -1331,7 +1332,7 @@ if (range) { index_var = scope.free_variable(); source_part = source.compile_variables(o); - for_part = source.compile(merge(o, { + for_part = source.compile(h.merge(o, { index: ivar, step: this.step })); @@ -1360,7 +1361,7 @@ } if (o.returns) { return_result = 'return ' + return_result; - del(o, 'returns'); + h.del(o, 'returns'); if (this.filter) { body = new IfNode(this.filter, body, null, { statement: true @@ -1376,7 +1377,7 @@ if (!(top_level)) { return_result = "\n" + this.tab + return_result + ";"; } - body = body.compile(merge(o, { + body = body.compile(h.merge(o, { indent: body_dent, top: true })); @@ -1397,7 +1398,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 = h.compact([this.condition, this.body, this.else_body]); this.tags = tags || {}; if (this.condition instanceof Array) { this.multiple = true; @@ -1478,7 +1479,7 @@ IfNode.prototype.compile_condition = function compile_condition(o) { var _a, _b, _c, _d, cond; return (function() { - _a = []; _b = flatten([this.condition]); + _a = []; _b = h.flatten([this.condition]); for (_c = 0, _d = _b.length; _c < _d; _c++) { cond = _b[_c]; _a.push(cond.compile(o)); @@ -1496,9 +1497,9 @@ if (this.switcher) { this.rewrite_switch(o); } - child = del(o, 'chain_child'); - cond_o = merge(o); - del(cond_o, 'returns'); + child = h.del(o, 'chain_child'); + cond_o = h.merge(o); + h.del(cond_o, 'returns'); o.indent = this.idt(1); o.top = true; if_dent = child ? '' : this.idt(); @@ -1509,7 +1510,7 @@ if (!(this.else_body)) { return if_part; } - else_part = this.is_chain() ? ' else ' + this.else_body.compile(merge(o, { + else_part = this.is_chain() ? ' else ' + this.else_body.compile(h.merge(o, { indent: this.idt(), chain_child: true })) : " else {\n" + (Expressions.wrap([this.else_body]).compile(o)) + "\n" + this.tab + "}"; diff --git a/lib/rewriter.js b/lib/rewriter.js index ca0a8c32..5f903e79 100644 --- a/lib/rewriter.js +++ b/lib/rewriter.js @@ -1,5 +1,5 @@ (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, pair; + 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, h, helpers, pair; var __hasProp = Object.prototype.hasOwnProperty; // The CoffeeScript language has a decent amount of optional syntax, // implicit syntax, and shorthand syntax. These things can greatly complicate a @@ -9,7 +9,8 @@ // add implicit indentation and parentheses, balance incorrect nestings, and // generally clean things up. // Set up exported variables for both Node.js and the browser. - (typeof process !== "undefined" && process !== null) ? process.mixin(require('helpers')) : (this.exports = this); + (typeof process !== "undefined" && process !== null) ? (helpers = require('helpers').helpers) : (this.exports = this); + h = helpers; // The **Rewriter** class is used by the [Lexer](lexer.html), directly against // its internal array of tokens. exports.Rewriter = (function() { @@ -89,7 +90,7 @@ Rewriter.prototype.remove_mid_expression_newlines = function remove_mid_expression_newlines() { return this.scan_tokens((function(__this) { var __func = function(prev, token, post, i) { - if (!(post && include(EXPRESSION_CLOSE, post[0]) && token[0] === 'TERMINATOR')) { + if (!(post && h.include(EXPRESSION_CLOSE, post[0]) && token[0] === 'TERMINATOR')) { return 1; } this.tokens.splice(i, 1); @@ -167,8 +168,8 @@ this.tokens.splice(i, 0, ['CALL_END', ')', token[2]]); return 2; } - if (!(typeof post !== "undefined" && post !== null) || include(IMPLICIT_END, tag)) { - if (tag === 'INDENT' && prev && include(IMPLICIT_BLOCK, prev[0])) { + if (!(typeof post !== "undefined" && post !== null) || h.include(IMPLICIT_END, tag)) { + if (tag === 'INDENT' && prev && h.include(IMPLICIT_BLOCK, prev[0])) { return 1; } if (open || tag === 'INDENT') { @@ -183,7 +184,7 @@ return size; } } - if (!(prev && include(IMPLICIT_FUNC, prev[0]) && include(IMPLICIT_CALL, tag))) { + if (!(prev && h.include(IMPLICIT_FUNC, prev[0]) && h.include(IMPLICIT_CALL, tag))) { return 1; } calls = 0; @@ -204,7 +205,7 @@ return this.scan_tokens((function(__this) { var __func = function(prev, token, post, i) { var idx, insertion, parens, pre, starter, tok; - if (!(include(SINGLE_LINERS, token[0]) && post[0] !== 'INDENT' && !(token[0] === 'ELSE' && post[0] === 'IF'))) { + if (!(h.include(SINGLE_LINERS, token[0]) && post[0] !== 'INDENT' && !(token[0] === 'ELSE' && post[0] === 'IF'))) { return 1; } starter = token[0]; @@ -215,7 +216,7 @@ idx += 1; tok = this.tokens[idx]; pre = this.tokens[idx - 1]; - if ((!tok || (include(SINGLE_CLOSERS, tok[0]) && tok[1] !== ';') || (tok[0] === ')' && parens === 0)) && !(starter === 'ELSE' && tok[0] === 'ELSE')) { + if ((!tok || (h.include(SINGLE_CLOSERS, tok[0]) && tok[1] !== ';') || (tok[0] === ')' && parens === 0)) && !(starter === 'ELSE' && tok[0] === 'ELSE')) { insertion = pre[0] === "," ? idx - 1 : idx; this.tokens.splice(insertion, 0, ['OUTDENT', 2, token[2]]); break; @@ -308,10 +309,10 @@ var inv, match, mtag, tag; tag = token[0]; inv = INVERSES[token[0]]; - if (include(EXPRESSION_START, tag)) { + if (h.include(EXPRESSION_START, tag)) { stack.push(token); return 1; - } else if (include(EXPRESSION_END, tag)) { + } else if (h.include(EXPRESSION_END, tag)) { if (debt[inv] > 0) { debt[inv] -= 1; this.tokens.splice(i, 1); diff --git a/src/helpers.coffee b/src/helpers.coffee index db9c1339..d395a32a 100644 --- a/src/helpers.coffee +++ b/src/helpers.coffee @@ -4,20 +4,21 @@ # Set up exported variables for both **Node.js** and the browser. this.exports: this unless process? +helpers: exports.helpers: {} # Does a list include a value? -exports.include: include: (list, value) -> +helpers.include: include: (list, value) -> list.indexOf(value) >= 0 # Peek at the beginning of a given string to see if it matches a sequence. -exports.starts: starts: (string, literal, start) -> +helpers.starts: starts: (string, literal, start) -> string.substring(start, (start or 0) + literal.length) is literal # Trim out all falsy values from an array. -exports.compact: compact: (array) -> item for item in array when item +helpers.compact: compact: (array) -> item for item in array when item # Count the number of occurences of a character in a string. -exports.count: count: (string, letter) -> +helpers.count: count: (string, letter) -> num: 0 pos: string.indexOf(letter) while pos isnt -1 @@ -28,7 +29,7 @@ exports.count: count: (string, letter) -> # Merge objects, returning a fresh copy with attributes from both sides. # Used every time `BaseNode#compile` is called, to allow properties in the # options hash to propagate down the tree without polluting other branches. -exports.merge: merge: (options, overrides) -> +helpers.merge: merge: (options, overrides) -> fresh: {} (fresh[key]: val) for key, val of options (fresh[key]: val) for key, val of overrides if overrides @@ -36,7 +37,7 @@ exports.merge: merge: (options, overrides) -> # Return a completely flattened version of an array. Handy for getting a # list of `children` from the nodes. -exports.flatten: flatten: (array) -> +helpers.flatten: flatten: (array) -> memo: [] for item in array if item instanceof Array then memo: memo.concat(item) else memo.push(item) @@ -44,7 +45,7 @@ exports.flatten: flatten: (array) -> # Delete a key from an object, returning the value. Useful when a node is # looking for a particular method in an options hash. -exports.del: del: (obj, key) -> +helpers.del: del: (obj, key) -> val: obj[key] delete obj[key] val @@ -53,7 +54,7 @@ exports.del: del: (obj, key) -> # a series of delimiters, all of which must be nested correctly within the # contents of the string. This method allows us to have strings within # interpolations within strings, ad infinitum. -exports.balanced_string: balanced_string: (str, delimited, options) -> +helpers.balanced_string: balanced_string: (str, delimited, options) -> options ||= {} slash: delimited[0][0] is '/' levels: [] diff --git a/src/lexer.coffee b/src/lexer.coffee index ea4b9063..dff12079 100644 --- a/src/lexer.coffee +++ b/src/lexer.coffee @@ -10,11 +10,13 @@ # Set up the Lexer for both Node.js and the browser, depending on where we are. if process? Rewriter: require('rewriter').Rewriter - process.mixin require 'helpers' + helpers: require('helpers').helpers else this.exports: this Rewriter: this.Rewriter +h: helpers + # The Lexer Class # --------------- @@ -87,10 +89,10 @@ exports.Lexer: class Lexer return false unless id: @match IDENTIFIER, 1 @name_access_type() tag: 'IDENTIFIER' - tag: id.toUpperCase() if include(KEYWORDS, id) and - not (include(ACCESSORS, @tag(0)) and not @prev().spaced) - @identifier_error id if include RESERVED, id - tag: 'LEADING_WHEN' if tag is 'WHEN' and include BEFORE_WHEN, @tag() + tag: id.toUpperCase() if h.include(KEYWORDS, id) and + not (h.include(ACCESSORS, @tag(0)) and not @prev().spaced) + @identifier_error id if h.include RESERVED, id + tag: 'LEADING_WHEN' if tag is 'WHEN' and h.include BEFORE_WHEN, @tag() @token(tag, id) @i += id.length true @@ -105,12 +107,12 @@ exports.Lexer: class Lexer # Matches strings, including multi-line strings. Ensures that quotation marks # are balanced within the string's contents, and within nested interpolations. string_token: -> - return false unless starts(@chunk, '"') or starts(@chunk, "'") + return false unless h.starts(@chunk, '"') or h.starts(@chunk, "'") return false unless string: @balanced_token(['"', '"'], ['${', '}']) or @balanced_token ["'", "'"] @interpolate_string string.replace(STRING_NEWLINES, " \\\n") - @line += count string, "\n" + @line += h.count string, "\n" @i += string.length true @@ -120,13 +122,13 @@ exports.Lexer: class Lexer return false unless match = @chunk.match(HEREDOC) doc: @sanitize_heredoc match[2] or match[4] @token 'STRING', "\"$doc\"" - @line += count match[1], "\n" + @line += h.count match[1], "\n" @i += match[1].length true # Matches JavaScript interpolated directly into the source via backticks. js_token: -> - return false unless starts @chunk, '`' + return false unless h.starts @chunk, '`' return false unless script: @balanced_token ['`', '`'] @token 'JS', script.replace(JS_CLEANER, '') @i += script.length @@ -138,7 +140,7 @@ exports.Lexer: class Lexer # borrow interpolation from `@interpolate_string`. regex_token: -> return false unless @chunk.match REGEX_START - return false if include NOT_REGEX, @tag() + return false if h.include NOT_REGEX, @tag() return false unless regex: @balanced_token ['/', '/'] regex += (flags: @chunk.substr(regex.length).match(REGEX_FLAGS)) if regex.match REGEX_INTERPOLATION @@ -155,7 +157,7 @@ exports.Lexer: class Lexer # Matches a token in which which the passed delimiter pairs must be correctly # balanced (ie. strings, JS literals). balanced_token: (delimited...) -> - balanced_string @chunk, delimited + h.balanced_string @chunk, delimited # Matches and conumes comments. We pass through comments into JavaScript, # so they're treated as real tokens, like any other part of the language. @@ -163,7 +165,7 @@ exports.Lexer: class Lexer return false unless comment: @match COMMENT, 1 @line += (comment.match(MULTILINER) or []).length lines: comment.replace(COMMENT_CLEANER, '').split(MULTILINER) - @token 'COMMENT', compact lines + @token 'COMMENT', h.compact lines @token 'TERMINATOR', "\n" @i += comment.length true @@ -244,7 +246,7 @@ exports.Lexer: class Lexer tag: value if value.match(ASSIGNMENT) tag: 'ASSIGN' - @assignment_error() if include JS_FORBIDDEN, @value + @assignment_error() if h.include JS_FORBIDDEN, @value else if value is ';' tag: 'TERMINATOR' else if value is '[' and @tag() is '?' and not_spaced @@ -254,7 +256,7 @@ exports.Lexer: class Lexer else if value is ']' and @soaked_index tag: 'SOAKED_INDEX_END' @soaked_index: false - else if include(CALLABLE, @tag()) and not_spaced + else if h.include(CALLABLE, @tag()) and not_spaced tag: 'CALL_START' if value is '(' tag: 'INDEX_START' if value is '[' @token tag, value @@ -324,7 +326,7 @@ exports.Lexer: class Lexer # new Lexer, tokenize the interpolated contents, and merge them into the # token stream. interpolate_string: (str, escape_quotes) -> - if str.length < 3 or not starts str, '"' + if str.length < 3 or not h.starts str, '"' @token 'STRING', str else lexer: new Lexer() @@ -332,16 +334,16 @@ exports.Lexer: class Lexer quote: str.substring(0, 1) [i, pi]: [1, 1] while i < str.length - 1 - if starts str, '\\', i + if h.starts str, '\\', i i += 1 else if match: str.substring(i).match INTERPOLATION [group, interp]: match - interp: "this.${ interp.substring(1) }" if starts interp, '@' + interp: "this.${ interp.substring(1) }" if h.starts interp, '@' tokens.push ['STRING', "$quote${ str.substring(pi, i) }$quote"] if pi < i tokens.push ['IDENTIFIER', interp] i += group.length - 1 pi: i + 1 - else if (expr: balanced_string str.substring(i), [['${', '}']]) + else if (expr: h.balanced_string str.substring(i), [['${', '}']]) tokens.push ['STRING', "$quote${ str.substring(pi, i) }$quote"] if pi < i inner: expr.substring(2, expr.length - 1) if inner.length diff --git a/src/nodes.coffee b/src/nodes.coffee index 15523dd2..6f64b999 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -6,11 +6,13 @@ # Set up for both **Node.js** and the browser, by # including the [Scope](scope.html) class. if process? - process.mixin require 'scope' - process.mixin require 'helpers' + Scope: require('scope').Scope + helpers: require('helpers').helpers else this.exports: this +h: helpers + # Helper function that marks a node as a JavaScript *statement*, or as a # *pure_statement*. Statements must be wrapped in a closure when used as an # expression, and nodes tagged as *pure_statement* cannot be closure-wrapped @@ -43,10 +45,10 @@ exports.BaseNode: class BaseNode # depending on whether it's being used as part of a larger expression, or is a # top-level statement within the function body. compile: (o) -> - @options: merge o or {} + @options: h.merge o or {} @tab: o.indent - del @options, 'operation' unless this instanceof ValueNode - top: if @top_sensitive() then @options.top else del @options, 'top' + h.del @options, 'operation' unless this instanceof ValueNode + top: if @top_sensitive() then @options.top else h.del @options, 'top' closure: @is_statement() and not @is_pure_statement() and not top and not @options.returns and not (this instanceof CommentNode) and not @contains (node) -> node.is_pure_statement() @@ -113,7 +115,7 @@ exports.Expressions: class Expressions extends BaseNode type: 'Expressions' constructor: (nodes) -> - @children: @expressions: compact flatten nodes or [] + @children: @expressions: h.compact h.flatten nodes or [] # Tack an expression on to the end of this expression list. push: (node) -> @@ -146,7 +148,7 @@ exports.Expressions: class Expressions extends BaseNode if o.scope then super(o) else @compile_root(o) compile_node: (o) -> - (@compile_expression(node, merge(o)) for node in @expressions).join("\n") + (@compile_expression(node, h.merge(o)) for node in @expressions).join("\n") # If we happen to be the top-level **Expressions**, wrap everything in # a safety closure, unless requested not to. @@ -173,9 +175,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() + returns: h.del(o, 'returns') and @is_last(node) and not node.is_pure_statement() + return (if stmt then '' else @idt()) + node.compile(h.merge(o, {top: true})) + (if stmt then '' else ';') unless returns + return node.compile(h.merge(o, {returns: true})) if node.is_statement() "${@tab}return ${node.compile(o)};" # Wrap up the given nodes as an **Expressions**, unless it already happens @@ -222,7 +224,7 @@ exports.ReturnNode: class ReturnNode extends BaseNode @children: [@expression: expression] compile_node: (o) -> - return @expression.compile(merge(o, {returns: true})) if @expression.is_statement() + return @expression.compile(h.merge(o, {returns: true})) if @expression.is_statement() "${@tab}return ${@expression.compile(o)};" statement ReturnNode, true @@ -238,7 +240,7 @@ exports.ValueNode: class ValueNode extends BaseNode # A **ValueNode** has a base and a list of property accesses. constructor: (base, properties) -> - @children: flatten [@base: base, @properties: (properties or [])] + @children: h.flatten [@base: base, @properties: (properties or [])] # Add a property access to the list. push: (prop) -> @@ -278,8 +280,8 @@ exports.ValueNode: class ValueNode extends BaseNode # evaluate a anything twice when building the soak chain. compile_node: (o) -> soaked: false - only: del(o, 'only_first') - op: del(o, 'operation') + only: h.del(o, 'only_first') + op: h.del(o, 'operation') props: if only then @properties[0...@properties.length - 1] else @properties baseline: @base.compile o baseline: "($baseline)" if @base instanceof ObjectNode and @has_properties() @@ -326,7 +328,7 @@ exports.CallNode: class CallNode extends BaseNode type: 'Call' constructor: (variable, args) -> - @children: flatten [@variable: variable, @args: (args or [])] + @children: h.flatten [@variable: variable, @args: (args or [])] @prefix: '' # Tag this invocation as creating a new instance. @@ -448,8 +450,8 @@ exports.RangeNode: class RangeNode extends BaseNode # needed to iterate over the values in the range. Used by comprehensions. compile_node: (o) -> return @compile_array(o) unless o.index - idx: del o, 'index' - step: del o, 'step' + idx: h.del o, 'index' + step: h.del o, 'step' vars: "$idx = $@from_var" step: if step then step.compile(o) else '1' equals: if @exclusive then '' else '=' @@ -545,7 +547,7 @@ exports.ClassNode: class ClassNode extends BaseNode # Initialize a **ClassNode** with its name, an optional superclass, and a # list of prototype property assignments. constructor: (variable, parent, props) -> - @children: compact flatten [@variable: variable, @parent: parent, @properties: props or []] + @children: h.compact h.flatten [@variable: variable, @parent: parent, @properties: props or []] # Instead of generating the JavaScript string directly, we build up the # equivalent syntax tree and compile that, in pieces. You can see the @@ -555,7 +557,7 @@ exports.ClassNode: class ClassNode extends BaseNode constructor: null props: new Expressions() o.top: true - ret: del o, 'returns' + ret: h.del o, 'returns' for prop in @properties if prop.variable and prop.variable.base.value is 'constructor' @@ -614,10 +616,10 @@ exports.AssignNode: class AssignNode extends BaseNode # we've been assigned to, for correct internal references. If the variable # has not been seen yet within the current scope, declare it. compile_node: (o) -> - top: del o, 'top' + top: h.del o, 'top' return @compile_pattern_match(o) if @is_statement() return @compile_splice(o) if @is_value() and @variable.is_splice() - stmt: del o, 'as_statement' + stmt: h.del o, 'as_statement' name: @variable.compile(o) last: if @is_value() then @variable.last.replace(@LEADING_DOT, '') else name match: name.match(@PROTO_ASSIGN) @@ -661,7 +663,7 @@ exports.AssignNode: class AssignNode extends BaseNode # Compile the assignment from an array splice literal, using JavaScript's # `Array#splice` method. compile_splice: (o) -> - name: @variable.compile(merge(o, {only_first: true})) + name: @variable.compile(h.merge(o, {only_first: true})) l: @variable.properties.length range: @variable.properties[l - 1].range plus: if range.exclusive then '' else ' + 1' @@ -689,14 +691,14 @@ exports.CodeNode: class CodeNode extends BaseNode # arrow, generates a wrapper that saves the current value of `this` through # a closure. compile_node: (o) -> - shared_scope: del o, 'shared_scope' - top: del o, 'top' + shared_scope: h.del o, 'shared_scope' + top: h.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' - del o, 'globals' + h.del o, 'no_wrap' + h.del o, 'globals' if @params[@params.length - 1] instanceof SplatNode splat: @params.pop() splat.index: @params.length @@ -717,7 +719,7 @@ exports.CodeNode: class CodeNode extends BaseNode # When traversing (for printing or inspecting), return the real children of # the function -- the parameters and body of expressions. real_children: -> - flatten [@params, @body.expressions] + h.flatten [@params, @body.expressions] # Custom `traverse` implementation that uses the `real_children`. traverse: (block) -> @@ -778,8 +780,8 @@ 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 + returns: h.del(o, 'returns') + top: h.del(o, 'top') and not returns o.indent: @idt(1) o.top: true cond: @condition.compile(o) @@ -826,7 +828,7 @@ exports.OpNode: class OpNode extends BaseNode constructor: (operator, first, second, flip) -> @type += ' ' + operator - @children: compact [@first: first, @second: second] + @children: h.compact [@first: first, @second: second] @operator: @CONVERSIONS[operator] or operator @flip: !!flip @@ -885,7 +887,7 @@ exports.TryNode: class TryNode extends BaseNode type: 'Try' constructor: (attempt, error, recovery, ensure) -> - @children: compact [@attempt: attempt, @recovery: recovery, @ensure: ensure] + @children: h.compact [@attempt: attempt, @recovery: recovery, @ensure: ensure] @error: error this @@ -897,7 +899,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(h.merge(o, {returns: null})) + "\n$@tab}" "${@tab}try {\n$attempt_part\n$@tab}$catch_part$finally_part" statement TryNode @@ -984,7 +986,7 @@ exports.ForNode: class ForNode extends BaseNode @step: source.step @object: !!source.object [@name, @index]: [@index, @name] if @object - @children: compact [@body, @source, @filter] + @children: h.compact [@body, @source, @filter] top_sensitive: -> true @@ -994,7 +996,7 @@ exports.ForNode: class ForNode extends BaseNode # 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: h.del(o, 'top') and not o.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 @@ -1011,7 +1013,7 @@ exports.ForNode: class ForNode extends BaseNode if range index_var: scope.free_variable() source_part: source.compile_variables o - for_part: source.compile merge o, {index: ivar, step: @step} + for_part: source.compile h.merge o, {index: ivar, step: @step} for_part: "$index_var = 0, $for_part, $index_var++" else index_var: null @@ -1027,7 +1029,7 @@ exports.ForNode: class ForNode extends BaseNode body: PushNode.wrap(rvar, body) unless top_level if o.returns return_result: 'return ' + return_result - del o, 'returns' + h.del o, 'returns' body: new IfNode(@filter, body, null, {statement: true}) if @filter else if @filter body: Expressions.wrap([new IfNode(@filter, body)]) @@ -1035,7 +1037,7 @@ exports.ForNode: class ForNode extends BaseNode 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})) + body: body.compile(h.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" @@ -1056,7 +1058,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: h.compact [@condition, @body, @else_body] @tags: tags or {} @multiple: true if @condition instanceof Array @condition: new OpNode('!', new ParentheticalNode(@condition)) if @tags.invert @@ -1113,7 +1115,7 @@ exports.IfNode: class IfNode extends BaseNode @statement ||= !!(@comment or @tags.statement or @body.is_statement() or (@else_body and @else_body.is_statement())) compile_condition: (o) -> - (cond.compile(o) for cond in flatten([@condition])).join(' || ') + (cond.compile(o) for cond in h.flatten([@condition])).join(' || ') compile_node: (o) -> if @is_statement() then @compile_statement(o) else @compile_ternary(o) @@ -1122,9 +1124,9 @@ exports.IfNode: class IfNode extends BaseNode # 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' + child: h.del o, 'chain_child' + cond_o: h.merge o + h.del cond_o, 'returns' o.indent: @idt(1) o.top: true if_dent: if child then '' else @idt() @@ -1134,7 +1136,7 @@ exports.IfNode: class IfNode extends BaseNode if_part: "$prefix${if_dent}if (${ @compile_condition(cond_o) }) {\n$body\n$@tab}" return if_part unless @else_body else_part: if @is_chain() - ' else ' + @else_body.compile(merge(o, {indent: @idt(), chain_child: true})) + ' else ' + @else_body.compile(h.merge(o, {indent: @idt(), chain_child: true})) else " else {\n${ Expressions.wrap([@else_body]).compile(o) }\n$@tab}" "$if_part$else_part" diff --git a/src/rewriter.coffee b/src/rewriter.coffee index e11093ad..d96d71b4 100644 --- a/src/rewriter.coffee +++ b/src/rewriter.coffee @@ -8,10 +8,12 @@ # Set up exported variables for both Node.js and the browser. if process? - process.mixin require 'helpers' + helpers: require('helpers').helpers else this.exports: this +h: helpers + # The **Rewriter** class is used by the [Lexer](lexer.html), directly against # its internal array of tokens. exports.Rewriter: class Rewriter @@ -71,7 +73,7 @@ exports.Rewriter: class Rewriter # this, remove their trailing newlines. remove_mid_expression_newlines: -> @scan_tokens (prev, token, post, i) => - return 1 unless post and include(EXPRESSION_CLOSE, post[0]) and token[0] is 'TERMINATOR' + return 1 unless post and h.include(EXPRESSION_CLOSE, post[0]) and token[0] is 'TERMINATOR' @tokens.splice(i, 1) return 0 @@ -121,8 +123,8 @@ exports.Rewriter: class Rewriter stack[stack.length - 1] -= 1 @tokens.splice(i, 0, ['CALL_END', ')', token[2]]) return 2 - if !post? or include IMPLICIT_END, tag - return 1 if tag is 'INDENT' and prev and include IMPLICIT_BLOCK, prev[0] + if !post? or h.include IMPLICIT_END, tag + return 1 if tag is 'INDENT' and prev and h.include IMPLICIT_BLOCK, prev[0] if open or tag is 'INDENT' idx: if tag is 'OUTDENT' then i + 1 else i stack_pointer: if tag is 'INDENT' then 2 else 1 @@ -131,7 +133,7 @@ exports.Rewriter: class Rewriter size: stack[stack.length - stack_pointer] + 1 stack[stack.length - stack_pointer]: 0 return size - return 1 unless prev and include(IMPLICIT_FUNC, prev[0]) and include IMPLICIT_CALL, tag + return 1 unless prev and h.include(IMPLICIT_FUNC, prev[0]) and h.include IMPLICIT_CALL, tag calls: 0 @tokens.splice(i, 0, ['CALL_START', '(', token[2]]) stack[stack.length - 1] += 1 @@ -143,7 +145,7 @@ exports.Rewriter: class Rewriter # but we need to make sure it's balanced. add_implicit_indentation: -> @scan_tokens (prev, token, post, i) => - return 1 unless include(SINGLE_LINERS, token[0]) and + return 1 unless h.include(SINGLE_LINERS, token[0]) and post[0] isnt 'INDENT' and not (token[0] is 'ELSE' and post[0] is 'IF') starter: token[0] @@ -155,7 +157,7 @@ exports.Rewriter: class Rewriter tok: @tokens[idx] pre: @tokens[idx - 1] if (not tok or - (include(SINGLE_CLOSERS, tok[0]) and tok[1] isnt ';') or + (h.include(SINGLE_CLOSERS, tok[0]) and tok[1] isnt ';') or (tok[0] is ')' && parens is 0)) and not (starter is 'ELSE' and tok[0] is 'ELSE') insertion: if pre[0] is "," then idx - 1 else idx @@ -204,10 +206,10 @@ exports.Rewriter: class Rewriter @scan_tokens (prev, token, post, i) => tag: token[0] inv: INVERSES[token[0]] - if include EXPRESSION_START, tag + if h.include EXPRESSION_START, tag stack.push token return 1 - else if include EXPRESSION_END, tag + else if h.include EXPRESSION_END, tag if debt[inv] > 0 debt[inv] -= 1 @tokens.splice i, 1 diff --git a/test/test_functions.coffee b/test/test_functions.coffee index e6d38324..a48d087a 100644 --- a/test/test_functions.coffee +++ b/test/test_functions.coffee @@ -106,3 +106,8 @@ ok result is 'lo' func: (x) -> (x) -> (x) -> x ok func(1)(2)(3) is 3 + + +# Ensure that functions with the same name don't clash with helper functions. +del: -> 5 +ok del() is 5