From e5fe145f800c8fcfd0d715584dbf6de940140dee Mon Sep 17 00:00:00 2001 From: satyr Date: Mon, 11 Oct 2010 09:17:31 +0900 Subject: [PATCH] destructuring assignment no longer uses a temporary variable for simple LHS --- lib/lexer.js | 28 +++++++++--------- lib/nodes.js | 74 +++++++++++++++++++++++++++++++++--------------- src/nodes.coffee | 48 ++++++++++++++++++++++++------- 3 files changed, 102 insertions(+), 48 deletions(-) diff --git a/lib/lexer.js b/lib/lexer.js index 487a423d..94e4c41b 100644 --- a/lib/lexer.js +++ b/lib/lexer.js @@ -30,11 +30,11 @@ return (new Rewriter).rewrite(this.tokens); }; Lexer.prototype.identifierToken = function() { - var _ref2, colon, forcedIdentifier, id, input, match, tag; + var colon, forcedIdentifier, id, input, match, tag; if (!(match = IDENTIFIER.exec(this.chunk))) { return false; } - _ref2 = match, input = _ref2[0], id = _ref2[1], colon = _ref2[2]; + input = match[0], id = match[1], colon = match[2]; this.i += input.length; if (id === 'all' && this.tag() === 'FOR') { this.token('ALL', id); @@ -153,11 +153,11 @@ return true; }; Lexer.prototype.commentToken = function() { - var _ref2, comment, here, match; + var comment, here, match; if (!(match = this.chunk.match(COMMENT))) { return false; } - _ref2 = match, comment = _ref2[0], here = _ref2[1]; + comment = match[0], here = match[1]; this.line += count(comment, '\n'); this.i += comment.length; if (here) { @@ -197,8 +197,8 @@ return true; }; Lexer.prototype.heregexToken = function(match) { - var _i, _len, _ref2, _ref3, _ref4, _ref5, _this, body, flags, heregex, re, tag, tokens, value; - _ref2 = match, heregex = _ref2[0], body = _ref2[1], flags = _ref2[2]; + var _i, _len, _ref2, _ref3, _ref4, _this, body, flags, heregex, re, tag, tokens, value; + heregex = match[0], body = match[1], flags = match[2]; this.i += heregex.length; if (0 > body.indexOf('#{')) { re = body.replace(HEREGEX_OMIT, '').replace(/\//g, '\\/'); @@ -208,10 +208,10 @@ this.token('IDENTIFIER', 'RegExp'); this.tokens.push(['CALL_START', '(']); tokens = []; - for (_i = 0, _len = (_ref3 = this.interpolateString(body, { + for (_i = 0, _len = (_ref2 = this.interpolateString(body, { regex: true })).length; _i < _len; _i++) { - _ref4 = _ref3[_i], tag = _ref4[0], value = _ref4[1]; + _ref3 = _ref2[_i], tag = _ref3[0], value = _ref3[1]; if (tag === 'TOKENS') { tokens.push.apply(tokens, value); } else { @@ -224,7 +224,7 @@ tokens.push(['+', '+']); } tokens.pop(); - if ((((_ref5 = tokens[0]) != null) ? _ref5[0] : undefined) !== 'STRING') { + if ((((_ref4 = tokens[0]) != null) ? _ref4[0] : undefined) !== 'STRING') { this.tokens.push(['STRING', '""'], ['+', '+']); } (_this = this.tokens).push.apply(_this, tokens); @@ -399,15 +399,15 @@ return true; }; Lexer.prototype.sanitizeHeredoc = function(doc, options) { - var _ref2, _ref3, attempt, herecomment, indent, match; - _ref2 = options, indent = _ref2.indent, herecomment = _ref2.herecomment; + var _ref2, attempt, herecomment, indent, match; + indent = options.indent, herecomment = options.herecomment; if (herecomment && 0 > doc.indexOf('\n')) { return doc; } if (!herecomment) { while (match = HEREDOC_INDENT.exec(doc)) { attempt = match[1]; - if (indent === null || (0 < (_ref3 = attempt.length)) && (_ref3 < indent.length)) { + if (indent === null || (0 < (_ref2 = attempt.length)) && (_ref2 < indent.length)) { indent = attempt; } } @@ -452,7 +452,7 @@ throw SyntaxError("Reserved word \"" + (this.value()) + "\" on line " + (this.line + 1) + " can't be assigned"); }; Lexer.prototype.balancedString = function(str, delimited, options) { - var _i, _len, _ref2, close, i, levels, open, pair, slen; + var _i, _len, close, i, levels, open, pair, slen; options || (options = {}); levels = []; i = 0; @@ -463,7 +463,7 @@ } else { for (_i = 0, _len = delimited.length; _i < _len; _i++) { pair = delimited[_i]; - _ref2 = pair, open = _ref2[0], close = _ref2[1]; + open = pair[0], close = pair[1]; if (levels.length && starts(str, close, i) && last(levels) === pair) { levels.pop(); i += close.length - 1; diff --git a/lib/nodes.js b/lib/nodes.js index 93d8d045..1877fb1b 100644 --- a/lib/nodes.js +++ b/lib/nodes.js @@ -154,6 +154,7 @@ Base.prototype.isPureStatement = NO; Base.prototype.isComplex = YES; Base.prototype.topSensitive = NO; + Base.prototype.assigns = NO; return Base; })(); exports.Expressions = (function() { @@ -266,6 +267,9 @@ Literal.prototype.isReserved = function() { return !!this.value.reserved; }; + Literal.prototype.assigns = function(name) { + return name === this.value; + }; Literal.prototype.compileNode = function(o) { var end, idt, val; idt = this.isStatement(o) ? this.idt() : ''; @@ -348,6 +352,9 @@ Value.prototype.isComplex = function() { return this.base.isComplex() || this.hasProperties(); }; + Value.prototype.assigns = function(name) { + return !this.properties.length && this.base.assigns(name); + }; Value.prototype.makeReturn = function() { return this.properties.length ? Value.__super__.makeReturn.call(this) : this.base.makeReturn(); }; @@ -817,13 +824,7 @@ for (i = 0, _len = (_ref2 = this.properties).length; i < _len; i++) { prop = _ref2[i]; _result.push((function() { - join = ",\n"; - if ((prop === lastNoncom) || (prop instanceof Comment)) { - join = "\n"; - } - if (i === this.properties.length - 1) { - join = ''; - } + join = i === this.properties.length - 1 ? '' : (prop === lastNoncom || prop instanceof Comment ? '\n' : ',\n'); indent = prop instanceof Comment ? '' : this.idt(1); if (prop instanceof Value && prop.tags["this"]) { prop = new Assign(prop.properties[0].name, prop, 'object'); @@ -836,9 +837,19 @@ return _result; }).call(this); props = props.join(''); - obj = '{' + (props ? '\n' + props + '\n' + this.idt() : '') + '}'; + obj = ("{" + (props ? '\n' + props + '\n' + this.idt() : '') + "}"); return top ? ("(" + obj + ")") : obj; }; + ObjectLiteral.prototype.assigns = function(name) { + var _i, _len, _ref2, prop; + for (_i = 0, _len = (_ref2 = this.properties).length; _i < _len; _i++) { + prop = _ref2[_i]; + if (prop.assigns(name)) { + return true; + } + } + return false; + }; return ObjectLiteral; })(); exports.ArrayLiteral = (function() { @@ -874,6 +885,16 @@ objects = objects.join(''); return 0 < objects.indexOf('\n') ? ("[\n" + (o.indent) + objects + "\n" + (this.tab) + "]") : ("[" + objects + "]"); }; + ArrayLiteral.prototype.assigns = function(name) { + var _i, _len, _ref2, obj; + for (_i = 0, _len = (_ref2 = this.objects).length; _i < _len; _i++) { + obj = _ref2[_i]; + if (obj.assigns(name)) { + return true; + } + } + return false; + }; return ArrayLiteral; })(); exports.Class = (function() { @@ -1024,7 +1045,7 @@ return top || this.parenthetical ? val : ("(" + val + ")"); }; Assign.prototype.compilePatternMatch = function(o) { - var _len, _ref2, _ref3, accessClass, assigns, code, i, idx, isObject, obj, objects, olength, otop, splat, top, val, valVar, value; + var _len, _ref2, _ref3, accessClass, assigns, code, i, idx, isObject, obj, objects, olength, otop, ref, splat, top, val, valVar, value; if ((value = this.value).isStatement(o)) { value = Closure.wrap(value); } @@ -1050,15 +1071,19 @@ otop = merge(o, { top: true }); - valVar = o.scope.freeVariable('ref'); - assigns = [("" + valVar + " = " + (value.compile(o)))]; + valVar = value.compile(o); + assigns = []; splat = false; + if (!IDENTIFIER.test(valVar) || this.variable.assigns(valVar)) { + assigns.push("" + (ref = o.scope.freeVariable('ref')) + " = " + valVar); + valVar = ref; + } for (i = 0, _len = objects.length; i < _len; i++) { obj = objects[i]; idx = i; if (isObject) { if (obj instanceof Assign) { - _ref3 = [obj.value, obj.variable.base], obj = _ref3[0], idx = _ref3[1]; + _ref3 = obj, idx = _ref3.variable.base, obj = _ref3.value; } else { idx = obj.tags["this"] ? obj.properties[0].name : obj; } @@ -1095,6 +1120,9 @@ val = this.value.compile(o); return "([].splice.apply(" + name + ", [" + from + ", " + to + "].concat(" + ref + " = " + val + ")), " + ref + ")"; }; + Assign.prototype.assigns = function(name) { + return this[this.context === 'object' ? 'value' : 'variable'].assigns(name); + }; return Assign; })(); exports.Code = (function() { @@ -1221,16 +1249,16 @@ Splat = (function() { function Splat(name) { Splat.__super__.constructor.call(this); - if (!name.compile) { - name = new Literal(name); - } - this.name = name; + this.name = name.compile ? name : new Literal(name); return this; }; return Splat; })(); __extends(Splat, Base); Splat.prototype.children = ['name']; + Splat.prototype.assigns = function(name) { + return this.name.assigns(name); + }; Splat.prototype.compileNode = function(o) { return (this.index != null) ? this.compileParam(o) : this.name.compile(o); }; @@ -1611,16 +1639,16 @@ exports.For = (function() { For = (function() { function For(_arg, source, _arg2, _arg3) { - var _ref2, _ref3; + var _ref2; this.index = _arg3; this.name = _arg2; this.body = _arg; For.__super__.constructor.call(this); - _ref2 = source, this.source = _ref2.source, this.guard = _ref2.guard, this.step = _ref2.step; + this.source = source.source, this.guard = source.guard, this.step = source.step; this.raw = !!source.raw; this.object = !!source.object; if (this.object) { - _ref3 = [this.index, this.name], this.name = _ref3[0], this.index = _ref3[1]; + _ref2 = [this.index, this.name], this.name = _ref2[0], this.index = _ref2[1]; } this.pattern = this.name instanceof Value; if (this.index instanceof Value) { @@ -1772,16 +1800,16 @@ return this; }; Switch.prototype.compileNode = function(o) { - var _i, _j, _len, _len2, _ref2, _ref3, _ref4, block, code, condition, conditions, exprs, idt, pair; + var _i, _j, _len, _len2, _ref2, _ref3, block, code, condition, conditions, exprs, idt, pair; idt = (o.indent = this.idt(2)); o.top = true; code = ("" + (this.tab) + "switch (" + (this.subject.compile(o)) + ") {"); for (_i = 0, _len = (_ref2 = this.cases).length; _i < _len; _i++) { pair = _ref2[_i]; - _ref3 = pair, conditions = _ref3[0], block = _ref3[1]; + conditions = pair[0], block = pair[1]; exprs = block.expressions; - for (_j = 0, _len2 = (_ref4 = flatten([conditions])).length; _j < _len2; _j++) { - condition = _ref4[_j]; + for (_j = 0, _len2 = (_ref3 = flatten([conditions])).length; _j < _len2; _j++) { + condition = _ref3[_j]; if (this.tags.subjectless) { condition = new Op('!!', new Parens(condition)); } diff --git a/src/nodes.coffee b/src/nodes.coffee index eb4b3dfd..a695122b 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -143,6 +143,9 @@ exports.Base = class Base isComplex : YES topSensitive : NO + # Is this node used to assign a certain variable? + assigns: NO + #### Expressions # The expressions body is the list of expressions that forms the body of an @@ -253,6 +256,8 @@ exports.Literal = class Literal extends Base isReserved: -> !!@value.reserved + assigns: (name) -> name is @value + compileNode: (o) -> idt = if @isStatement(o) then @idt() else '' end = if @isStatement(o) then ';' else '' @@ -324,6 +329,9 @@ exports.Value = class Value extends Base isComplex: -> @base.isComplex() or @hasProperties() + assigns: (name) -> + not @properties.length and @base.assigns name + makeReturn: -> if @properties.length then super() else @base.makeReturn() @@ -702,12 +710,15 @@ exports.ObjectLiteral = class ObjectLiteral extends Base compileNode: (o) -> top = del o, 'top' o.indent = @idt 1 - nonComments = prop for prop in @properties when (prop not instanceof Comment) - lastNoncom = last nonComments + nonComments = prop for prop in @properties when prop not instanceof Comment + lastNoncom = last nonComments props = for prop, i in @properties - join = ",\n" - join = "\n" if (prop is lastNoncom) or (prop instanceof Comment) - join = '' if i is @properties.length - 1 + join = if i is @properties.length - 1 + '' + else if prop is lastNoncom or prop instanceof Comment + '\n' + else + ',\n' indent = if prop instanceof Comment then '' else @idt 1 if prop instanceof Value and prop.tags.this prop = new Assign prop.properties[0].name, prop, 'object' @@ -715,9 +726,13 @@ exports.ObjectLiteral = class ObjectLiteral extends Base prop = new Assign prop, prop, 'object' indent + prop.compile(o) + join props = props.join('') - obj = '{' + (if props then '\n' + props + '\n' + @idt() else '') + '}' + obj = "{#{ if props then '\n' + props + '\n' + @idt() else '' }}" if top then "(#{obj})" else obj + assigns: (name) -> + for prop in @properties when prop.assigns name then return yes + no + #### ArrayLiteral # An array literal. @@ -752,6 +767,10 @@ exports.ArrayLiteral = class ArrayLiteral extends Base else "[#{objects}]" + assigns: (name) -> + for obj in @objects when obj.assigns name then return yes + no + #### Class # The CoffeeScript class definition. @@ -898,16 +917,19 @@ exports.Assign = class Assign extends Base return new Assign(obj, value).compile o top = del o, 'top' otop = merge o, top: yes - valVar = o.scope.freeVariable 'ref' - assigns = ["#{valVar} = #{ value.compile o }"] + valVar = value.compile o + assigns = [] splat = false + if not IDENTIFIER.test(valVar) or @variable.assigns(valVar) + assigns.push "#{ ref = o.scope.freeVariable 'ref' } = #{valVar}" + valVar = ref for obj, i in objects # A regular array pattern-match. idx = i if isObject if obj instanceof Assign # A regular object pattern-match. - [obj, idx] = [obj.value, obj.variable.base] + {variable: {base: idx}, value: obj} = obj else # A shorthand `{a, b, @c} = val` pattern-match. idx = if obj.tags.this then obj.properties[0].name else obj @@ -937,6 +959,9 @@ exports.Assign = class Assign extends Base val = @value.compile(o) "([].splice.apply(#{name}, [#{from}, #{to}].concat(#{ref} = #{val})), #{ref})" + assigns: (name) -> + @[if @context is 'object' then 'value' else 'variable'].assigns name + #### Code # A function definition. This is the only node that creates a new Scope. @@ -1039,8 +1064,9 @@ exports.Splat = class Splat extends Base constructor: (name) -> super() - name = new Literal name unless name.compile - @name = name + @name = if name.compile then name else new Literal name + + assigns: (name) -> @name.assigns name compileNode: (o) -> if @index? then @compileParam(o) else @name.compile(o)