From 55794d9534944258b6a7962f6e31f25bfa277713 Mon Sep 17 00:00:00 2001 From: satyr Date: Sun, 17 Oct 2010 13:19:51 +0900 Subject: [PATCH] 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