adding existence soaks for indexed-lookup property accesses: obj?['property']

This commit is contained in:
Jeremy Ashkenas
2010-02-24 00:06:01 -05:00
parent 4eeb8c4bd2
commit 10d335ccb1
10 changed files with 111 additions and 83 deletions

View File

@@ -285,6 +285,8 @@
// Indexing into an object or array. // Indexing into an object or array.
Index: [o("INDEX_START Expression INDEX_END", function() { Index: [o("INDEX_START Expression INDEX_END", function() {
return new IndexNode($2); return new IndexNode($2);
}), o("SOAKED_INDEX_START Expression SOAKED_INDEX_END", function() {
return new IndexNode($2, 'soak');
}) })
], ],
// An object literal. // An object literal.

View File

@@ -285,24 +285,30 @@
// Multi-character operators are also literal tokens, so that Racc can assign // Multi-character operators are also literal tokens, so that Racc can assign
// the proper order of operations. // the proper order of operations.
lex.prototype.literal_token = function literal_token() { lex.prototype.literal_token = function literal_token() {
var match, tag, value; var match, not_spaced, tag, value;
match = this.chunk.match(OPERATOR); match = this.chunk.match(OPERATOR);
value = match && match[1]; value = match && match[1];
if (value && value.match(CODE)) { if (value && value.match(CODE)) {
this.tag_parameters(); this.tag_parameters();
} }
value = value || this.chunk.substr(0, 1); value = value || this.chunk.substr(0, 1);
not_spaced = !this.prev() || !this.prev().spaced;
tag = value; tag = value;
if (value.match(ASSIGNMENT)) { if (value.match(ASSIGNMENT)) {
tag = 'ASSIGN'; tag = 'ASSIGN';
if (JS_FORBIDDEN.indexOf(this.value()) >= 0) { if (JS_FORBIDDEN.indexOf(this.value()) >= 0) {
throw new Error('SyntaxError: Reserved word "' + this.value() + '" on line ' + this.line + ' can\'t be assigned'); throw new Error('SyntaxError: Reserved word "' + this.value() + '" on line ' + this.line + ' can\'t be assigned');
} }
} } else if (value === ';') {
if (value === ';') {
tag = 'TERMINATOR'; tag = 'TERMINATOR';
} } else if (value === '[' && this.tag() === '?' && not_spaced) {
if (CALLABLE.indexOf(this.tag()) >= 0 && (!this.prev() || !this.prev().spaced)) { tag = 'SOAKED_INDEX_START';
this.soaked_index = true;
this.tokens.pop();
} else if (value === ']' && this.soaked_index) {
tag = 'SOAKED_INDEX_END';
this.soaked_index = false;
} else if (CALLABLE.indexOf(this.tag()) >= 0 && not_spaced) {
if (value === '(') { if (value === '(') {
tag = 'CALL_START'; tag = 'CALL_START';
} }

View File

@@ -379,7 +379,7 @@
for (_b = 0; _b < _a.length; _b++) { for (_b = 0; _b < _a.length; _b++) {
prop = _a[_b]; prop = _a[_b];
this.source = baseline; this.source = baseline;
if (prop instanceof AccessorNode && prop.soak) { if (prop.soak_node) {
soaked = true; soaked = true;
if (this.base instanceof CallNode && prop === props[0]) { if (this.base instanceof CallNode && prop === props[0]) {
temp = o.scope.free_variable(); temp = o.scope.free_variable();
@@ -511,7 +511,7 @@
constructor: function constructor(name, tag) { constructor: function constructor(name, tag) {
this.children = [(this.name = name)]; this.children = [(this.name = name)];
this.prototype = tag === 'prototype'; this.prototype = tag === 'prototype';
this.soak = tag === 'soak'; this.soak_node = tag === 'soak';
return this; return this;
}, },
compile_node: function compile_node(o) { compile_node: function compile_node(o) {
@@ -521,8 +521,9 @@
// An indexed accessor into a part of an array or object. // An indexed accessor into a part of an array or object.
IndexNode = (exports.IndexNode = inherit(Node, { IndexNode = (exports.IndexNode = inherit(Node, {
type: 'Index', type: 'Index',
constructor: function constructor(index) { constructor: function constructor(index, tag) {
this.children = [(this.index = index)]; this.children = [(this.index = index)];
this.soak_node = tag === 'soak';
return this; return this;
}, },
compile_node: function compile_node(o) { compile_node: function compile_node(o) {

File diff suppressed because one or more lines are too long

View File

@@ -9,7 +9,7 @@
// indentation, and single-line flavors of expressions. // indentation, and single-line flavors of expressions.
exports.Rewriter = (re = function re() { }); exports.Rewriter = (re = function re() { });
// Tokens that must be balanced. // Tokens that must be balanced.
BALANCED_PAIRS = [['(', ')'], ['[', ']'], ['{', '}'], ['INDENT', 'OUTDENT'], ['PARAM_START', 'PARAM_END'], ['CALL_START', 'CALL_END'], ['INDEX_START', 'INDEX_END']]; BALANCED_PAIRS = [['(', ')'], ['[', ']'], ['{', '}'], ['INDENT', 'OUTDENT'], ['PARAM_START', 'PARAM_END'], ['CALL_START', 'CALL_END'], ['INDEX_START', 'INDEX_END'], ['SOAKED_INDEX_START', 'SOAKED_INDEX_END']];
// Tokens that signal the start of a balanced pair. // Tokens that signal the start of a balanced pair.
EXPRESSION_START = (function() { EXPRESSION_START = (function() {
_a = []; _b = BALANCED_PAIRS; _a = []; _b = BALANCED_PAIRS;

View File

@@ -251,6 +251,7 @@ grammar: {
# Indexing into an object or array. # Indexing into an object or array.
Index: [ Index: [
o "INDEX_START Expression INDEX_END", -> new IndexNode($2) o "INDEX_START Expression INDEX_END", -> new IndexNode($2)
o "SOAKED_INDEX_START Expression SOAKED_INDEX_END", -> new IndexNode($2, 'soak')
] ]
# An object literal. # An object literal.

View File

@@ -246,13 +246,21 @@ lex::literal_token: ->
value: match and match[1] value: match and match[1]
@tag_parameters() if value and value.match(CODE) @tag_parameters() if value and value.match(CODE)
value ||= @chunk.substr(0, 1) value ||= @chunk.substr(0, 1)
not_spaced: not @prev() or not @prev().spaced
tag: value tag: value
if value.match(ASSIGNMENT) if value.match(ASSIGNMENT)
tag: 'ASSIGN' tag: 'ASSIGN'
throw new Error('SyntaxError: Reserved word "' + @value() + '" on line ' + @line + ' can\'t be assigned') if JS_FORBIDDEN.indexOf(@value()) >= 0 throw new Error('SyntaxError: Reserved word "' + @value() + '" on line ' + @line + ' can\'t be assigned') if JS_FORBIDDEN.indexOf(@value()) >= 0
tag: 'TERMINATOR' if value == ';' else if value is ';'
tag: 'TERMINATOR'
if CALLABLE.indexOf(@tag()) >= 0 and (not @prev() or not @prev().spaced) else if value is '[' and @tag() is '?' and not_spaced
tag: 'SOAKED_INDEX_START'
@soaked_index: true
@tokens.pop()
else if value is ']' and @soaked_index
tag: 'SOAKED_INDEX_END'
@soaked_index: false
else if CALLABLE.indexOf(@tag()) >= 0 and not_spaced
tag: 'CALL_START' if value is '(' tag: 'CALL_START' if value is '('
tag: 'INDEX_START' if value is '[' tag: 'INDEX_START' if value is '['
@token tag, value @token tag, value

View File

@@ -283,7 +283,7 @@ ValueNode: exports.ValueNode: inherit Node, {
for prop in props for prop in props
@source: baseline @source: baseline
if prop instanceof AccessorNode and prop.soak if prop.soak_node
soaked: true soaked: true
if @base instanceof CallNode and prop is props[0] if @base instanceof CallNode and prop is props[0]
temp: o.scope.free_variable() temp: o.scope.free_variable()
@@ -405,7 +405,7 @@ AccessorNode: exports.AccessorNode: inherit Node, {
constructor: (name, tag) -> constructor: (name, tag) ->
@children: [@name: name] @children: [@name: name]
@prototype: tag is 'prototype' @prototype: tag is 'prototype'
@soak: tag is 'soak' @soak_node: tag is 'soak'
this this
compile_node: (o) -> compile_node: (o) ->
@@ -417,8 +417,9 @@ AccessorNode: exports.AccessorNode: inherit Node, {
IndexNode: exports.IndexNode: inherit Node, { IndexNode: exports.IndexNode: inherit Node, {
type: 'Index' type: 'Index'
constructor: (index) -> constructor: (index, tag) ->
@children: [@index: index] @children: [@index: index]
@soak_node: tag is 'soak'
this this
compile_node: (o) -> compile_node: (o) ->

View File

@@ -7,7 +7,8 @@ exports.Rewriter: re: ->
# Tokens that must be balanced. # Tokens that must be balanced.
BALANCED_PAIRS: [['(', ')'], ['[', ']'], ['{', '}'], ['INDENT', 'OUTDENT'], BALANCED_PAIRS: [['(', ')'], ['[', ']'], ['{', '}'], ['INDENT', 'OUTDENT'],
['PARAM_START', 'PARAM_END'], ['CALL_START', 'CALL_END'], ['INDEX_START', 'INDEX_END']] ['PARAM_START', 'PARAM_END'], ['CALL_START', 'CALL_END'],
['INDEX_START', 'INDEX_END'], ['SOAKED_INDEX_START', 'SOAKED_INDEX_END']]
# Tokens that signal the start of a balanced pair. # Tokens that signal the start of a balanced pair.
EXPRESSION_START: pair[0] for pair in BALANCED_PAIRS EXPRESSION_START: pair[0] for pair in BALANCED_PAIRS

View File

@@ -41,10 +41,16 @@ obj: {
ok obj?.prop is "hello" ok obj?.prop is "hello"
ok obj?['prop'] is "hello"
ok obj.prop?.length is 5 ok obj.prop?.length is 5
ok obj?['prop']?['length'] is 5
ok obj?.prop?.non?.existent?.property is undefined ok obj?.prop?.non?.existent?.property is undefined
ok obj?['non']?['existent'].property is undefined
# Soaks and caches method calls as well. # Soaks and caches method calls as well.