fixing issue 196, better handling of soak/existence chains

This commit is contained in:
Jeremy Ashkenas 2010-02-22 22:11:47 -05:00
parent 1f870911c9
commit 3df7bd98f4
3 changed files with 40 additions and 35 deletions

View File

@ -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];

View File

@ -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.

View File

@ -68,3 +68,8 @@ result: value?.toString().toLowerCase()
ok result is '10'
# Safely existence test on soaks.
result: not value?.property?
ok result