Added safe soaking on non-existent variables.

This commit is contained in:
Tim Jones
2010-05-05 21:58:48 +12:00
parent d0d0fa4d10
commit 0b3bb66708
3 changed files with 40 additions and 17 deletions

View File

@@ -425,34 +425,46 @@
ValueNode.prototype.is_statement = function is_statement() { ValueNode.prototype.is_statement = function is_statement() {
return this.base.is_statement && this.base.is_statement() && !this.has_properties(); return this.base.is_statement && this.base.is_statement() && !this.has_properties();
}; };
// Works out if the value is the start of a chain.
ValueNode.prototype.is_start = function is_start(o) {
var node;
if (this === o.chain_root && this.properties[0] instanceof AccessorNode) {
return true;
}
node = o.chain_root.base || o.chain_root.variable;
while (node instanceof CallNode) {
node = node.variable;
}
return node === this;
};
// We compile a value to JavaScript by compiling and joining each property. // We compile a value to JavaScript by compiling and joining each property.
// Things get much more insteresting if the chain of properties has *soak* // Things get much more insteresting if the chain of properties has *soak*
// operators `?.` interspersed. Then we have to take care not to accidentally // operators `?.` interspersed. Then we have to take care not to accidentally
// evaluate a anything twice when building the soak chain. // evaluate a anything twice when building the soak chain.
ValueNode.prototype.compile_node = function compile_node(o) { ValueNode.prototype.compile_node = function compile_node(o) {
var _b, _c, _d, baseline, complete, only, op, part, prop, props, temp; var _b, _c, baseline, complete, i, only, op, part, prop, props, temp;
only = del(o, 'only_first'); only = del(o, 'only_first');
op = del(o, 'operation'); op = del(o, 'operation');
props = only ? this.properties.slice(0, this.properties.length - 1) : this.properties; props = only ? this.properties.slice(0, this.properties.length - 1) : this.properties;
if (!(o.chain_root)) { o.chain_root = o.chain_root || this;
o.chain_root = this;
}
baseline = this.base.compile(o); baseline = this.base.compile(o);
if (this.base instanceof ObjectNode && this.has_properties()) { if (this.base instanceof ObjectNode && this.has_properties()) {
baseline = ("(" + baseline + ")"); baseline = ("(" + baseline + ")");
} }
complete = (this.last = baseline); complete = (this.last = baseline);
_c = props; _b = props;
for (_b = 0, _d = _c.length; _b < _d; _b++) { for (i = 0, _c = _b.length; i < _c; i++) {
prop = _c[_b]; prop = _b[i];
this.source = baseline; this.source = baseline;
if (prop.soak_node) { if (prop.soak_node) {
if (this.base instanceof CallNode && prop === props[0]) { if (this.base instanceof CallNode && i === 0) {
temp = o.scope.free_variable(); temp = o.scope.free_variable();
complete = ("(" + temp + " = " + complete + ")" + this.SOAK) + (baseline = temp + prop.compile(o)); complete = ("(" + (baseline = temp) + " = (" + complete + "))");
} else {
complete = complete + this.SOAK + (baseline += prop.compile(o));
} }
if (i === 0 && this.is_start(o)) {
complete = ("typeof " + complete + " === \"undefined\" || " + baseline);
}
complete += this.SOAK + (baseline += prop.compile(o));
} else { } else {
part = prop.compile(o); part = prop.compile(o);
baseline += part; baseline += part;

View File

@@ -298,6 +298,13 @@ exports.ValueNode: class ValueNode extends BaseNode
# Values are considered to be statements if their base is a statement. # Values are considered to be statements if their base is a statement.
is_statement: -> is_statement: ->
@base.is_statement and @base.is_statement() and not @has_properties() @base.is_statement and @base.is_statement() and not @has_properties()
# Works out if the value is the start of a chain.
is_start: (o) ->
return true if this is o.chain_root and @properties[0] instanceof AccessorNode
node: o.chain_root.base or o.chain_root.variable
while node instanceof CallNode then node: node.variable
node is this
# We compile a value to JavaScript by compiling and joining each property. # We compile a value to JavaScript by compiling and joining each property.
# Things get much more insteresting if the chain of properties has *soak* # Things get much more insteresting if the chain of properties has *soak*
@@ -307,19 +314,19 @@ exports.ValueNode: class ValueNode extends BaseNode
only: del(o, 'only_first') only: del(o, 'only_first')
op: del(o, 'operation') op: del(o, 'operation')
props: if only then @properties[0...@properties.length - 1] else @properties props: if only then @properties[0...@properties.length - 1] else @properties
o.chain_root: this unless o.chain_root o.chain_root: or this
baseline: @base.compile o baseline: @base.compile o
baseline: "($baseline)" if @base instanceof ObjectNode and @has_properties() baseline: "($baseline)" if @base instanceof ObjectNode and @has_properties()
complete: @last: baseline complete: @last: baseline
for prop in props for prop, i in props
@source: baseline @source: baseline
if prop.soak_node if prop.soak_node
if @base instanceof CallNode and prop is props[0] if @base instanceof CallNode and i is 0
temp: o.scope.free_variable() temp: o.scope.free_variable()
complete: "($temp = $complete)$@SOAK" + (baseline: temp + prop.compile(o)) complete: "(${ baseline: temp } = ($complete))"
else complete: "typeof $complete === \"undefined\" || $baseline" if i is 0 and @is_start(o)
complete: complete + @SOAK + (baseline: + prop.compile(o)) complete: + @SOAK + (baseline: + prop.compile(o))
else else
part: prop.compile(o) part: prop.compile(o)
baseline: + part baseline: + part

View File

@@ -74,3 +74,7 @@ ok result is '10'
result: not value?.property? result: not value?.property?
ok result ok result
# Safely calls values off of non-existent variables.
result: nothing?.value
ok result is undefined