1
0
Fork 0
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:
Stan Angeloff 2010-05-23 12:42:15 +03:00
parent 2cccd621ea
commit c200b95f6a
4 changed files with 156 additions and 27 deletions

View file

@ -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': '!'
};
})();

View file

@ -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': '!'
};
})();

View file

@ -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': '!'
}

View file

@ -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': '!'
}