mirror of
https://github.com/jashkenas/coffeescript.git
synced 2022-11-09 12:23:24 -05:00
Allowing operators as object keys
This commit is contained in:
parent
2cccd621ea
commit
c200b95f6a
4 changed files with 156 additions and 27 deletions
16
lib/lexer.js
16
lib/lexer.js
|
@ -1,5 +1,5 @@
|
||||||
(function(){
|
(function(){
|
||||||
var ACCESSORS, ASSIGNMENT, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_KEYWORDS, COMMENT, COMMENT_CLEANER, CONVERSIONS, HALF_ASSIGNMENTS, HEREDOC, HEREDOC_INDENT, IDENTIFIER, INTERPOLATION, JS_CLEANER, JS_FORBIDDEN, JS_KEYWORDS, KEYWORDS, LAST_DENT, LAST_DENTS, LINE_BREAK, Lexer, MULTILINER, MULTI_DENT, NOT_REGEX, NO_NEWLINE, NUMBER, OPERATOR, REGEX_END, REGEX_ESCAPE, REGEX_INTERPOLATION, REGEX_START, RESERVED, Rewriter, STRING_NEWLINES, WHITESPACE, _a, _b, _c, balanced_string, compact, count, helpers, include, starts;
|
var ACCESSORS, ASSIGNMENT, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_KEYWORDS, COMMENT, COMMENT_CLEANER, HALF_ASSIGNMENTS, HEREDOC, HEREDOC_INDENT, IDENTIFIER, INTERPOLATION, JS_CLEANER, JS_FORBIDDEN, JS_KEYWORDS, KEYWORDS, LAST_DENT, LAST_DENTS, LINE_BREAK, Lexer, MULTILINER, MULTI_DENT, NOT_REGEX, NO_NEWLINE, NUMBER, OPERATOR, REGEX_END, REGEX_ESCAPE, REGEX_INTERPOLATION, REGEX_START, RESERVED, Rewriter, STRING_NEWLINES, WHITESPACE, _a, _b, _c, balanced_string, compact, count, helpers, include, starts;
|
||||||
var __slice = Array.prototype.slice;
|
var __slice = Array.prototype.slice;
|
||||||
// The CoffeeScript Lexer. Uses a series of token-matching regexes to attempt
|
// The CoffeeScript Lexer. Uses a series of token-matching regexes to attempt
|
||||||
// matches against the beginning of the source code. When a match is found,
|
// matches against the beginning of the source code. When a match is found,
|
||||||
|
@ -126,7 +126,7 @@
|
||||||
// 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.identifier_token = function() {
|
Lexer.prototype.identifier_token = function() {
|
||||||
var accessed, id, spaced, tag;
|
var accessed, id, operator, spaced, tag;
|
||||||
if (!(id = this.match(IDENTIFIER, 1))) {
|
if (!(id = this.match(IDENTIFIER, 1))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -145,8 +145,8 @@
|
||||||
}
|
}
|
||||||
this.i += id.length;
|
this.i += id.length;
|
||||||
if (!(accessed)) {
|
if (!(accessed)) {
|
||||||
if (include(COFFEE_ALIASES, id)) {
|
if ((operator = Rewriter.alias_operator(id))) {
|
||||||
tag = (id = CONVERSIONS[id]);
|
tag = (id = operator);
|
||||||
}
|
}
|
||||||
if (this.prev() && this.prev()[0] === 'ASSIGN' && include(HALF_ASSIGNMENTS, tag)) {
|
if (this.prev() && this.prev()[0] === 'ASSIGN' && include(HALF_ASSIGNMENTS, tag)) {
|
||||||
return this.tag_half_assignment(tag);
|
return this.tag_half_assignment(tag);
|
||||||
|
@ -702,12 +702,4 @@
|
||||||
LINE_BREAK = ['INDENT', 'OUTDENT', 'TERMINATOR'];
|
LINE_BREAK = ['INDENT', 'OUTDENT', 'TERMINATOR'];
|
||||||
// Half-assignments...
|
// Half-assignments...
|
||||||
HALF_ASSIGNMENTS = ['-', '+', '/', '*', '%', '||', '&&', '?'];
|
HALF_ASSIGNMENTS = ['-', '+', '/', '*', '%', '||', '&&', '?'];
|
||||||
// Conversions from CoffeeScript operators into JavaScript ones.
|
|
||||||
CONVERSIONS = {
|
|
||||||
'and': '&&',
|
|
||||||
'or': '||',
|
|
||||||
'is': '==',
|
|
||||||
'isnt': '!=',
|
|
||||||
'not': '!'
|
|
||||||
};
|
|
||||||
})();
|
})();
|
||||||
|
|
137
lib/rewriter.js
137
lib/rewriter.js
|
@ -1,10 +1,10 @@
|
||||||
(function(){
|
(function(){
|
||||||
var BALANCED_PAIRS, EXPRESSION_CLOSE, EXPRESSION_END, EXPRESSION_START, IMPLICIT_BLOCK, IMPLICIT_CALL, IMPLICIT_END, IMPLICIT_FUNC, INVERSES, Rewriter, SINGLE_CLOSERS, SINGLE_LINERS, _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, helpers, include, pair;
|
var BALANCED_PAIRS, CONVERSIONS, EXPRESSION_CLOSE, EXPRESSION_END, EXPRESSION_START, IMPLICIT_BLOCK, IMPLICIT_CALL, IMPLICIT_END, IMPLICIT_FUNC, INVERSES, Rewriter, SINGLE_CLOSERS, SINGLE_LINERS, _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, exits, helpers, include, pair;
|
||||||
var __slice = Array.prototype.slice, __bind = function(func, obj, args) {
|
var __hasProp = Object.prototype.hasOwnProperty, __slice = Array.prototype.slice, __bind = function(func, obj, args) {
|
||||||
return function() {
|
return function() {
|
||||||
return func.apply(obj || {}, args ? args.concat(__slice.call(arguments, 0)) : arguments);
|
return func.apply(obj || {}, args ? args.concat(__slice.call(arguments, 0)) : arguments);
|
||||||
};
|
};
|
||||||
}, __hasProp = Object.prototype.hasOwnProperty;
|
};
|
||||||
// The CoffeeScript language has a good deal of optional syntax, implicit syntax,
|
// The CoffeeScript language has a good deal of optional syntax, implicit syntax,
|
||||||
// and shorthand syntax. This can greatly complicate a grammar and bloat
|
// and shorthand syntax. This can greatly complicate a grammar and bloat
|
||||||
// the resulting parse table. Instead of making the parser handle it all, we take
|
// the resulting parse table. Instead of making the parser handle it all, we take
|
||||||
|
@ -22,6 +22,32 @@
|
||||||
// Import the helpers we need.
|
// Import the helpers we need.
|
||||||
_b = helpers;
|
_b = helpers;
|
||||||
include = _b.include;
|
include = _b.include;
|
||||||
|
// Helper method to check if the given stream of tokens matches the exit conditions
|
||||||
|
exits = function(prev, token, post, cond) {
|
||||||
|
var _c, _d, _e, _f, args, k, length, match, pair, v;
|
||||||
|
length = 0;
|
||||||
|
match = 0;
|
||||||
|
args = {
|
||||||
|
prev: prev,
|
||||||
|
token: token,
|
||||||
|
post: post
|
||||||
|
};
|
||||||
|
_c = args;
|
||||||
|
for (k in _c) { if (__hasProp.call(_c, k)) {
|
||||||
|
v = _c[k];
|
||||||
|
if (k in cond) {
|
||||||
|
length += 1;
|
||||||
|
_e = cond[k];
|
||||||
|
for (_d = 0, _f = _e.length; _d < _f; _d++) {
|
||||||
|
pair = _e[_d];
|
||||||
|
if (v[0] === pair[0] && v[1] === pair[1]) {
|
||||||
|
match += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
return match === length;
|
||||||
|
};
|
||||||
// The **Rewriter** class is used by the [Lexer](lexer.html), directly against
|
// The **Rewriter** class is used by the [Lexer](lexer.html), directly against
|
||||||
// its internal array of tokens.
|
// its internal array of tokens.
|
||||||
exports.Rewriter = (function() {
|
exports.Rewriter = (function() {
|
||||||
|
@ -39,6 +65,7 @@
|
||||||
this.close_open_calls_and_indexes();
|
this.close_open_calls_and_indexes();
|
||||||
this.add_implicit_indentation();
|
this.add_implicit_indentation();
|
||||||
this.add_implicit_parentheses();
|
this.add_implicit_parentheses();
|
||||||
|
this.rewrite_object_keys();
|
||||||
this.ensure_balance(BALANCED_PAIRS);
|
this.ensure_balance(BALANCED_PAIRS);
|
||||||
this.rewrite_closing_parens();
|
this.rewrite_closing_parens();
|
||||||
return this.tokens;
|
return this.tokens;
|
||||||
|
@ -244,6 +271,74 @@
|
||||||
return 0;
|
return 0;
|
||||||
}, this));
|
}, this));
|
||||||
};
|
};
|
||||||
|
// Allow reserved words to be used as object keys. We scan the token stream
|
||||||
|
// until we enter an object. Any token before an assignment is considered the
|
||||||
|
// key which we rewrite back to an `IDENTIFIER`.
|
||||||
|
Rewriter.prototype.rewrite_object_keys = function() {
|
||||||
|
var levels;
|
||||||
|
levels = [];
|
||||||
|
return this.scan_tokens(__bind(function(prev, token, post, i) {
|
||||||
|
var _c, _d, _e, after, alias, balanced, last, pair, popped;
|
||||||
|
if (token[0] === '{') {
|
||||||
|
levels.push({
|
||||||
|
rewrite: true,
|
||||||
|
cond: {
|
||||||
|
token: [['}', '}']]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (levels.length) {
|
||||||
|
popped = false;
|
||||||
|
while ((last = levels[levels.length - 1]) && exits(prev, token, post, last.cond)) {
|
||||||
|
levels.pop();
|
||||||
|
popped = true;
|
||||||
|
}
|
||||||
|
if (!popped) {
|
||||||
|
balanced = false;
|
||||||
|
_d = BALANCED_PAIRS;
|
||||||
|
for (_c = 0, _e = _d.length; _c < _e; _c++) {
|
||||||
|
pair = _d[_c];
|
||||||
|
if (post && post[0] === pair[0]) {
|
||||||
|
levels.push({
|
||||||
|
rewrite: false,
|
||||||
|
cond: {
|
||||||
|
token: [[pair[1], pair[2] || post[1]]]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
balanced = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (token[0] === 'ASSIGN') {
|
||||||
|
if (last.rewrite) {
|
||||||
|
prev[0] = 'IDENTIFIER';
|
||||||
|
}
|
||||||
|
if ((alias = Rewriter.alias_operator(prev[1], true))) {
|
||||||
|
prev[1] = alias;
|
||||||
|
}
|
||||||
|
if (!balanced) {
|
||||||
|
after = this.tokens[i + 2];
|
||||||
|
if (post && post[0] === '->' && after && after[0] === 'INDENT') {
|
||||||
|
levels.push({
|
||||||
|
rewrite: false,
|
||||||
|
cond: {
|
||||||
|
prev: [['OUTDENT', after[1]]],
|
||||||
|
token: [['TERMINATOR', '\n']]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (last.rewrite) {
|
||||||
|
levels.push({
|
||||||
|
rewrite: false,
|
||||||
|
cond: {
|
||||||
|
token: [[',', ','], ['TERMINATOR', '\n'], ['}', '}']]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}, this));
|
||||||
|
};
|
||||||
// Ensure that all listed pairs of tokens are correctly balanced throughout
|
// Ensure that all listed pairs of tokens are correctly balanced throughout
|
||||||
// the course of the token stream.
|
// the course of the token stream.
|
||||||
Rewriter.prototype.ensure_balance = function(pairs) {
|
Rewriter.prototype.ensure_balance = function(pairs) {
|
||||||
|
@ -344,12 +439,36 @@
|
||||||
}
|
}
|
||||||
}, this));
|
}, this));
|
||||||
};
|
};
|
||||||
|
// Rewriter Properties
|
||||||
|
// ----------------
|
||||||
|
// Alias an identifier to a Coffee operator or vice versa.
|
||||||
|
Rewriter.alias_operator = function(id, reverse) {
|
||||||
|
var _c, _d, k, v;
|
||||||
|
if (!reverse) {
|
||||||
|
_c = CONVERSIONS;
|
||||||
|
for (k in _c) { if (__hasProp.call(_c, k)) {
|
||||||
|
if (id === k) {
|
||||||
|
return CONVERSIONS[k];
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
if (reverse) {
|
||||||
|
_d = CONVERSIONS;
|
||||||
|
for (k in _d) { if (__hasProp.call(_d, k)) {
|
||||||
|
v = _d[k];
|
||||||
|
if (id === v) {
|
||||||
|
return k;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
return Rewriter;
|
return Rewriter;
|
||||||
})();
|
}).call(this);
|
||||||
// Constants
|
// Constants
|
||||||
// ---------
|
// ---------
|
||||||
// List of the token pairs that must be balanced.
|
// List of the token pairs that must be balanced.
|
||||||
BALANCED_PAIRS = [['(', ')'], ['[', ']'], ['{', '}'], ['INDENT', 'OUTDENT'], ['PARAM_START', 'PARAM_END'], ['CALL_START', 'CALL_END'], ['INDEX_START', 'INDEX_END'], ['SOAKED_INDEX_START', 'SOAKED_INDEX_END']];
|
BALANCED_PAIRS = [['(', ')', ')'], ['[', ']', ']'], ['{', '}', '}'], ['INDENT', 'OUTDENT'], ['PARAM_START', 'PARAM_END', ')'], ['CALL_START', 'CALL_END', ')'], ['INDEX_START', 'INDEX_END', ']'], ['SOAKED_INDEX_START', 'SOAKED_INDEX_END', ']']];
|
||||||
// The inverse mappings of `BALANCED_PAIRS` we're trying to fix up, so we can
|
// The inverse mappings of `BALANCED_PAIRS` we're trying to fix up, so we can
|
||||||
// look things up from either end.
|
// look things up from either end.
|
||||||
INVERSES = {};
|
INVERSES = {};
|
||||||
|
@ -391,4 +510,12 @@
|
||||||
// The grammar can't disambiguate them, so we insert the implicit indentation.
|
// The grammar can't disambiguate them, so we insert the implicit indentation.
|
||||||
SINGLE_LINERS = ['ELSE', "->", "=>", 'TRY', 'FINALLY', 'THEN'];
|
SINGLE_LINERS = ['ELSE', "->", "=>", 'TRY', 'FINALLY', 'THEN'];
|
||||||
SINGLE_CLOSERS = ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN'];
|
SINGLE_CLOSERS = ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN'];
|
||||||
|
// Conversions from CoffeeScript operators into JavaScript ones.
|
||||||
|
CONVERSIONS = {
|
||||||
|
'and': '&&',
|
||||||
|
'or': '||',
|
||||||
|
'is': '==',
|
||||||
|
'isnt': '!=',
|
||||||
|
'not': '!'
|
||||||
|
};
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -98,7 +98,7 @@ exports.Lexer: class Lexer
|
||||||
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
|
@i: + id.length
|
||||||
unless accessed
|
unless accessed
|
||||||
tag: id: CONVERSIONS[id] if include COFFEE_ALIASES, id
|
tag: id: operator if (operator: Rewriter.alias_operator id)
|
||||||
return @tag_half_assignment tag if @prev() and @prev()[0] is 'ASSIGN' and include HALF_ASSIGNMENTS, tag
|
return @tag_half_assignment tag if @prev() and @prev()[0] is 'ASSIGN' and include HALF_ASSIGNMENTS, tag
|
||||||
@token tag, id
|
@token tag, id
|
||||||
true
|
true
|
||||||
|
@ -537,12 +537,3 @@ LINE_BREAK: ['INDENT', 'OUTDENT', 'TERMINATOR']
|
||||||
|
|
||||||
# Half-assignments...
|
# Half-assignments...
|
||||||
HALF_ASSIGNMENTS: ['-', '+', '/', '*', '%', '||', '&&', '?']
|
HALF_ASSIGNMENTS: ['-', '+', '/', '*', '%', '||', '&&', '?']
|
||||||
|
|
||||||
# Conversions from CoffeeScript operators into JavaScript ones.
|
|
||||||
CONVERSIONS: {
|
|
||||||
'and': '&&'
|
|
||||||
'or': '||'
|
|
||||||
'is': '=='
|
|
||||||
'isnt': '!='
|
|
||||||
'not': '!'
|
|
||||||
}
|
|
||||||
|
|
|
@ -213,6 +213,7 @@ exports.Rewriter: class Rewriter
|
||||||
balanced: yes
|
balanced: yes
|
||||||
if token[0] is 'ASSIGN'
|
if token[0] is 'ASSIGN'
|
||||||
prev[0]: 'IDENTIFIER' if last.rewrite
|
prev[0]: 'IDENTIFIER' if last.rewrite
|
||||||
|
prev[1]: alias if (alias: Rewriter.alias_operator prev[1], yes)
|
||||||
if not balanced
|
if not balanced
|
||||||
after: @tokens[i + 2]
|
after: @tokens[i + 2]
|
||||||
if post and post[0] is '->' and after and after[0] is 'INDENT'
|
if post and post[0] is '->' and after and after[0] is 'INDENT'
|
||||||
|
@ -289,6 +290,15 @@ exports.Rewriter: class Rewriter
|
||||||
else
|
else
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
# Rewriter Properties
|
||||||
|
# ----------------
|
||||||
|
|
||||||
|
# Alias an identifier to a Coffee operator or vice versa.
|
||||||
|
@alias_operator: (id, reverse) ->
|
||||||
|
(return CONVERSIONS[k]) for k of CONVERSIONS when id is k if not reverse
|
||||||
|
(return k) for k, v of CONVERSIONS when id is v if reverse
|
||||||
|
false
|
||||||
|
|
||||||
# Constants
|
# Constants
|
||||||
# ---------
|
# ---------
|
||||||
|
|
||||||
|
@ -333,3 +343,12 @@ IMPLICIT_END: ['IF', 'UNLESS', 'FOR', 'WHILE', 'UNTIL', 'TERMINATOR', 'INDENT'
|
||||||
# The grammar can't disambiguate them, so we insert the implicit indentation.
|
# The grammar can't disambiguate them, so we insert the implicit indentation.
|
||||||
SINGLE_LINERS: ['ELSE', "->", "=>", 'TRY', 'FINALLY', 'THEN']
|
SINGLE_LINERS: ['ELSE', "->", "=>", 'TRY', 'FINALLY', 'THEN']
|
||||||
SINGLE_CLOSERS: ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN']
|
SINGLE_CLOSERS: ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN']
|
||||||
|
|
||||||
|
# Conversions from CoffeeScript operators into JavaScript ones.
|
||||||
|
CONVERSIONS: {
|
||||||
|
'and': '&&'
|
||||||
|
'or': '||'
|
||||||
|
'is': '=='
|
||||||
|
'isnt': '!='
|
||||||
|
'not': '!'
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue