From 55794d9534944258b6a7962f6e31f25bfa277713 Mon Sep 17 00:00:00 2001 From: satyr Date: Sun, 17 Oct 2010 13:19:51 +0900 Subject: [PATCH 1/3] nodes: soaking Call now converts to If using the same logic as soaking Accessor --- documentation/coffee/soaks.coffee | 2 +- lib/nodes.js | 113 +++++++++++++++--------------- src/nodes.coffee | 76 ++++++++++---------- test/test_existence.coffee | 25 ++++--- 4 files changed, 109 insertions(+), 107 deletions(-) diff --git a/documentation/coffee/soaks.coffee b/documentation/coffee/soaks.coffee index c0256e10..16b2350f 100644 --- a/documentation/coffee/soaks.coffee +++ b/documentation/coffee/soaks.coffee @@ -1 +1 @@ -lottery.drawWinner()?.address?.zipcode +lottery.drawWinner?().address?.zipcode diff --git a/lib/nodes.js b/lib/nodes.js index 652837d5..51076035 100644 --- a/lib/nodes.js +++ b/lib/nodes.js @@ -105,7 +105,7 @@ } return _result; }).call(this).join(''); - klass = override || this.constructor.name + (this.soakNode || this.exist ? '?' : ''); + klass = override || this.constructor.name + (this.soakNode ? '?' : ''); return '\n' + idt + klass + children; }; Base.prototype.eachChild = function(func) { @@ -152,6 +152,7 @@ Base.prototype.isPureStatement = NO; Base.prototype.isComplex = YES; Base.prototype.topSensitive = NO; + Base.prototype.unfoldSoak = NO; Base.prototype.assigns = NO; return Base; })(); @@ -391,9 +392,9 @@ return !o.top || this.properties.length ? Value.__super__.compile.call(this, o) : this.base.compile(o); }; Value.prototype.compileNode = function(o) { - var _i, _len, code, ex, prop, props; - if (ex = this.unfoldSoak(o)) { - return ex.compile(o); + var _i, _len, code, ifn, prop, props; + if (ifn = this.unfoldSoak(o)) { + return ifn.compile(o); } props = this.properties; if (this.parenthetical && !props.length) { @@ -411,9 +412,9 @@ }; Value.prototype.unfoldSoak = function(o) { var _len, _ref2, fst, i, ifn, prop, ref, snd; - if (this.base.soakNode) { - Array.prototype.push.apply(this.base.body.properties, this.properties); - return this.base; + if (ifn = this.base.unfoldSoak(o)) { + Array.prototype.push.apply(ifn.body.properties, this.properties); + return ifn; } for (i = 0, _len = (_ref2 = this.properties).length; i < _len; i++) { prop = _ref2[i]; @@ -427,28 +428,21 @@ snd.base = ref; } ifn = new If(new Existence(fst), snd, { - operation: true + soak: true }); - ifn.soakNode = true; return ifn; } } return null; }; Value.unfoldSoak = function(o, parent, name) { - var ifnode, node; - node = parent[name]; - if (node instanceof If && node.soakNode) { - ifnode = node; - } else if (node instanceof Value) { - ifnode = node.unfoldSoak(o); - } - if (!ifnode) { + var ifn; + if (!(ifn = parent[name].unfoldSoak(o))) { return; } - parent[name] = ifnode.body; - ifnode.body = new Value(parent); - return ifnode; + parent[name] = ifn.body; + ifn.body = new Value(parent); + return ifn; }; return Value; }).call(this); @@ -472,7 +466,7 @@ exports.Call = (function() { Call = (function() { function Call(variable, _arg, _arg2) { - this.exist = _arg2; + this.soakNode = _arg2; this.args = _arg; Call.__super__.constructor.call(this); this.isNew = false; @@ -508,7 +502,25 @@ return method.klass ? ("" + (method.klass) + ".__super__." + name) : ("" + name + ".__super__.constructor"); }; Call.prototype.unfoldSoak = function(o) { - var _i, _len, _ref2, call, list, node; + var _i, _len, _ref2, _ref3, call, ifn, left, list, rite, val; + if (this.soakNode) { + if (val = this.variable) { + if (!(val instanceof Value)) { + val = new Value(val); + } + _ref2 = val.cacheReference(o), left = _ref2[0], rite = _ref2[1]; + } else { + left = new Literal(this.superReference(o)); + rite = new Value(left); + } + rite = new Call(rite, this.args); + rite.isNew = this.isNew; + left = new Literal("typeof " + (left.compile(o)) + " === \"function\""); + ifn = new If(left, new Value(rite), { + soak: true + }); + return ifn; + } call = this; list = []; while (true) { @@ -525,51 +537,35 @@ break; } } - for (_i = 0, _len = (_ref2 = list.reverse()).length; _i < _len; _i++) { - call = _ref2[_i]; - if (node) { + for (_i = 0, _len = (_ref3 = list.reverse()).length; _i < _len; _i++) { + call = _ref3[_i]; + if (ifn) { if (call.variable instanceof Call) { - call.variable = node; + call.variable = ifn; } else { - call.variable.base = node; + call.variable.base = ifn; } } - node = Value.unfoldSoak(o, call, 'variable'); + ifn = Value.unfoldSoak(o, call, 'variable'); } - return node; + return ifn; }; Call.prototype.compileNode = function(o) { - var _i, _j, _len, _len2, _ref2, _ref3, _ref4, _ref5, _result, arg, args, left, node, rite, val; - if (node = this.unfoldSoak(o)) { - return node.compile(o); + var _i, _j, _len, _len2, _ref2, _ref3, _ref4, _result, arg, args, ifn; + if (ifn = this.unfoldSoak(o)) { + return ifn.compile(o); } (((_ref2 = this.variable) != null) ? (_ref2.tags.front = this.tags.front) : undefined); - if (this.exist) { - if (val = this.variable) { - if (!(val instanceof Value)) { - val = new Value(val); - } - _ref3 = val.cacheReference(o), left = _ref3[0], rite = _ref3[1]; - rite = new Call(rite, this.args); - } else { - left = new Literal(this.superReference(o)); - rite = new Call(new Value(left), this.args); - rite.isNew = this.isNew; - } - left = ("typeof " + (left.compile(o)) + " !== \"function\""); - rite = rite.compile(o); - return ("(" + left + " ? undefined : " + rite + ")"); - } - for (_i = 0, _len = (_ref4 = this.args).length; _i < _len; _i++) { - arg = _ref4[_i]; + for (_i = 0, _len = (_ref3 = this.args).length; _i < _len; _i++) { + arg = _ref3[_i]; if (arg instanceof Splat) { return this.compileSplat(o); } } args = (function() { _result = []; - for (_j = 0, _len2 = (_ref5 = this.args).length; _j < _len2; _j++) { - arg = _ref5[_j]; + for (_j = 0, _len2 = (_ref4 = this.args).length; _j < _len2; _j++) { + arg = _ref4[_j]; _result.push((arg.parenthetical = true) && arg.compile(o)); } return _result; @@ -1816,11 +1812,11 @@ })(); exports.If = (function() { If = (function() { - function If(condition, _arg, _arg2) { - this.tags = _arg2; + function If(condition, _arg, tags) { this.body = _arg; - this.tags || (this.tags = {}); - this.condition = this.tags.invert ? condition.invert() : condition; + this.tags = tags || (tags = {}); + this.condition = tags.invert ? condition.invert() : condition; + this.soakNode = tags.soak; this.elseBody = null; this.isChain = false; return this; @@ -1898,7 +1894,10 @@ ifPart = this.condition.compile(o) + ' ? ' + this.bodyNode().compile(o); elsePart = this.elseBody ? this.elseBodyNode().compile(o) : 'undefined'; code = ("" + ifPart + " : " + elsePart); - return this.tags.operation ? ("(" + code + ")") : code; + return this.tags.operation || this.soakNode ? ("(" + code + ")") : code; + }; + If.prototype.unfoldSoak = function() { + return this.soakNode && this; }; return If; })(); diff --git a/src/nodes.coffee b/src/nodes.coffee index d3eb5f1d..61114f01 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -110,7 +110,7 @@ exports.Base = class Base toString: (idt, override) -> idt or= '' children = (child.toString idt + TAB for child in @collectChildren()).join('') - klass = override or @constructor.name + if @soakNode or @exist then '?' else '' + klass = override or @constructor.name + if @soakNode then '?' else '' '\n' + idt + klass + children eachChild: (func) -> @@ -141,6 +141,7 @@ exports.Base = class Base isPureStatement : NO isComplex : YES topSensitive : NO + unfoldSoak : NO # Is this node used to assign a certain variable? assigns: NO @@ -376,7 +377,7 @@ exports.Value = class Value extends Base # operators `?.` interspersed. Then we have to take care not to accidentally # evaluate a anything twice when building the soak chain. compileNode: (o) -> - return ex.compile o if ex = @unfoldSoak o + return ifn.compile o if ifn = @unfoldSoak o props = @properties @base.parenthetical = yes if @parenthetical and not props.length code = @base.compile o @@ -386,9 +387,9 @@ exports.Value = class Value extends Base # Unfold a soak into an `If`: `a?.b` -> `a.b if a?` unfoldSoak: (o) -> - if @base.soakNode - Array::push.apply @base.body.properties, @properties - return @base + if ifn = @base.unfoldSoak o + Array::push.apply ifn.body.properties, @properties + return ifn for prop, i in @properties when prop.soakNode prop.soakNode = off fst = new Value @base, @properties.slice 0, i @@ -397,22 +398,16 @@ exports.Value = class Value extends Base ref = new Literal o.scope.freeVariable 'ref' fst = new Parens new Assign ref, fst snd.base = ref - ifn = new If new Existence(fst), snd, operation: yes - ifn.soakNode = on + ifn = new If new Existence(fst), snd, soak: yes return ifn null # Unfold a node's child if soak, then tuck the node under created `If` @unfoldSoak: (o, parent, name) -> - node = parent[name] - if node instanceof If and node.soakNode - ifnode = node - else if node instanceof Value - ifnode = node.unfoldSoak o - return unless ifnode - parent[name] = ifnode.body - ifnode.body = new Value parent - ifnode + return unless ifn = parent[name].unfoldSoak o + parent[name] = ifn.body + ifn.body = new Value parent + ifn #### Comment @@ -438,7 +433,7 @@ exports.Call = class Call extends Base children: ['variable', 'args'] - constructor: (variable, @args, @exist) -> + constructor: (variable, @args, @soakNode) -> super() @isNew = false @isSuper = variable is 'super' @@ -469,6 +464,18 @@ exports.Call = class Call extends Base # Soaked chained invocations unfold into if/else ternary structures. unfoldSoak: (o) -> + if @soakNode + if val = @variable + val = new Value val unless val instanceof Value + [left, rite] = val.cacheReference o + else + left = new Literal @superReference o + rite = new Value left + rite = new Call rite, @args + rite.isNew = @isNew + left = new Literal "typeof #{ left.compile o } === \"function\"" + ifn = new If left, new Value(rite), soak: yes + return ifn call = this list = [] loop @@ -480,30 +487,18 @@ exports.Call = class Call extends Base list.push call break unless (call = call.variable.base) instanceof Call for call in list.reverse() - if node + if ifn if call.variable instanceof Call - call.variable = node + call.variable = ifn else - call.variable.base = node - node = Value.unfoldSoak o, call, 'variable' - node + call.variable.base = ifn + ifn = Value.unfoldSoak o, call, 'variable' + ifn # Compile a vanilla function call. compileNode: (o) -> - return node.compile o if node = @unfoldSoak o + return ifn.compile o if ifn = @unfoldSoak o @variable?.tags.front = @tags.front - if @exist - if val = @variable - val = new Value val unless val instanceof Value - [left, rite] = val.cacheReference o - rite = new Call rite, @args - else - left = new Literal @superReference o - rite = new Call new Value(left), @args - rite.isNew = @isNew - left = "typeof #{ left.compile o } !== \"function\"" - rite = rite.compile o - return "(#{left} ? undefined : #{rite})" for arg in @args when arg instanceof Splat return @compileSplat o args = ((arg.parenthetical = on) and arg.compile o for arg in @args).join ', ' @@ -1528,9 +1523,10 @@ exports.If = class If extends Base topSensitive: YES - constructor: (condition, @body, @tags) -> - @tags or= {} - @condition = if @tags.invert then condition.invert() else condition + constructor: (condition, @body, tags) -> + @tags = tags or= {} + @condition = if tags.invert then condition.invert() else condition + @soakNode = tags.soak @elseBody = null @isChain = false @@ -1592,7 +1588,9 @@ exports.If = class If extends Base ifPart = @condition.compile(o) + ' ? ' + @bodyNode().compile(o) elsePart = if @elseBody then @elseBodyNode().compile(o) else 'undefined' code = "#{ifPart} : #{elsePart}" - if @tags.operation then "(#{code})" else code + if @tags.operation or @soakNode then "(#{code})" else code + + unfoldSoak: -> @soakNode and this # Faux-Nodes # ---------- diff --git a/test/test_existence.coffee b/test/test_existence.coffee index 3b2b2c6a..e0687bf8 100644 --- a/test/test_existence.coffee +++ b/test/test_existence.coffee @@ -113,21 +113,26 @@ obj = { returnThis: -> this } -ok plus1?(41) is 42 -ok (plus1? 41) is 42 -ok plus2?(41) is undefined -ok (plus2? 41) is undefined -ok obj.returnThis?() is obj -ok obj.counter().counter().returnThis?() is obj -ok count is 2 +eq plus1?(41), 42 +eq (plus1? 41), 42 +eq plus2?(41), undefined +eq (plus2? 41), undefined +eq obj.returnThis?(), obj +eq obj.returnSelf?(), undefined +eq obj.returnThis?().flag = on, on +eq obj.returnSelf?().flag = on, undefined +eq obj.counter().counter().returnThis?(), obj +eq count, 2 maybe_close = (f, arg) -> if typeof f is 'function' then () -> f(arg) else -1 -ok maybe_close(plus1, 41)?() is 42 -ok (maybe_close plus1, 41)?() is 42 -ok (maybe_close 'string', 41)?() is undefined +eq maybe_close(plus1, 41)?(), 42 +eq (maybe_close plus1, 41)?(), 42 +eq (maybe_close 'string', 41)?(), undefined eq 2?(3), undefined +eq new Number?(42) | 0, 42 +eq new Bumper?(42) | 0, 0 #726 eq calendar?[Date()], undefined From 8d0a0e8ab1cae695eb257ec4a169ebd620c62609 Mon Sep 17 00:00:00 2001 From: satyr Date: Sun, 17 Oct 2010 13:53:02 +0900 Subject: [PATCH 2/3] nodes: Value.unfoldSoak -> If.unfoldSoak --- lib/nodes.js | 26 +++++++++++++------------- src/nodes.coffee | 18 +++++++++--------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/lib/nodes.js b/lib/nodes.js index 51076035..a6715ea2 100644 --- a/lib/nodes.js +++ b/lib/nodes.js @@ -435,17 +435,8 @@ } return null; }; - Value.unfoldSoak = function(o, parent, name) { - var ifn; - if (!(ifn = parent[name].unfoldSoak(o))) { - return; - } - parent[name] = ifn.body; - ifn.body = new Value(parent); - return ifn; - }; return Value; - }).call(this); + })(); exports.Comment = (function() { Comment = (function() { function Comment(_arg) { @@ -546,7 +537,7 @@ call.variable.base = ifn; } } - ifn = Value.unfoldSoak(o, call, 'variable'); + ifn = If.unfoldSoak(o, call, 'variable'); } return ifn; }; @@ -1001,7 +992,7 @@ if (this.variable.isSplice()) { return this.compileSplice(o); } - if (ifn = Value.unfoldSoak(o, this, 'variable')) { + if (ifn = If.unfoldSoak(o, this, 'variable')) { delete o.top; return ifn.compile(o); } @@ -1899,8 +1890,17 @@ If.prototype.unfoldSoak = function() { return this.soakNode && this; }; + If.unfoldSoak = function(o, parent, name) { + var ifn; + if (!(ifn = parent[name].unfoldSoak(o))) { + return; + } + parent[name] = ifn.body; + ifn.body = new Value(parent); + return ifn; + }; return If; - })(); + }).call(this); Push = { wrap: function(name, expressions) { if (expressions.empty() || expressions.containsPureStatement()) { diff --git a/src/nodes.coffee b/src/nodes.coffee index 61114f01..a4d632ed 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -402,13 +402,6 @@ exports.Value = class Value extends Base return ifn null - # Unfold a node's child if soak, then tuck the node under created `If` - @unfoldSoak: (o, parent, name) -> - return unless ifn = parent[name].unfoldSoak o - parent[name] = ifn.body - ifn.body = new Value parent - ifn - #### Comment # CoffeeScript passes through block comments as JavaScript block comments @@ -492,7 +485,7 @@ exports.Call = class Call extends Base call.variable = ifn else call.variable.base = ifn - ifn = Value.unfoldSoak o, call, 'variable' + ifn = If.unfoldSoak o, call, 'variable' ifn # Compile a vanilla function call. @@ -863,7 +856,7 @@ exports.Assign = class Assign extends Base if isValue = @isValue() return @compilePatternMatch(o) if @variable.isArray() or @variable.isObject() return @compileSplice(o) if @variable.isSplice() - if ifn = Value.unfoldSoak o, this, 'variable' + if ifn = If.unfoldSoak o, this, 'variable' delete o.top return ifn.compile o top = del o, 'top' @@ -1592,6 +1585,13 @@ exports.If = class If extends Base unfoldSoak: -> @soakNode and this + # Unfold a node's child if soak, then tuck the node under created `If` + @unfoldSoak: (o, parent, name) -> + return unless ifn = parent[name].unfoldSoak o + parent[name] = ifn.body + ifn.body = new Value parent + ifn + # Faux-Nodes # ---------- # Faux-nodes are never created by the grammar, but are used during code From 87560d943ce67c8363dd6014f876589729984086 Mon Sep 17 00:00:00 2001 From: satyr Date: Mon, 18 Oct 2010 07:43:29 +0900 Subject: [PATCH 3/3] lexer: made REGEX more efficient --- lib/lexer.js | 9 +++++---- src/lexer.coffee | 20 +++++++++++++------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/lib/lexer.js b/lib/lexer.js index 762ea83e..1b9f97ac 100644 --- a/lib/lexer.js +++ b/lib/lexer.js @@ -179,7 +179,7 @@ return true; }; Lexer.prototype.regexToken = function() { - var match; + var match, regex; if (this.chunk.charAt(0) !== '/') { return false; } @@ -192,8 +192,9 @@ if (!(match = REGEX.exec(this.chunk))) { return false; } - this.token('REGEX', match[0]); - this.i += match[0].length; + regex = match[0]; + this.token('REGEX', regex === '//' ? '/(?:)/' : regex); + this.i += regex.length; return true; }; Lexer.prototype.heregexToken = function(match) { @@ -610,7 +611,7 @@ MULTI_DENT = /^(?:\n[ \t]*)+/; SIMPLESTR = /^'[^\\']*(?:\\.[^\\']*)*'/; JSTOKEN = /^`[^\\`]*(?:\\.[^\\`]*)*`/; - REGEX = /^\/(?!\s)(?:[^[\/\n\\]+|\\[\s\S]|\[([^\]\n\\]+|\\[\s\S])*])+\/[imgy]{0,4}(?![A-Za-z])/; + REGEX = /^\/(?!\s)[^[\/\n\\]*(?:(?:\\[\s\S]|\[[^\]\n\\]*(?:\\[\s\S][^\]\n\\]*)*])[^[\/\n\\]*)*\/[imgy]{0,4}(?![A-Za-z])/; HEREGEX = /^\/{3}([\s\S]+?)\/{3}([imgy]{0,4})(?![A-Za-z])/; HEREGEX_OMIT = /\s+(?:#.*)?/g; MULTILINER = /\n/g; diff --git a/src/lexer.coffee b/src/lexer.coffee index 1c30123f..b34104ff 100644 --- a/src/lexer.coffee +++ b/src/lexer.coffee @@ -189,8 +189,9 @@ exports.Lexer = class Lexer return @heregexToken match if match = HEREGEX.exec @chunk return false if include NOT_REGEX, @tag() return false unless match = REGEX.exec @chunk - @token 'REGEX', match[0] - @i += match[0].length + [regex] = match + @token 'REGEX', if regex is '//' then '/(?:)/' else regex + @i += regex.length true # Matches experimental, multiline and extended regular expression literals. @@ -559,11 +560,16 @@ JSTOKEN = /^`[^\\`]*(?:\\.[^\\`]*)*`/ # Regex-matching-regexes. REGEX = /// ^ - / (?!\s) # disallow leading whitespace - (?: [^ [ / \n \\ ]+ # every other thing - | \\[\s\S] # anything escaped - | \[ ( [^ \] \n \\ ]+ | \\[\s\S] )* ] # character class - )+ + / (?! \s ) # disallow leading whitespace + [^ [ / \n \\ ]* # every other thing + (?: + (?: \\[\s\S] # anything escaped + | \[ # character class + [^ \] \n \\ ]* + (?: \\[\s\S] [^ \] \n \\ ]* )* + ] + ) [^ [ / \n \\ ]* + )* / [imgy]{0,4} (?![A-Za-z]) /// HEREGEX = /^\/{3}([\s\S]+?)\/{3}([imgy]{0,4})(?![A-Za-z])/