From 3bba51d5d9830b7bc88204e8bc243926859ec1d4 Mon Sep 17 00:00:00 2001 From: satyr Date: Mon, 27 Sep 2010 17:56:56 +0900 Subject: [PATCH] made nodes cache more aggressively, fixing #653 --- lib/lexer.js | 2 +- lib/nodes.js | 180 ++++++++++++++++++++--------------------------- lib/rewriter.js | 12 ++-- src/nodes.coffee | 118 +++++++++++++++++-------------- 4 files changed, 149 insertions(+), 163 deletions(-) diff --git a/lib/lexer.js b/lib/lexer.js index cb45c2cf..a6ff177d 100644 --- a/lib/lexer.js +++ b/lib/lexer.js @@ -380,7 +380,7 @@ if (!(herecomment)) { while ((match = HEREDOC_INDENT.exec(doc))) { attempt = match[1]; - if (indent === null || (0 < attempt.length) && (attempt.length < indent.length)) { + if (indent === null || (0 < (_ref2 = attempt.length)) && (_ref2 < indent.length)) { indent = attempt; } } diff --git a/lib/nodes.js b/lib/nodes.js index 616e96c4..48cd3a1d 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, IS_STRING, IfNode, InNode, IndexNode, LiteralNode, NUMBER, ObjectNode, OpNode, ParamNode, ParentheticalNode, PushNode, RangeNode, ReturnNode, SIMPLENUM, Scope, SliceNode, SplatNode, SwitchNode, TAB, TRAILING_WHITESPACE, ThrowNode, TryNode, UTILITIES, ValueNode, WhileNode, _ref, compact, del, ends, flatten, include, indexOf, literal, merge, starts, utility; + var AccessorNode, ArrayNode, AssignNode, BaseNode, CallNode, ClassNode, ClosureNode, CodeNode, CommentNode, ExistenceNode, Expressions, ExtendsNode, ForNode, IDENTIFIER, IS_STRING, IfNode, InNode, IndexNode, LiteralNode, NO, NUMBER, ObjectNode, OpNode, ParamNode, ParentheticalNode, PushNode, RangeNode, ReturnNode, SIMPLENUM, Scope, SliceNode, SplatNode, SwitchNode, TAB, TRAILING_WHITESPACE, ThrowNode, TryNode, UTILITIES, ValueNode, WhileNode, YES, _ref, compact, del, ends, flatten, include, indexOf, literal, merge, starts, utility; var __extends = function(child, parent) { var ctor = function(){}; ctor.prototype = parent.prototype; @@ -19,6 +19,12 @@ indexOf = _ref.indexOf; starts = _ref.starts; ends = _ref.ends; + YES = function() { + return true; + }; + NO = function() { + return false; + }; exports.BaseNode = (function() { BaseNode = function() { this.tags = {}; @@ -51,7 +57,7 @@ var compiled, pair, reference; options || (options = {}); pair = (function() { - if (!(this.containsType(CallNode) || (this instanceof ValueNode && (!(this.base instanceof LiteralNode) || this.hasProperties())))) { + if (!(this.isComplex())) { return [this, this]; } else if (this instanceof ValueNode && options.assignment) { return this.cacheIndexes(o); @@ -154,15 +160,10 @@ BaseNode.prototype.unwrap = function() { return this; }; - BaseNode.prototype.isStatement = function() { - return false; - }; - BaseNode.prototype.isPureStatement = function() { - return false; - }; - BaseNode.prototype.topSensitive = function() { - return false; - }; + BaseNode.prototype.isStatement = NO; + BaseNode.prototype.isPureStatement = NO; + BaseNode.prototype.isComplex = YES; + BaseNode.prototype.topSensitive = NO; return BaseNode; })(); exports.Expressions = (function() { @@ -174,9 +175,7 @@ __extends(Expressions, BaseNode); Expressions.prototype["class"] = 'Expressions'; Expressions.prototype.children = ['expressions']; - Expressions.prototype.isStatement = function() { - return true; - }; + Expressions.prototype.isStatement = YES; Expressions.prototype.push = function(node) { this.expressions.push(node); return this; @@ -269,6 +268,7 @@ return this.value === 'break' || this.value === 'continue' || this.value === 'debugger'; }; LiteralNode.prototype.isPureStatement = LiteralNode.prototype.isStatement; + LiteralNode.prototype.isComplex = NO; LiteralNode.prototype.compileNode = function(o) { var end, idt; idt = this.isStatement(o) ? this.idt() : ''; @@ -288,12 +288,8 @@ }; __extends(ReturnNode, BaseNode); ReturnNode.prototype["class"] = 'ReturnNode'; - ReturnNode.prototype.isStatement = function() { - return true; - }; - ReturnNode.prototype.isPureStatement = function() { - return true; - }; + ReturnNode.prototype.isStatement = YES; + ReturnNode.prototype.isPureStatement = YES; ReturnNode.prototype.children = ['expression']; ReturnNode.prototype.makeReturn = function() { return this; @@ -341,6 +337,9 @@ ValueNode.prototype.isSplice = function() { return this.hasProperties() && this.properties[this.properties.length - 1] instanceof SliceNode; }; + ValueNode.prototype.isComplex = function() { + return this.base.isComplex() || this.properties.length; + }; ValueNode.prototype.makeReturn = function() { return this.hasProperties() ? ValueNode.__super__.makeReturn.call(this) : this.base.makeReturn(); }; @@ -354,29 +353,23 @@ return this.base instanceof LiteralNode && this.base.value.match(NUMBER); }; ValueNode.prototype.cacheIndexes = function(o) { - var _i, _len, _ref2, copy, i; + var _len, _ref2, _ref3, copy, i, index, indexVar, prop; copy = new ValueNode(this.base, this.properties.slice(0)); - if (this.base instanceof CallNode) { + if (this.base.isComplex()) { _ref2 = this.base.compileReference(o); this.base = _ref2[0]; copy.base = _ref2[1]; } _ref2 = copy.properties; - for (_i = 0, _len = _ref2.length; _i < _len; _i++) { - (function() { - var _ref3, index, indexVar; - var i = _i; - var prop = _ref2[_i]; - if (prop instanceof IndexNode && prop.contains(function(n) { - return n instanceof CallNode; - })) { - _ref3 = prop.index.compileReference(o); - index = _ref3[0]; - indexVar = _ref3[1]; - this.properties[i] = new IndexNode(index); - return (copy.properties[i] = new IndexNode(indexVar)); - } - }).call(this); + for (i = 0, _len = _ref2.length; i < _len; i++) { + prop = _ref2[i]; + if (prop instanceof IndexNode && prop.index.isComplex()) { + _ref3 = prop.index.compileReference(o); + index = _ref3[0]; + indexVar = _ref3[1]; + this.properties[i] = new IndexNode(index); + copy.properties[i] = new IndexNode(indexVar); + } } return [this, copy]; }; @@ -384,19 +377,20 @@ return !o.top || this.properties.length ? ValueNode.__super__.compile.call(this, o) : this.base.compile(o); }; ValueNode.prototype.compileNode = function(o) { - var _i, _len, _ref2, baseline, complete, copy, hasSoak, i, me, only, op, part, prop, props, temp; + var _i, _len, _ref2, baseline, complete, copy, hasSoak, i, me, only, op, part, prevcomp, prop, props, temp; only = del(o, 'onlyFirst'); op = this.tags.operation; - props = only ? this.properties.slice(0, this.properties.length - 1) : this.properties; + props = only ? this.properties.slice(0, -1) : this.properties; o.chainRoot || (o.chainRoot = this); _ref2 = props; for (_i = 0, _len = _ref2.length; _i < _len; _i++) { prop = _ref2[_i]; if (prop.soakNode) { hasSoak = true; + break; } } - if (hasSoak && this.containsType(CallNode)) { + if (hasSoak && this.isComplex()) { _ref2 = this.cacheIndexes(o); me = _ref2[0]; copy = _ref2[1]; @@ -414,19 +408,15 @@ prop = _ref2[i]; this.source = baseline; if (prop.soakNode) { - if (this.base.containsType(CallNode) && i === 0) { + if (i === 0 && this.base.isComplex()) { temp = o.scope.freeVariable('ref'); - complete = ("(" + (baseline = temp) + " = (" + (complete) + "))"); + complete = ("(" + (baseline = temp) + " = (" + (prevcomp = complete) + "))"); } - complete = i === 0 ? ("(typeof " + (complete) + " === \"undefined\" || " + (baseline) + " === null) ? undefined : ") : ("" + (complete) + " == null ? undefined : "); - complete += (baseline += prop.compile(o)); + complete = i === 0 && !o.scope.check(complete) ? ("(typeof " + (complete) + " === \"undefined\" || " + (baseline) + " === null)") : ("" + (complete) + " == null"); + complete += ' ? undefined : ' + (baseline += prop.compile(o)); } else { part = prop.compile(o); - if (hasSoak && prop.containsType(CallNode)) { - baseline += copy.properties[i].compile(o); - } else { - baseline += part; - } + baseline += (hasSoak && prop.isComplex() ? copy.properties[i].compile(o) : part); complete += part; this.last = part; } @@ -443,14 +433,12 @@ }; __extends(CommentNode, BaseNode); CommentNode.prototype["class"] = 'CommentNode'; - CommentNode.prototype.isStatement = function() { - return true; - }; + CommentNode.prototype.isStatement = YES; CommentNode.prototype.makeReturn = function() { return this; }; CommentNode.prototype.compileNode = function(o) { - return this.tab + '/*' + this.comment.replace(/\r?\n/g, '\n' + this.tab) + '*/'; + return this.tab + '/*' + this.comment.replace(/\n/g, '\n' + this.tab) + '*/'; }; return CommentNode; })(); @@ -551,7 +539,7 @@ var _i, _len, _ref2, a, b, c, mentionsArgs, meth, obj, temp; meth = this.meth || this.superReference(o); obj = this.variable && this.variable.source || 'this'; - if (obj.match(/\(/)) { + if (!(IDENTIFIER.test(obj) || NUMBER.test(obj))) { temp = o.scope.freeVariable('ref'); obj = temp; meth = ("(" + (temp) + " = " + (this.variable.source) + ")" + (this.variable.last)); @@ -573,7 +561,7 @@ c = o.scope.freeVariable('result'); return "" + (this.first) + "(function() {\n" + (this.idt(1)) + "var ctor = function(){};\n" + (this.idt(1)) + "__extends(ctor, " + (a) + " = " + (meth) + ");\n" + (this.idt(1)) + "return typeof (" + (c) + " = " + (a) + ".apply(" + (b) + " = new ctor, " + (this.compileSplatArguments(o)) + ")) === \"object\" ? " + (c) + " : " + (b) + ";\n" + (this.tab) + "})." + (mentionsArgs ? 'apply(this, arguments)' : 'call(this)') + (this.last); } else { - return "" + (this.first) + (this.prefix()) + (meth) + ".apply(" + (obj) + ", " + (this.compileSplatArguments(o)) + ")" + (this.last); + return "" + (this.first) + (meth) + ".apply(" + (obj) + ", " + (this.compileSplatArguments(o)) + ")" + (this.last); } }; return CallNode; @@ -613,6 +601,7 @@ namePart = name.match(IS_STRING) ? ("[" + (name) + "]") : ("." + (name)); return this.prototype + namePart; }; + AccessorNode.prototype.isComplex = NO; return AccessorNode; })(); exports.IndexNode = (function() { @@ -631,6 +620,9 @@ prefix = this.proto ? '.prototype' : ''; return "" + (prefix) + "[" + (idx) + "]"; }; + IndexNode.prototype.isComplex = function() { + return this.index.isComplex(); + }; return IndexNode; })(); exports.RangeNode = (function() { @@ -691,7 +683,7 @@ }; RangeNode.prototype.compileSimple = function(o) { var _ref2, from, idx, step, to; - _ref2 = [parseInt(this.fromNum, 10), parseInt(this.toNum, 10)]; + _ref2 = [+this.fromNum, +this.toNum]; from = _ref2[0]; to = _ref2[1]; idx = del(o, 'index'); @@ -700,15 +692,15 @@ return from <= to ? ("" + (idx) + " = " + (from) + "; " + (idx) + " <" + (this.equals) + " " + (to) + "; " + (step || ("" + (idx) + "++"))) : ("" + (idx) + " = " + (from) + "; " + (idx) + " >" + (this.equals) + " " + (to) + "; " + (step || ("" + (idx) + "--"))); }; RangeNode.prototype.compileArray = function(o) { - var _i, _result, body, clause, i, idt, post, pre, range, result, vars; + var _i, _ref2, _ref3, _result, body, clause, i, idt, post, pre, range, result, vars; idt = this.idt(1); vars = this.compileVariables(merge(o, { indent: idt })); if (this.fromNum && this.toNum && (Math.abs(+this.fromNum - +this.toNum) <= 20)) { range = (function() { - _result = []; - for (var _i = +this.fromNum; +this.fromNum <= +this.toNum ? _i <= +this.toNum : _i >= +this.toNum; +this.fromNum <= +this.toNum ? _i += 1 : _i -= 1){ _result.push(_i); } + _result = []; _ref2 = +this.fromNum; _ref3 = +this.toNum; + for (var _i = _ref2; _ref2 <= _ref3 ? _i <= _ref3 : _i >= _ref3; _ref2 <= _ref3 ? _i += 1 : _i -= 1){ _result.push(_i); } return _result; }).call(this); if (this.exclusive) { @@ -761,9 +753,7 @@ __extends(ObjectNode, BaseNode); ObjectNode.prototype["class"] = 'ObjectNode'; ObjectNode.prototype.children = ['properties']; - ObjectNode.prototype.topSensitive = function() { - return true; - }; + ObjectNode.prototype.topSensitive = YES; ObjectNode.prototype.compileNode = function(o) { var _i, _len, _ref2, _result, i, indent, join, lastNoncom, nonComments, obj, prop, props, top; top = del(o, 'top'); @@ -855,9 +845,7 @@ __extends(ClassNode, BaseNode); ClassNode.prototype["class"] = 'ClassNode'; ClassNode.prototype.children = ['variable', 'parent', 'properties']; - ClassNode.prototype.isStatement = function() { - return true; - }; + ClassNode.prototype.isStatement = YES; ClassNode.prototype.makeReturn = function() { this.returns = true; return this; @@ -940,12 +928,10 @@ }; __extends(AssignNode, BaseNode); AssignNode.prototype.PROTO_ASSIGN = /^(\S+)\.prototype/; - AssignNode.prototype.LEADING_DOT = /^\.(prototype\.)?/; + AssignNode.prototype.LEADING_DOT = /^\.(?:prototype\.)?/; AssignNode.prototype["class"] = 'AssignNode'; AssignNode.prototype.children = ['variable', 'value']; - AssignNode.prototype.topSensitive = function() { - return true; - }; + AssignNode.prototype.topSensitive = YES; AssignNode.prototype.isValue = function() { return this.variable instanceof ValueNode; }; @@ -1045,7 +1031,7 @@ from = range.from ? range.from.compile(o) : '0'; to = range.to ? range.to.compile(o) + ' - ' + from + plus : ("" + (name) + ".length"); val = this.value.compile(o); - return "" + (name) + ".splice.apply(" + (name) + ", [" + (from) + ", " + (to) + "].concat(" + (val) + "))"; + return "[].splice.apply(" + (name) + ", [" + (from) + ", " + (to) + "].concat(" + (val) + "))"; }; return AssignNode; })(); @@ -1222,10 +1208,10 @@ prev = args[(last = args.length - 1)]; if (!(arg instanceof SplatNode)) { if (prev && starts(prev, '[') && ends(prev, ']')) { - args[last] = ("" + (prev.substr(0, prev.length - 1)) + ", " + (code) + "]"); + args[last] = ("" + (prev.slice(0, -1)) + ", " + (code) + "]"); continue; } else if (prev && starts(prev, '.concat([') && ends(prev, '])')) { - args[last] = ("" + (prev.substr(0, prev.length - 2)) + ", " + (code) + "])"); + args[last] = ("" + (prev.slice(0, -2)) + ", " + (code) + "])"); continue; } else { code = ("[" + (code) + "]"); @@ -1253,9 +1239,7 @@ __extends(WhileNode, BaseNode); WhileNode.prototype["class"] = 'WhileNode'; WhileNode.prototype.children = ['condition', 'guard', 'body']; - WhileNode.prototype.isStatement = function() { - return true; - }; + WhileNode.prototype.isStatement = YES; WhileNode.prototype.addBody = function(body) { this.body = body; return this; @@ -1335,6 +1319,9 @@ var _ref2; return (('===' === (_ref2 = this.operator) || '!==' === _ref2)) && !(this.first instanceof OpNode) && !(this.second instanceof OpNode); }; + OpNode.prototype.isComplex = function() { + return this.operator !== '!' || this.first.isComplex(); + }; OpNode.prototype.isMutator = function() { var _ref2; return ends(this.operator, '=') && !(('===' === (_ref2 = this.operator) || '!==' === _ref2)); @@ -1372,11 +1359,9 @@ OpNode.prototype.compileChain = function(o) { var _ref2, first, second, shared; shared = this.first.unwrap().second; - if (shared.containsType(CallNode)) { - _ref2 = shared.compileReference(o); - this.first.second = _ref2[0]; - shared = _ref2[1]; - } + _ref2 = shared.compileReference(o); + this.first.second = _ref2[0]; + shared = _ref2[1]; _ref2 = [this.first.compile(o), this.second.compile(o), shared.compile(o)]; first = _ref2[0]; second = _ref2[1]; @@ -1482,9 +1467,7 @@ __extends(TryNode, BaseNode); TryNode.prototype["class"] = 'TryNode'; TryNode.prototype.children = ['attempt', 'recovery', 'ensure']; - TryNode.prototype.isStatement = function() { - return true; - }; + TryNode.prototype.isStatement = YES; TryNode.prototype.makeReturn = function() { if (this.attempt) { this.attempt = this.attempt.makeReturn(); @@ -1515,9 +1498,7 @@ __extends(ThrowNode, BaseNode); ThrowNode.prototype["class"] = 'ThrowNode'; ThrowNode.prototype.children = ['expression']; - ThrowNode.prototype.isStatement = function() { - return true; - }; + ThrowNode.prototype.isStatement = YES; ThrowNode.prototype.makeReturn = function() { return this; }; @@ -1563,12 +1544,13 @@ ParentheticalNode.prototype.isStatement = function(o) { return this.expression.isStatement(o); }; + ParentheticalNode.prototype.isComplex = function() { + return this.expression.isComplex(); + }; + ParentheticalNode.prototype.topSensitive = YES; ParentheticalNode.prototype.makeReturn = function() { return this.expression.makeReturn(); }; - ParentheticalNode.prototype.topSensitive = function() { - return true; - }; ParentheticalNode.prototype.compileNode = function(o) { var code, top; top = del(o, 'top'); @@ -1612,12 +1594,8 @@ __extends(ForNode, BaseNode); ForNode.prototype["class"] = 'ForNode'; ForNode.prototype.children = ['body', 'source', 'guard']; - ForNode.prototype.isStatement = function() { - return true; - }; - ForNode.prototype.topSensitive = function() { - return true; - }; + ForNode.prototype.isStatement = YES; + ForNode.prototype.topSensitive = YES; ForNode.prototype.makeReturn = function() { this.returns = true; return this; @@ -1739,9 +1717,7 @@ __extends(SwitchNode, BaseNode); SwitchNode.prototype["class"] = 'SwitchNode'; SwitchNode.prototype.children = ['subject', 'cases', 'otherwise']; - SwitchNode.prototype.isStatement = function() { - return true; - }; + SwitchNode.prototype.isStatement = YES; SwitchNode.prototype.makeReturn = function() { var _i, _len, _ref2, pair; _ref2 = this.cases; @@ -1807,9 +1783,7 @@ __extends(IfNode, BaseNode); IfNode.prototype["class"] = 'IfNode'; IfNode.prototype.children = ['condition', 'body', 'elseBody', 'assigner']; - IfNode.prototype.topSensitive = function() { - return true; - }; + IfNode.prototype.topSensitive = YES; IfNode.prototype.bodyNode = function() { return this.body == null ? undefined : this.body.unwrap(); }; @@ -1935,8 +1909,8 @@ }; TAB = ' '; TRAILING_WHITESPACE = /[ \t]+$/gm; - IDENTIFIER = /^[a-zA-Z\$_](\w|\$)*$/; - NUMBER = /^(((\b0(x|X)[0-9a-fA-F]+)|((\b[0-9]+(\.[0-9]+)?|\.[0-9]+)(e[+\-]?[0-9]+)?)))\b$/i; + IDENTIFIER = /^[$A-Za-zA-Z_][$\w]*$/; + NUMBER = /^0x[\da-f]+|^(?:\d+(\.\d+)?|\.\d+)(?:e[+-]?\d+)?$/i; SIMPLENUM = /^-?\d+$/; IS_STRING = /^['"]/; literal = function(name) { diff --git a/lib/rewriter.js b/lib/rewriter.js index 98cafa76..5fb9aef0 100644 --- a/lib/rewriter.js +++ b/lib/rewriter.js @@ -66,14 +66,14 @@ after = _ref2[3]; if (after && after[0] === 'INDENT') { this.tokens.splice(i + 2, 1); - if (before && before[0] === 'OUTDENT' && post && (prev[0] === post[0]) && (post[0] === 'TERMINATOR')) { + if (before && before[0] === 'OUTDENT' && post && (prev[0] === (_ref2 = post[0])) && (_ref2 === 'TERMINATOR')) { this.tokens.splice(i - 2, 1); } else { this.tokens.splice(i, 0, after); } } else if (prev && !('TERMINATOR' === (_ref2 = prev[0]) || 'INDENT' === _ref2 || 'OUTDENT' === _ref2)) { if (post && post[0] === 'TERMINATOR' && after && after[0] === 'OUTDENT') { - this.tokens.splice.apply(this.tokens, [i + 2, 0].concat(this.tokens.splice(i, 2))); + (_ref2 = this.tokens).splice.apply(_ref2, [i + 2, 0].concat(this.tokens.splice(i, 2))); if (this.tokens[i + 2][0] !== 'TERMINATOR') { this.tokens.splice(i + 2, 0, ['TERMINATOR', "\n", prev[2]]); } @@ -229,11 +229,11 @@ return this.scanTokens(function(token, i) { var _ref2, action, condition, indent, outdent, starter; if (token[0] === 'ELSE' && this.tag(i - 1) !== 'OUTDENT') { - this.tokens.splice.apply(this.tokens, [i, 0].concat(this.indentation(token))); + (_ref2 = this.tokens).splice.apply(_ref2, [i, 0].concat(this.indentation(token))); return 2; } if (token[0] === 'CATCH' && (this.tag(i + 2) === 'TERMINATOR' || this.tag(i + 2) === 'FINALLY')) { - this.tokens.splice.apply(this.tokens, [i + 2, 0].concat(this.indentation(token))); + (_ref2 = this.tokens).splice.apply(_ref2, [i + 2, 0].concat(this.indentation(token))); return 4; } if (include(SINGLE_LINERS, token[0]) && this.tag(i + 1) !== 'INDENT' && !(token[0] === 'ELSE' && this.tag(i + 1) === 'IF')) { @@ -337,7 +337,7 @@ (debt[key] = 0); } return this.scanTokens(function(token, i) { - var inv, match, mtag, oppos, tag; + var _ref3, inv, match, mtag, oppos, tag; tag = token[0]; inv = INVERSES[token[0]]; if (include(EXPRESSION_START, tag)) { @@ -357,7 +357,7 @@ } debt[mtag] += 1; val = [oppos, mtag === 'INDENT' ? match[1] : oppos]; - if ((this.tokens[i + 2] == null ? undefined : this.tokens[i + 2][0]) === mtag) { + if ((this.tokens[(_ref3 = i + 2)] == null ? undefined : this.tokens[_ref3][0]) === mtag) { this.tokens.splice(i + 3, 0, val); stack.push(match); } else { diff --git a/src/nodes.coffee b/src/nodes.coffee index b646827b..4e6581e1 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -8,6 +8,9 @@ # Import the helpers we plan to use. {compact, flatten, merge, del, include, indexOf, starts, ends} = require './helpers' +YES = -> yes +NO = -> no + #### BaseNode # The **BaseNode** is the abstract base class for all nodes in the syntax tree. @@ -59,8 +62,7 @@ exports.BaseNode = class BaseNode # by assigning it to a temporary variable. compileReference: (o, options) -> options or= {} - pair = if not (@containsType(CallNode) or - (this instanceof ValueNode and (not (@base instanceof LiteralNode) or @hasProperties()))) + pair = unless @isComplex() [this, this] else if this instanceof ValueNode and options.assignment this.cacheIndexes(o) @@ -137,9 +139,10 @@ exports.BaseNode = class BaseNode children: [] unwrap : -> this - isStatement : -> no - isPureStatement : -> no - topSensitive : -> no + isStatement : NO + isPureStatement : NO + isComplex : YES + topSensitive : NO #### Expressions @@ -150,7 +153,7 @@ exports.Expressions = class Expressions extends BaseNode class: 'Expressions' children: ['expressions'] - isStatement: -> yes + isStatement: YES constructor: (nodes) -> super() @@ -247,6 +250,8 @@ exports.LiteralNode = class LiteralNode extends BaseNode @value is 'break' or @value is 'continue' or @value is 'debugger' isPureStatement: LiteralNode::isStatement + isComplex: NO + compileNode: (o) -> idt = if @isStatement(o) then @idt() else '' end = if @isStatement(o) then ';' else '' @@ -261,8 +266,8 @@ exports.LiteralNode = class LiteralNode extends BaseNode exports.ReturnNode = class ReturnNode extends BaseNode class: 'ReturnNode' - isStatement: -> yes - isPureStatement: -> yes + isStatement: YES + isPureStatement: YES children: ['expression'] constructor: (@expression) -> @@ -313,6 +318,9 @@ exports.ValueNode = class ValueNode extends BaseNode isSplice: -> @hasProperties() and @properties[@properties.length - 1] instanceof SliceNode + isComplex: -> + @base.isComplex() or @properties.length + makeReturn: -> if @hasProperties() then super() else @base.makeReturn() @@ -333,10 +341,10 @@ exports.ValueNode = class ValueNode extends BaseNode # the value of the indexes. cacheIndexes: (o) -> copy = new ValueNode @base, @properties[0..] - if @base instanceof CallNode + if @base.isComplex() [@base, copy.base] = @base.compileReference o for prop, i in copy.properties - if prop instanceof IndexNode and prop.contains((n) -> n instanceof CallNode) + if prop instanceof IndexNode and prop.index.isComplex() [index, indexVar] = prop.index.compileReference o this.properties[i] = new IndexNode index copy.properties[i] = new IndexNode indexVar @@ -353,11 +361,12 @@ exports.ValueNode = class ValueNode extends BaseNode compileNode: (o) -> only = del o, 'onlyFirst' op = @tags.operation - props = if only then @properties[0...@properties.length - 1] else @properties + props = if only then @properties[0...-1] else @properties o.chainRoot or= this - for prop in props - hasSoak = yes if prop.soakNode - if hasSoak and @containsType CallNode + for prop in props when prop.soakNode + hasSoak = yes + break + if hasSoak and @isComplex() [me, copy] = @cacheIndexes o @base.parenthetical = yes if @parenthetical and not props.length baseline = @base.compile o @@ -367,20 +376,20 @@ exports.ValueNode = class ValueNode extends BaseNode for prop, i in props @source = baseline if prop.soakNode - if @base.containsType(CallNode) and i is 0 + if i is 0 and @base.isComplex() temp = o.scope.freeVariable 'ref' - complete = "(#{ baseline = temp } = (#{complete}))" - complete = if i is 0 - "(typeof #{complete} === \"undefined\" || #{baseline} === null) ? undefined : " + complete = "(#{ baseline = temp } = (#{prevcomp = complete}))" + complete = if i is 0 and not o.scope.check complete + "(typeof #{complete} === \"undefined\" || #{baseline} === null)" else - "#{complete} == null ? undefined : " - complete += (baseline += prop.compile(o)) + "#{complete} == null" + complete += ' ? undefined : ' + baseline += prop.compile o else part = prop.compile(o) - if hasSoak and prop.containsType CallNode - baseline += copy.properties[i].compile o + baseline += if hasSoak and prop.isComplex() + copy.properties[i].compile o else - baseline += part + part complete += part @last = part @@ -393,7 +402,7 @@ exports.ValueNode = class ValueNode extends BaseNode exports.CommentNode = class CommentNode extends BaseNode class: 'CommentNode' - isStatement: -> yes + isStatement: YES constructor: (@comment) -> super() @@ -402,7 +411,7 @@ exports.CommentNode = class CommentNode extends BaseNode this compileNode: (o) -> - @tab + '/*' + @comment.replace(/\r?\n/g, '\n' + @tab) + '*/' + @tab + '/*' + @comment.replace(/\n/g, '\n' + @tab) + '*/' #### CallNode @@ -481,7 +490,7 @@ exports.CallNode = class CallNode extends BaseNode compileSplat: (o) -> meth = @meth or @superReference(o) obj = @variable and @variable.source or 'this' - if obj.match(/\(/) + unless IDENTIFIER.test(obj) or NUMBER.test(obj) temp = o.scope.freeVariable 'ref' obj = temp meth = "(#{temp} = #{ @variable.source })#{ @variable.last }" @@ -501,7 +510,7 @@ exports.CallNode = class CallNode extends BaseNode #{@tab}}).#{ if mentionsArgs then 'apply(this, arguments)' else 'call(this)'}#{@last} """ else - "#{@first}#{@prefix()}#{meth}.apply(#{obj}, #{ @compileSplatArguments(o) })#{@last}" + "#{@first}#{meth}.apply(#{obj}, #{ @compileSplatArguments(o) })#{@last}" #### ExtendsNode @@ -541,6 +550,8 @@ exports.AccessorNode = class AccessorNode extends BaseNode namePart = if name.match(IS_STRING) then "[#{name}]" else ".#{name}" @prototype + namePart + isComplex: NO + #### IndexNode # A `[ ... ]` indexed accessor into an array or object. @@ -558,6 +569,8 @@ exports.IndexNode = class IndexNode extends BaseNode prefix = if @proto then '.prototype' else '' "#{prefix}[#{idx}]" + isComplex: -> @index.isComplex() + #### RangeNode # A range literal. Ranges can be used to extract portions (slices) of arrays, @@ -601,7 +614,7 @@ exports.RangeNode = class RangeNode extends BaseNode # Compile a simple range comprehension, with integers. compileSimple: (o) -> - [from, to] = [parseInt(@fromNum, 10), parseInt(@toNum, 10)] + [from, to] = [+@fromNum, +@toNum] idx = del o, 'index' step = del o, 'step' step and= "#{idx} += #{step.compile(o)}" @@ -614,7 +627,7 @@ exports.RangeNode = class RangeNode extends BaseNode compileArray: (o) -> idt = @idt 1 vars = @compileVariables merge o, indent: idt - if @fromNum and @toNum and Math.abs(+@fromNum - +@toNum) <= 20 + if @fromNum and @toNum and Math.abs(@fromNum - @toNum) <= 20 range = [+@fromNum..+@toNum] range.pop() if @exclusive return "[#{ range.join(', ') }]" @@ -658,7 +671,7 @@ exports.ObjectNode = class ObjectNode extends BaseNode class: 'ObjectNode' children: ['properties'] - topSensitive: -> true + topSensitive: YES constructor: (props) -> super() @@ -720,7 +733,7 @@ exports.ClassNode = class ClassNode extends BaseNode class: 'ClassNode' children: ['variable', 'parent', 'properties'] - isStatement: -> yes + isStatement: YES # Initialize a **ClassNode** with its name, an optional superclass, and a # list of prototype property assignments. @@ -794,7 +807,7 @@ exports.AssignNode = class AssignNode extends BaseNode # Matchers for detecting prototype assignments. PROTO_ASSIGN: /^(\S+)\.prototype/ - LEADING_DOT: /^\.(prototype\.)?/ + LEADING_DOT: /^\.(?:prototype\.)?/ class: 'AssignNode' children: ['variable', 'value'] @@ -802,8 +815,7 @@ exports.AssignNode = class AssignNode extends BaseNode constructor: (@variable, @value, @context) -> super() - topSensitive: -> - true + topSensitive: YES isValue: -> @variable instanceof ValueNode @@ -887,7 +899,7 @@ exports.AssignNode = class AssignNode extends BaseNode from = if range.from then range.from.compile(o) else '0' to = if range.to then range.to.compile(o) + ' - ' + from + plus else "#{name}.length" val = @value.compile(o) - "#{name}.splice.apply(#{name}, [#{from}, #{to}].concat(#{val}))" + "[].splice.apply(#{name}, [#{from}, #{to}].concat(#{val}))" #### CodeNode @@ -1032,10 +1044,10 @@ exports.SplatNode = class SplatNode extends BaseNode prev = args[last = args.length - 1] if arg not instanceof SplatNode if prev and starts(prev, '[') and ends(prev, ']') - args[last] = "#{prev.substr(0, prev.length - 1)}, #{code}]" + args[last] = "#{prev.slice 0, -1}, #{code}]" continue else if prev and starts(prev, '.concat([') and ends(prev, '])') - args[last] = "#{prev.substr(0, prev.length - 2)}, #{code}])" + args[last] = "#{prev.slice 0, -2}, #{code}])" continue else code = "[#{code}]" @@ -1051,7 +1063,7 @@ exports.WhileNode = class WhileNode extends BaseNode class: 'WhileNode' children: ['condition', 'guard', 'body'] - isStatement: -> yes + isStatement: YES constructor: (condition, opts) -> super() @@ -1139,6 +1151,7 @@ exports.OpNode = class OpNode extends BaseNode (@operator in ['===', '!==']) and not (@first instanceof OpNode) and not (@second instanceof OpNode) + isComplex: -> @operator isnt '!' or @first.isComplex() isMutator: -> ends(@operator, '=') and not (@operator in ['===', '!==']) @@ -1168,7 +1181,7 @@ exports.OpNode = class OpNode extends BaseNode # true compileChain: (o) -> shared = @first.unwrap().second - [@first.second, shared] = shared.compileReference(o) if shared.containsType CallNode + [@first.second, shared] = shared.compileReference o [first, second, shared] = [@first.compile(o), @second.compile(o), shared.compile(o)] "(#{first}) && (#{shared} #{@operator} #{second})" @@ -1230,7 +1243,7 @@ exports.TryNode = class TryNode extends BaseNode class: 'TryNode' children: ['attempt', 'recovery', 'ensure'] - isStatement: -> yes + isStatement: YES constructor: (@attempt, @error, @recovery, @ensure) -> super() @@ -1258,7 +1271,7 @@ exports.ThrowNode = class ThrowNode extends BaseNode class: 'ThrowNode' children: ['expression'] - isStatement: -> yes + isStatement: YES constructor: (@expression) -> super() @@ -1311,13 +1324,14 @@ exports.ParentheticalNode = class ParentheticalNode extends BaseNode isStatement: (o) -> @expression.isStatement(o) + isComplex: -> + @expression.isComplex() + + topSensitive: YES makeReturn: -> @expression.makeReturn() - topSensitive: -> - yes - compileNode: (o) -> top = del o, 'top' @expression.parenthetical = true @@ -1340,7 +1354,7 @@ exports.ForNode = class ForNode extends BaseNode class: 'ForNode' children: ['body', 'source', 'guard'] - isStatement: -> yes + isStatement: YES constructor: (@body, source, @name, @index) -> super() @@ -1355,8 +1369,7 @@ exports.ForNode = class ForNode extends BaseNode throw new Error('index cannot be a pattern matching expression') if @index instanceof ValueNode @returns = false - topSensitive: -> - true + topSensitive: YES makeReturn: -> @returns = true @@ -1430,7 +1443,7 @@ exports.SwitchNode = class SwitchNode extends BaseNode class: 'SwitchNode' children: ['subject', 'cases', 'otherwise'] - isStatement: -> yes + isStatement: YES constructor: (@subject, @cases, @otherwise) -> super() @@ -1471,7 +1484,7 @@ exports.IfNode = class IfNode extends BaseNode class: 'IfNode' children: ['condition', 'body', 'elseBody', 'assigner'] - topSensitive: -> true + topSensitive: YES constructor: (@condition, @body, @tags) -> @tags or= {} @@ -1629,10 +1642,9 @@ TAB = ' ' # with Git. TRAILING_WHITESPACE = /[ \t]+$/gm -# Keep these identifier regexes in sync with the Lexer. -IDENTIFIER = /^[a-zA-Z\$_](\w|\$)*$/ -NUMBER = /^(((\b0(x|X)[0-9a-fA-F]+)|((\b[0-9]+(\.[0-9]+)?|\.[0-9]+)(e[+\-]?[0-9]+)?)))\b$/i -SIMPLENUM = /^-?\d+$/ +IDENTIFIER = /^[$A-Za-zA-Z_][$\w]*$/ +NUMBER = /^0x[\da-f]+|^(?:\d+(\.\d+)?|\.\d+)(?:e[+-]?\d+)?$/i +SIMPLENUM = /^-?\d+$/ # Is a literal value a string? IS_STRING = /^['"]/