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

View File

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

View File

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

View File

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

View File

@@ -117,4 +117,11 @@ class Hive.Bee extends Hive
constructor: (name) -> super name constructor: (name) -> super name
maya: new Hive.Bee 'Maya' 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[0][0] is 'a'
ok result[1]['b'] is 'c' 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'