diff --git a/lib/nodes.js b/lib/nodes.js index fa5f8fd1..851c59b8 100644 --- a/lib/nodes.js +++ b/lib/nodes.js @@ -115,6 +115,14 @@ o.shared_scope = o.scope; return ClosureNode.wrap(this).compile(o); }; + // If the code generation wishes to use the result of a complex expression + // in multiple places, ensure that the expression is only ever evaluated once. + Node.prototype.compile_reference = function compile_reference(o) { + var compiled, reference; + reference = new LiteralNode(o.scope.free_variable()); + compiled = new AssignNode(reference, this); + return [compiled, reference]; + }; // Quick short method for the current indentation level, plus tabbing in. Node.prototype.idt = function idt(tabs) { var _a, _b, _c, _d, i, idt; @@ -357,7 +365,7 @@ return this.base.is_statement && this.base.is_statement() && !this.has_properties(); }, compile_node: function compile_node(o) { - var _a, _b, baseline, code, only, op, part, parts, prop, props, soaked, temp; + var _a, _b, baseline, complete, only, op, part, prop, props, soaked, temp; soaked = false; only = del(o, 'only_first'); op = del(o, 'operation'); @@ -366,28 +374,27 @@ if (this.base instanceof ObjectNode && this.has_properties()) { baseline = '(' + baseline + ')'; } - parts = [baseline]; + complete = (this.last = baseline); _a = props; for (_b = 0; _b < _a.length; _b++) { prop = _a[_b]; + this.source = baseline; if (prop instanceof AccessorNode && prop.soak) { soaked = true; if (this.base instanceof CallNode && prop === props[0]) { temp = o.scope.free_variable(); - parts[parts.length - 1] = '(' + temp + ' = ' + baseline + ')' + this.SOAK + ((baseline = temp + prop.compile(o))); + complete = '(' + temp + ' = ' + complete + ')' + this.SOAK + ((baseline = temp + prop.compile(o))); } else { - parts[parts.length - 1] += (this.SOAK + (baseline += prop.compile(o))); + complete = complete + this.SOAK + (baseline += prop.compile(o)); } } else { part = prop.compile(o); baseline += part; - parts.push(part); + complete += part; + this.last = part; } } - this.last = parts[parts.length - 1]; - this.source = parts.length > 1 ? parts.slice(0, (parts.length - 1)).join('') : null; - code = parts.join('').replace(/\)\(\)\)/, '()))'); - return op && soaked ? '(' + code + ')' : code; + return op && soaked ? '(' + complete + ')' : complete; } })); // Pass through CoffeeScript comments into JavaScript comments at the @@ -466,14 +473,6 @@ return _a; }).call(this); return this.prefix + meth + '.apply(' + obj + ', ' + args.join('') + ')'; - }, - // If the code generation wished to use the result of a function call - // in multiple places, ensure that the function is only ever called once. - compile_reference: function compile_reference(o) { - var call, reference; - reference = new LiteralNode(o.scope.free_variable()); - call = new ParentheticalNode(new AssignNode(reference, this)); - return [call, reference]; } })); // Node to extend an object's prototype with an ancestor object. @@ -875,7 +874,8 @@ return this; }, compile_node: function compile_node(o) { - return (typeof this.index !== "undefined" && this.index !== null) ? this.compile_param(o) : this.name.compile(o); + var _a; + return (typeof (_a = this.index) !== "undefined" && _a !== null) ? this.compile_param(o) : this.name.compile(o); }, compile_param: function compile_param(o) { var name; @@ -1056,7 +1056,7 @@ _a = [variable, variable]; first = _a[0]; second = _a[1]; - if (variable instanceof CallNode) { + if (variable instanceof CallNode || (variable instanceof ValueNode && variable.has_properties())) { _b = variable.compile_reference(o); first = _b[0]; second = _b[1]; diff --git a/src/nodes.coffee b/src/nodes.coffee index 1d0769ed..021cc93e 100644 --- a/src/nodes.coffee +++ b/src/nodes.coffee @@ -77,6 +77,13 @@ Node::compile_closure: (o) -> o.shared_scope: o.scope ClosureNode.wrap(this).compile(o) +# If the code generation wishes to use the result of a complex expression +# in multiple places, ensure that the expression is only ever evaluated once. +Node::compile_reference: (o) -> + reference: new LiteralNode(o.scope.free_variable()) + compiled: new AssignNode(reference, this) + [compiled, reference] + # Quick short method for the current indentation level, plus tabbing in. Node::idt: (tabs) -> idt: (@indent || '') @@ -272,25 +279,24 @@ ValueNode: exports.ValueNode: inherit Node, { props: if only then @properties[0...@properties.length - 1] else @properties baseline: @base.compile o baseline: '(' + baseline + ')' if @base instanceof ObjectNode and @has_properties() - parts: [baseline] + complete: @last: baseline for prop in props + @source: baseline if prop instanceof AccessorNode and prop.soak soaked: true if @base instanceof CallNode and prop is props[0] temp: o.scope.free_variable() - parts[parts.length - 1]: '(' + temp + ' = ' + baseline + ')' + @SOAK + (baseline: temp + prop.compile(o)) + complete: '(' + temp + ' = ' + complete + ')' + @SOAK + (baseline: temp + prop.compile(o)) else - parts[parts.length - 1] += (@SOAK + (baseline += prop.compile(o))) + complete: complete + @SOAK + (baseline += prop.compile(o)) else part: prop.compile(o) baseline += part - parts.push(part) + complete += part + @last: part - @last: parts[parts.length - 1] - @source: if parts.length > 1 then parts[0...(parts.length - 1)].join('') else null - code: parts.join('').replace(/\)\(\)\)/, '()))') - if op and soaked then '(' + code + ')' else code + if op and soaked then '(' + complete + ')' else complete } @@ -356,13 +362,6 @@ CallNode: exports.CallNode: inherit Node, { if i is 0 then code else '.concat(' + code + ')' @prefix + meth + '.apply(' + obj + ', ' + args.join('') + ')' - # If the code generation wished to use the result of a function call - # in multiple places, ensure that the function is only ever called once. - compile_reference: (o) -> - reference: new LiteralNode(o.scope.free_variable()) - call: new ParentheticalNode(new AssignNode(reference, this)) - [call, reference] - } # Node to extend an object's prototype with an ancestor object. @@ -845,7 +844,8 @@ ExistenceNode: exports.ExistenceNode: inherit Node, { ExistenceNode.compile_test: (o, variable) -> [first, second]: [variable, variable] - [first, second]: variable.compile_reference(o) if variable instanceof CallNode + if variable instanceof CallNode or (variable instanceof ValueNode and variable.has_properties()) + [first, second]: variable.compile_reference(o) '(typeof ' + first.compile(o) + ' !== "undefined" && ' + second.compile(o) + ' !== null)' # An extra set of parentheses, specified explicitly in the source. diff --git a/test/test_existence.coffee b/test/test_existence.coffee index 502e8f6f..bcbca299 100644 --- a/test/test_existence.coffee +++ b/test/test_existence.coffee @@ -68,3 +68,8 @@ result: value?.toString().toLowerCase() ok result is '10' + +# Safely existence test on soaks. +result: not value?.property? +ok result +