first draft at ticket #437 ... automatic quoting of reserved words and keywords.

This commit is contained in:
Jeremy Ashkenas 2010-06-15 00:54:02 -04:00
parent d0948e5586
commit 4b284f6687
6 changed files with 72 additions and 25 deletions

View File

@ -128,22 +128,32 @@
// referenced as property names here, so you can still do `jQuery.is()` even
// though `is` means `===` otherwise.
Lexer.prototype.identifierToken = function() {
var forcedIdentifier, id, tag;
var close_index, forcedIdentifier, id, tag;
if (!(id = this.match(IDENTIFIER, 1))) {
return false;
}
this.i += id.length;
forcedIdentifier = this.tagAccessor() || this.match(ASSIGNED, 1);
tag = 'IDENTIFIER';
if (include(JS_KEYWORDS, id) || (!forcedIdentifier && include(COFFEE_KEYWORDS, id))) {
tag = id.toUpperCase();
}
if (include(RESERVED, id)) {
this.identifierError(id);
}
if (tag === 'WHEN' && include(LINE_BREAK, this.tag())) {
tag = 'LEADING_WHEN';
}
this.i += id.length;
if (include(JS_FORBIDDEN, id)) {
if (forcedIdentifier) {
tag = 'STRING';
id = ("'" + id + "'");
if (forcedIdentifier === 'accessor') {
close_index = true;
this.tokens.pop();
this.token('INDEX_START', '[');
}
} else if (include(RESERVED, id)) {
this.identifierError(id);
}
}
if (!(forcedIdentifier)) {
if (include(COFFEE_ALIASES, id)) {
tag = (id = CONVERSIONS[id]);
@ -153,6 +163,9 @@
}
}
this.token(tag, id);
if (close_index) {
this.token(']', ']');
}
return true;
};
// Matches numbers, including decimals, hex, and exponential notation.
@ -419,21 +432,28 @@
// if it's a special kind of accessor. Return `true` if any type of accessor
// is the previous token.
Lexer.prototype.tagAccessor = function() {
var prev;
var accessor, prev;
if ((!(prev = this.prev())) || (prev && prev.spaced)) {
return false;
}
if (prev[1] === '::') {
return this.tag(1, 'PROTOTYPE_ACCESS');
} else if (prev[1] === '.' && !(this.value(2) === '.')) {
if (this.tag(2) === '?') {
this.tag(1, 'SOAK_ACCESS');
return this.tokens.splice(-2, 1);
accessor = (function() {
if (prev[1] === '::') {
return this.tag(1, 'PROTOTYPE_ACCESS');
} else if (prev[1] === '.' && !(this.value(2) === '.')) {
if (this.tag(2) === '?') {
this.tag(1, 'SOAK_ACCESS');
return this.tokens.splice(-2, 1);
} else {
return this.tag(1, 'PROPERTY_ACCESS');
}
} else {
return this.tag(1, 'PROPERTY_ACCESS');
return prev[0] === '@';
}
}).call(this);
if (accessor) {
return 'accessor';
} else {
return prev[0] === '@';
return false;
}
};
// Sanitize a heredoc or herecomment by escaping internal double quotes and

View File

@ -651,7 +651,7 @@
exports.AccessorNode = (function() {
AccessorNode = function(name, tag) {
this.name = name;
this.prototype = tag === 'prototype';
this.prototype = tag === 'prototype' ? '.prototype' : '';
this.soakNode = tag === 'soak';
return this;
};
@ -659,10 +659,11 @@
AccessorNode.prototype.type = 'AccessorNode';
AccessorNode.prototype.children = ['name'];
AccessorNode.prototype.compileNode = function(o) {
var protoPart;
var name, namePart;
name = this.name.compile(o);
o.chainRoot.wrapped = o.chainRoot.wrapped || this.soakNode;
protoPart = this.prototype ? 'prototype.' : '';
return "." + protoPart + (this.name.compile(o));
namePart = name.match(IS_STRING) ? ("[" + name + "]") : ("." + name);
return this.prototype + namePart;
};
return AccessorNode;
})();

View File

@ -90,16 +90,26 @@ exports.Lexer: class Lexer
# though `is` means `===` otherwise.
identifierToken: ->
return false unless id: @match IDENTIFIER, 1
@i: + id.length
forcedIdentifier: @tagAccessor() or @match ASSIGNED, 1
tag: 'IDENTIFIER'
tag: id.toUpperCase() if include(JS_KEYWORDS, id) or (not forcedIdentifier and include(COFFEE_KEYWORDS, id))
@identifierError id if include RESERVED, id
tag: 'LEADING_WHEN' if tag is 'WHEN' and include LINE_BREAK, @tag()
@i: + id.length
if include(JS_FORBIDDEN, id)
if forcedIdentifier
tag: 'STRING'
id: "'$id'"
if forcedIdentifier is 'accessor'
close_index: true
@tokens.pop()
@token 'INDEX_START', '['
else if include(RESERVED, id)
@identifierError id
unless forcedIdentifier
tag: id: CONVERSIONS[id] if include COFFEE_ALIASES, id
return @tagHalfAssignment tag if @prev() and @prev()[0] is 'ASSIGN' and include HALF_ASSIGNMENTS, tag
@token tag, id
@token ']', ']' if close_index
true
# Matches numbers, including decimals, hex, and exponential notation.
@ -289,7 +299,7 @@ exports.Lexer: class Lexer
# is the previous token.
tagAccessor: ->
return false if (not prev: @prev()) or (prev and prev.spaced)
if prev[1] is '::'
accessor: if prev[1] is '::'
@tag 1, 'PROTOTYPE_ACCESS'
else if prev[1] is '.' and not (@value(2) is '.')
if @tag(2) is '?'
@ -299,6 +309,7 @@ exports.Lexer: class Lexer
@tag 1, 'PROPERTY_ACCESS'
else
prev[0] is '@'
if accessor then 'accessor' else false
# Sanitize a heredoc or herecomment by escaping internal double quotes and
# erasing all external indentation on the left-hand side.

View File

@ -474,13 +474,14 @@ exports.AccessorNode: class AccessorNode extends BaseNode
constructor: (name, tag) ->
@name: name
@prototype: tag is 'prototype'
@prototype: if tag is 'prototype' then '.prototype' else ''
@soakNode: tag is 'soak'
compileNode: (o) ->
name: @name.compile o
o.chainRoot.wrapped: or @soakNode
protoPart: if @prototype then 'prototype.' else ''
".$protoPart${@name.compile(o)}"
namePart: if name.match(IS_STRING) then "[$name]" else ".$name"
@prototype + namePart
#### IndexNode

View File

@ -117,4 +117,11 @@ class Hive.Bee extends Hive
constructor: (name) -> super name
maya: new Hive.Bee 'Maya'
ok maya.name is 'Maya'
ok maya.name is 'Maya'
# Class with JS-keyword properties.
class Class
class: 'class'
ok (new Class()).class is 'class'

View File

@ -107,3 +107,10 @@ result: [['a']
ok result[0][0] is 'a'
ok result[1]['b'] is 'c'
# Object literals should be able to include keywords.
obj: {class: 'hot'}
obj.function: 'dog'
ok obj.class + obj.function is 'hotdog'