mirror of
https://github.com/jashkenas/coffeescript.git
synced 2026-02-17 02:51:25 -05:00
adding existence soaks for indexed-lookup property accesses: obj?['property']
This commit is contained in:
@@ -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.
|
||||||
|
|||||||
16
lib/lexer.js
16
lib/lexer.js
@@ -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';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
134
lib/parser.js
134
lib/parser.js
File diff suppressed because one or more lines are too long
@@ -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;
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) ->
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user