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(){
|
||||
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;
|
||||
// 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,
|
||||
|
@ -126,7 +126,7 @@
|
|||
// referenced as property names here, so you can still do `jQuery.is()` even
|
||||
// though `is` means `===` otherwise.
|
||||
Lexer.prototype.identifier_token = function() {
|
||||
var accessed, id, spaced, tag;
|
||||
var accessed, id, operator, spaced, tag;
|
||||
if (!(id = this.match(IDENTIFIER, 1))) {
|
||||
return false;
|
||||
}
|
||||
|
@ -145,8 +145,8 @@
|
|||
}
|
||||
this.i += id.length;
|
||||
if (!(accessed)) {
|
||||
if (include(COFFEE_ALIASES, id)) {
|
||||
tag = (id = CONVERSIONS[id]);
|
||||
if ((operator = Rewriter.alias_operator(id))) {
|
||||
tag = (id = operator);
|
||||
}
|
||||
if (this.prev() && this.prev()[0] === 'ASSIGN' && include(HALF_ASSIGNMENTS, tag)) {
|
||||
return this.tag_half_assignment(tag);
|
||||
|
@ -702,12 +702,4 @@
|
|||
LINE_BREAK = ['INDENT', 'OUTDENT', 'TERMINATOR'];
|
||||
// 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(){
|
||||
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 __slice = Array.prototype.slice, __bind = function(func, obj, args) {
|
||||
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 __hasProp = Object.prototype.hasOwnProperty, __slice = Array.prototype.slice, __bind = function(func, obj, args) {
|
||||
return function() {
|
||||
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,
|
||||
// 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
|
||||
|
@ -22,6 +22,32 @@
|
|||
// Import the helpers we need.
|
||||
_b = helpers;
|
||||
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
|
||||
// its internal array of tokens.
|
||||
exports.Rewriter = (function() {
|
||||
|
@ -39,6 +65,7 @@
|
|||
this.close_open_calls_and_indexes();
|
||||
this.add_implicit_indentation();
|
||||
this.add_implicit_parentheses();
|
||||
this.rewrite_object_keys();
|
||||
this.ensure_balance(BALANCED_PAIRS);
|
||||
this.rewrite_closing_parens();
|
||||
return this.tokens;
|
||||
|
@ -244,6 +271,74 @@
|
|||
return 0;
|
||||
}, 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
|
||||
// the course of the token stream.
|
||||
Rewriter.prototype.ensure_balance = function(pairs) {
|
||||
|
@ -344,12 +439,36 @@
|
|||
}
|
||||
}, 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;
|
||||
})();
|
||||
}).call(this);
|
||||
// Constants
|
||||
// ---------
|
||||
// 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
|
||||
// look things up from either end.
|
||||
INVERSES = {};
|
||||
|
@ -391,4 +510,12 @@
|
|||
// The grammar can't disambiguate them, so we insert the implicit indentation.
|
||||
SINGLE_LINERS = ['ELSE', "->", "=>", 'TRY', 'FINALLY', 'THEN'];
|
||||
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()
|
||||
@i: + id.length
|
||||
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
|
||||
@token tag, id
|
||||
true
|
||||
|
@ -537,12 +537,3 @@ LINE_BREAK: ['INDENT', 'OUTDENT', 'TERMINATOR']
|
|||
|
||||
# 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
|
||||
if token[0] is 'ASSIGN'
|
||||
prev[0]: 'IDENTIFIER' if last.rewrite
|
||||
prev[1]: alias if (alias: Rewriter.alias_operator prev[1], yes)
|
||||
if not balanced
|
||||
after: @tokens[i + 2]
|
||||
if post and post[0] is '->' and after and after[0] is 'INDENT'
|
||||
|
@ -289,6 +290,15 @@ exports.Rewriter: class Rewriter
|
|||
else
|
||||
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
|
||||
# ---------
|
||||
|
||||
|
@ -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.
|
||||
SINGLE_LINERS: ['ELSE', "->", "=>", 'TRY', 'FINALLY', 'THEN']
|
||||
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