mirror of
https://github.com/jashkenas/coffeescript.git
synced 2022-11-09 12:23:24 -05:00
refactored rewriter
This commit is contained in:
parent
a59d056ad2
commit
765f57b658
2 changed files with 508 additions and 527 deletions
333
lib/rewriter.js
333
lib/rewriter.js
|
@ -1,11 +1,9 @@
|
|||
(function() {
|
||||
var BALANCED_PAIRS, EXPRESSION_CLOSE, EXPRESSION_END, EXPRESSION_START, IMPLICIT_BLOCK, IMPLICIT_CALL, IMPLICIT_END, IMPLICIT_FUNC, INVERSES, LINEBREAKS, Rewriter, SINGLE_CLOSERS, SINGLE_LINERS, _i, _len, _result, include, pair;
|
||||
var __hasProp = Object.prototype.hasOwnProperty;
|
||||
var BALANCED_PAIRS, EXPRESSION_CLOSE, EXPRESSION_END, EXPRESSION_START, IMPLICIT_BLOCK, IMPLICIT_CALL, IMPLICIT_END, IMPLICIT_FUNC, INVERSES, LINEBREAKS, SINGLE_CLOSERS, SINGLE_LINERS, _i, _len, _ref, include, left, rite;
|
||||
include = require('./helpers').include;
|
||||
exports.Rewriter = (function() {
|
||||
Rewriter = function() {};
|
||||
Rewriter.prototype.rewrite = function(tokens) {
|
||||
this.tokens = tokens;
|
||||
exports.Rewriter = function() {};
|
||||
exports.Rewriter.prototype.rewrite = function(_arg) {
|
||||
this.tokens = _arg;
|
||||
this.adjustComments();
|
||||
this.removeLeadingNewlines();
|
||||
this.removeMidExpressionNewlines();
|
||||
|
@ -19,19 +17,16 @@
|
|||
this.rewriteClosingParens();
|
||||
return this.tokens;
|
||||
};
|
||||
Rewriter.prototype.scanTokens = function(block) {
|
||||
var i, move;
|
||||
exports.Rewriter.prototype.scanTokens = function(block) {
|
||||
var i, token, tokens;
|
||||
tokens = this.tokens;
|
||||
i = 0;
|
||||
while (true) {
|
||||
if (!(this.tokens[i])) {
|
||||
break;
|
||||
}
|
||||
move = block.call(this, this.tokens[i], i);
|
||||
i += move;
|
||||
while (token = tokens[i]) {
|
||||
i += block.call(this, token, i, tokens);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
Rewriter.prototype.detectEnd = function(i, condition, action) {
|
||||
exports.Rewriter.prototype.detectEnd = function(i, condition, action) {
|
||||
var levels, token;
|
||||
levels = 0;
|
||||
while (true) {
|
||||
|
@ -44,81 +39,82 @@
|
|||
}
|
||||
if (include(EXPRESSION_START, token[0])) {
|
||||
levels += 1;
|
||||
}
|
||||
if (include(EXPRESSION_END, token[0])) {
|
||||
} else if (include(EXPRESSION_END, token[0])) {
|
||||
levels -= 1;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
return i - 1;
|
||||
};
|
||||
Rewriter.prototype.adjustComments = function() {
|
||||
return this.scanTokens(function(token, i) {
|
||||
var _ref, _this, after, before, post, prev;
|
||||
exports.Rewriter.prototype.adjustComments = function() {
|
||||
return this.scanTokens(function(token, i, tokens) {
|
||||
var _ref, after, before, post, prev;
|
||||
if (token[0] !== 'HERECOMMENT') {
|
||||
return 1;
|
||||
}
|
||||
_ref = [this.tokens[i - 2], this.tokens[i - 1], this.tokens[i + 1], this.tokens[i + 2]], before = _ref[0], prev = _ref[1], post = _ref[2], after = _ref[3];
|
||||
if (after && after[0] === 'INDENT') {
|
||||
this.tokens.splice(i + 2, 1);
|
||||
if (before && before[0] === 'OUTDENT' && post && (prev[0] === (_ref = post[0])) && (_ref === 'TERMINATOR')) {
|
||||
this.tokens.splice(i - 2, 1);
|
||||
before = tokens[i - 2];
|
||||
prev = tokens[i - 1];
|
||||
post = tokens[i + 1];
|
||||
after = tokens[i + 2];
|
||||
if (((after != null) ? after[0] === 'INDENT' : undefined)) {
|
||||
tokens.splice(i + 2, 1);
|
||||
if (((before != null) ? before[0] === 'OUTDENT' : undefined) && ((post != null) ? post[0] === 'TERMINATOR' : undefined)) {
|
||||
tokens.splice(i - 2, 1);
|
||||
} else {
|
||||
this.tokens.splice(i, 0, after);
|
||||
tokens.splice(i, 0, after);
|
||||
}
|
||||
} else if (prev && !('TERMINATOR' === (_ref = prev[0]) || 'INDENT' === _ref || 'OUTDENT' === _ref)) {
|
||||
if (post && post[0] === 'TERMINATOR' && after && after[0] === 'OUTDENT') {
|
||||
(_this = this.tokens).splice.apply(_this, [i + 2, 0].concat(this.tokens.splice(i, 2)));
|
||||
if (this.tokens[i + 2][0] !== 'TERMINATOR') {
|
||||
this.tokens.splice(i + 2, 0, ['TERMINATOR', "\n", prev[2]]);
|
||||
} else if (!('TERMINATOR' === (_ref = ((prev != null) ? prev[0] : undefined)) || 'INDENT' === _ref || 'OUTDENT' === _ref)) {
|
||||
if (((post != null) ? post[0] === 'TERMINATOR' : undefined) && ((after != null) ? after[0] === 'OUTDENT' : undefined)) {
|
||||
tokens.splice.apply(tokens, [i + 2, 0].concat(tokens.splice(i, 2)));
|
||||
if (tokens[i + 2][0] !== 'TERMINATOR') {
|
||||
tokens.splice(i + 2, 0, ['TERMINATOR', '\n', prev[2]]);
|
||||
}
|
||||
} else {
|
||||
this.tokens.splice(i, 0, ['TERMINATOR', "\n", prev[2]]);
|
||||
tokens.splice(i, 0, ['TERMINATOR', '\n', prev[2]]);
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
};
|
||||
Rewriter.prototype.removeLeadingNewlines = function() {
|
||||
var _result;
|
||||
_result = [];
|
||||
while (this.tokens[0] && this.tokens[0][0] === 'TERMINATOR') {
|
||||
_result.push(this.tokens.shift());
|
||||
exports.Rewriter.prototype.removeLeadingNewlines = function() {
|
||||
var _len, _ref, i, tag;
|
||||
_ref = this.tokens;
|
||||
for (i = 0, _len = _ref.length; i < _len; i++) {
|
||||
tag = _ref[i][0];
|
||||
if (tag !== 'TERMINATOR') {
|
||||
break;
|
||||
}
|
||||
return _result;
|
||||
}
|
||||
return i ? this.tokens.splice(0, i) : undefined;
|
||||
};
|
||||
Rewriter.prototype.removeMidExpressionNewlines = function() {
|
||||
return this.scanTokens(function(token, i) {
|
||||
if (!(include(EXPRESSION_CLOSE, this.tag(i + 1)) && token[0] === 'TERMINATOR')) {
|
||||
exports.Rewriter.prototype.removeMidExpressionNewlines = function() {
|
||||
return this.scanTokens(function(token, i, tokens) {
|
||||
if (!(token[0] === 'TERMINATOR' && include(EXPRESSION_CLOSE, this.tag(i + 1)))) {
|
||||
return 1;
|
||||
}
|
||||
this.tokens.splice(i, 1);
|
||||
tokens.splice(i, 1);
|
||||
return 0;
|
||||
});
|
||||
};
|
||||
Rewriter.prototype.closeOpenCalls = function() {
|
||||
return this.scanTokens(function(token, i) {
|
||||
exports.Rewriter.prototype.closeOpenCalls = function() {
|
||||
var action, condition;
|
||||
if (token[0] === 'CALL_START') {
|
||||
condition = function(token, i) {
|
||||
var _ref;
|
||||
return ((')' === (_ref = token[0]) || 'CALL_END' === _ref)) || (token[0] === 'OUTDENT' && this.tokens[i - 1][0] === ')');
|
||||
return ((')' === (_ref = token[0]) || 'CALL_END' === _ref)) || token[0] === 'OUTDENT' && this.tag(i - 1) === ')';
|
||||
};
|
||||
action = function(token, i) {
|
||||
var idx;
|
||||
idx = token[0] === 'OUTDENT' ? i - 1 : i;
|
||||
return (this.tokens[idx][0] = 'CALL_END');
|
||||
return (this.tokens[token[0] === 'OUTDENT' ? i - 1 : i][0] = 'CALL_END');
|
||||
};
|
||||
return this.scanTokens(function(token, i) {
|
||||
if (token[0] === 'CALL_START') {
|
||||
this.detectEnd(i + 1, condition, action);
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
};
|
||||
Rewriter.prototype.closeOpenIndexes = function() {
|
||||
return this.scanTokens(function(token, i) {
|
||||
exports.Rewriter.prototype.closeOpenIndexes = function() {
|
||||
var action, condition;
|
||||
if (token[0] === 'INDEX_START') {
|
||||
condition = function(token, i) {
|
||||
var _ref;
|
||||
return (']' === (_ref = token[0]) || 'INDEX_END' === _ref);
|
||||
|
@ -126,24 +122,38 @@
|
|||
action = function(token, i) {
|
||||
return (token[0] = 'INDEX_END');
|
||||
};
|
||||
return this.scanTokens(function(token, i) {
|
||||
if (token[0] === 'INDEX_START') {
|
||||
this.detectEnd(i + 1, condition, action);
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
};
|
||||
Rewriter.prototype.addImplicitBraces = function() {
|
||||
var stack;
|
||||
exports.Rewriter.prototype.addImplicitBraces = function() {
|
||||
var action, condition, stack;
|
||||
stack = [];
|
||||
return this.scanTokens(function(token, i) {
|
||||
var action, condition, idx, last, tok;
|
||||
if (include(EXPRESSION_START, token[0])) {
|
||||
stack.push((token[0] === 'INDENT' && (this.tag(i - 1) === '{')) ? '{' : token[0]);
|
||||
condition = function(token, i) {
|
||||
var _ref, one, tag, three, two;
|
||||
if ((this.tag(i + 1) === 'HERECOMMENT' || this.tag(i - 1) === 'HERECOMMENT')) {
|
||||
return false;
|
||||
}
|
||||
if (include(EXPRESSION_END, token[0])) {
|
||||
_ref = this.tokens.slice(i + 1, i + 4), one = _ref[0], two = _ref[1], three = _ref[2];
|
||||
tag = token[0];
|
||||
return (('TERMINATOR' === tag || 'OUTDENT' === tag)) && !(((two != null) ? two[0] === ':' : undefined) || ((one != null) ? one[0] === '@' : undefined) && ((three != null) ? three[0] === ':' : undefined)) || tag === ',' && (!('IDENTIFIER' === (_ref = ((one != null) ? one[0] : undefined)) || 'STRING' === _ref || '@' === _ref || 'TERMINATOR' === _ref || 'OUTDENT' === _ref));
|
||||
};
|
||||
action = function(token, i) {
|
||||
return this.tokens.splice(i, 0, ['}', '}', token[2]]);
|
||||
};
|
||||
return this.scanTokens(function(token, i, tokens) {
|
||||
var idx, last, tag, tok;
|
||||
if (include(EXPRESSION_START, tag = token[0])) {
|
||||
stack.push(tag === 'INDENT' && this.tag(i - 1) === '{' ? '{' : tag);
|
||||
}
|
||||
if (include(EXPRESSION_END, tag)) {
|
||||
stack.pop();
|
||||
}
|
||||
last = stack[stack.length - 1];
|
||||
if (token[0] === ':' && (!last || last[0] !== '{')) {
|
||||
if (tag === ':' && !(last && last[0] === '{')) {
|
||||
stack.push('{');
|
||||
idx = this.tag(i - 2) === '@' ? i - 2 : i - 1;
|
||||
if (this.tag(idx - 2) === 'HERECOMMENT') {
|
||||
|
@ -151,62 +161,56 @@
|
|||
}
|
||||
tok = ['{', '{', token[2]];
|
||||
tok.generated = true;
|
||||
this.tokens.splice(idx, 0, tok);
|
||||
condition = function(token, i) {
|
||||
var _ref, one, three, two;
|
||||
_ref = this.tokens.slice(i + 1, i + 4), one = _ref[0], two = _ref[1], three = _ref[2];
|
||||
if ((this.tag(i + 1) === 'HERECOMMENT' || this.tag(i - 1) === 'HERECOMMENT')) {
|
||||
return false;
|
||||
}
|
||||
return ((('TERMINATOR' === (_ref = token[0]) || 'OUTDENT' === _ref)) && !((two && two[0] === ':') || (one && one[0] === '@' && three && three[0] === ':'))) || (token[0] === ',' && one && (!('IDENTIFIER' === (_ref = one[0]) || 'STRING' === _ref || '@' === _ref || 'TERMINATOR' === _ref || 'OUTDENT' === _ref)));
|
||||
};
|
||||
action = function(token, i) {
|
||||
return this.tokens.splice(i, 0, ['}', '}', token[2]]);
|
||||
};
|
||||
tokens.splice(idx, 0, tok);
|
||||
this.detectEnd(i + 2, condition, action);
|
||||
return 2;
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
};
|
||||
Rewriter.prototype.addImplicitParentheses = function() {
|
||||
var classLine;
|
||||
exports.Rewriter.prototype.addImplicitParentheses = function() {
|
||||
var action, classLine;
|
||||
classLine = false;
|
||||
return this.scanTokens(function(token, i) {
|
||||
var _ref, action, callObject, condition, idx, next, prev, seenSingle;
|
||||
if (token[0] === 'CLASS') {
|
||||
action = function(token, i) {
|
||||
var idx;
|
||||
idx = token[0] === 'OUTDENT' ? i + 1 : i;
|
||||
return this.tokens.splice(idx, 0, ['CALL_END', ')', token[2]]);
|
||||
};
|
||||
return this.scanTokens(function(token, i, tokens) {
|
||||
var _ref, callObject, condition, idx, next, prev, seenSingle, tag;
|
||||
tag = token[0];
|
||||
if (tag === 'CLASS') {
|
||||
classLine = true;
|
||||
}
|
||||
prev = this.tokens[i - 1];
|
||||
next = this.tokens[i + 1];
|
||||
prev = tokens[i - 1];
|
||||
next = tokens[i + 1];
|
||||
idx = 1;
|
||||
callObject = !classLine && token[0] === 'INDENT' && next && next.generated && next[0] === '{' && prev && include(IMPLICIT_FUNC, prev[0]);
|
||||
callObject = !classLine && tag === 'INDENT' && next && next.generated && next[0] === '{' && prev && include(IMPLICIT_FUNC, prev[0]);
|
||||
if (callObject) {
|
||||
idx = 2;
|
||||
}
|
||||
seenSingle = false;
|
||||
if (include(LINEBREAKS, token[0])) {
|
||||
if (include(LINEBREAKS, tag)) {
|
||||
classLine = false;
|
||||
}
|
||||
if (prev && !prev.spaced && token[0] === '?') {
|
||||
if (prev && !prev.spaced && tag === '?') {
|
||||
token.call = true;
|
||||
}
|
||||
if (prev && (prev.spaced && (include(IMPLICIT_FUNC, prev[0]) || prev.call) && include(IMPLICIT_CALL, token[0]) && !(token[0] === 'UNARY' && (('IN' === (_ref = this.tag(i + 1)) || 'OF' === _ref || 'INSTANCEOF' === _ref)))) || callObject) {
|
||||
this.tokens.splice(i, 0, ['CALL_START', '(', token[2]]);
|
||||
if (callObject || prev && prev.spaced && (prev.call || include(IMPLICIT_FUNC, prev[0])) && include(IMPLICIT_CALL, tag) && !(tag === 'UNARY' && (('IN' === (_ref = this.tag(i + 1)) || 'OF' === _ref || 'INSTANCEOF' === _ref)))) {
|
||||
tokens.splice(i, 0, ['CALL_START', '(', token[2]]);
|
||||
condition = function(token, i) {
|
||||
var _ref, post;
|
||||
var post;
|
||||
if (!seenSingle && token.fromThen) {
|
||||
return true;
|
||||
}
|
||||
if (('IF' === (_ref = token[0]) || 'ELSE' === _ref || 'UNLESS' === _ref || '->' === _ref || '=>' === _ref)) {
|
||||
tag = token[0];
|
||||
if (('IF' === tag || 'ELSE' === tag || 'UNLESS' === tag || '->' === tag || '=>' === tag)) {
|
||||
seenSingle = true;
|
||||
}
|
||||
post = this.tokens[i + 1];
|
||||
return (!token.generated && this.tokens[i - 1][0] !== ',' && include(IMPLICIT_END, token[0]) && !(token[0] === 'INDENT' && (include(IMPLICIT_BLOCK, this.tag(i - 1)) || this.tag(i - 2) === 'CLASS' || (post && post.generated && post[0] === '{')))) || token[0] === 'PROPERTY_ACCESS' && this.tag(i - 1) === 'OUTDENT';
|
||||
};
|
||||
action = function(token, i) {
|
||||
idx = token[0] === 'OUTDENT' ? i + 1 : i;
|
||||
return this.tokens.splice(idx, 0, ['CALL_END', ')', token[2]]);
|
||||
if (tag === 'PROPERTY_ACCESS' && this.tag(i - 1) === 'OUTDENT') {
|
||||
return true;
|
||||
}
|
||||
return !token.generated && this.tag(i - 1) !== ',' && include(IMPLICIT_END, tag) && (tag !== 'INDENT' || (this.tag(i - 2) !== 'CLASS' && !include(IMPLICIT_BLOCK, this.tag(i - 1)) && !((post = this.tokens[i + 1]) && post.generated && post[0] === '{')));
|
||||
};
|
||||
this.detectEnd(i + idx, condition, action);
|
||||
if (prev[0] === '?') {
|
||||
|
@ -217,82 +221,80 @@
|
|||
return 1;
|
||||
});
|
||||
};
|
||||
Rewriter.prototype.addImplicitIndentation = function() {
|
||||
return this.scanTokens(function(token, i) {
|
||||
var _ref, _this, action, condition, indent, outdent, starter;
|
||||
if (token[0] === 'ELSE' && this.tag(i - 1) !== 'OUTDENT') {
|
||||
(_this = this.tokens).splice.apply(_this, [i, 0].concat(this.indentation(token)));
|
||||
exports.Rewriter.prototype.addImplicitIndentation = function() {
|
||||
return this.scanTokens(function(token, i, tokens) {
|
||||
var _ref, action, condition, indent, outdent, starter, tag;
|
||||
tag = token[0];
|
||||
if (tag === 'ELSE' && this.tag(i - 1) !== 'OUTDENT') {
|
||||
tokens.splice.apply(tokens, [i, 0].concat(this.indentation(token)));
|
||||
return 2;
|
||||
}
|
||||
if (token[0] === 'CATCH' && (this.tag(i + 2) === 'TERMINATOR' || this.tag(i + 2) === 'FINALLY')) {
|
||||
(_this = this.tokens).splice.apply(_this, [i + 2, 0].concat(this.indentation(token)));
|
||||
if (tag === 'CATCH' && (('TERMINATOR' === (_ref = this.tag(i + 2)) || 'FINALLY' === _ref))) {
|
||||
tokens.splice.apply(tokens, [i + 2, 0].concat(this.indentation(token)));
|
||||
return 4;
|
||||
}
|
||||
if (include(SINGLE_LINERS, token[0]) && this.tag(i + 1) !== 'INDENT' && !(token[0] === 'ELSE' && this.tag(i + 1) === 'IF')) {
|
||||
starter = token[0];
|
||||
if (include(SINGLE_LINERS, tag) && this.tag(i + 1) !== 'INDENT' && !(tag === 'ELSE' && this.tag(i + 1) === 'IF')) {
|
||||
starter = tag;
|
||||
_ref = this.indentation(token), indent = _ref[0], outdent = _ref[1];
|
||||
if (starter === 'THEN') {
|
||||
indent.fromThen = true;
|
||||
}
|
||||
indent.generated = (outdent.generated = true);
|
||||
this.tokens.splice(i + 1, 0, indent);
|
||||
tokens.splice(i + 1, 0, indent);
|
||||
condition = function(token, i) {
|
||||
return (include(SINGLE_CLOSERS, token[0]) && token[1] !== ';') && !(token[0] === 'ELSE' && !('IF' === starter || 'THEN' === starter));
|
||||
return token[1] !== ';' && include(SINGLE_CLOSERS, token[0]) && !(token[0] === 'ELSE' && !('IF' === starter || 'THEN' === starter));
|
||||
};
|
||||
action = function(token, i) {
|
||||
var idx;
|
||||
idx = this.tokens[i - 1][0] === ',' ? i - 1 : i;
|
||||
return this.tokens.splice(idx, 0, outdent);
|
||||
return this.tokens.splice(this.tag(i - 1) === ',' ? i - 1 : i, 0, outdent);
|
||||
};
|
||||
this.detectEnd(i + 2, condition, action);
|
||||
if (token[0] === 'THEN') {
|
||||
this.tokens.splice(i, 1);
|
||||
if (tag === 'THEN') {
|
||||
tokens.splice(i, 1);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
};
|
||||
Rewriter.prototype.tagPostfixConditionals = function() {
|
||||
return this.scanTokens(function(token, i) {
|
||||
var _ref, action, condition, original;
|
||||
if (('IF' === (_ref = token[0]) || 'UNLESS' === _ref)) {
|
||||
original = token;
|
||||
exports.Rewriter.prototype.tagPostfixConditionals = function() {
|
||||
var condition;
|
||||
condition = function(token, i) {
|
||||
var _ref;
|
||||
return ('TERMINATOR' === (_ref = token[0]) || 'INDENT' === _ref);
|
||||
};
|
||||
action = function(token, i) {
|
||||
return token[0] !== 'INDENT' ? (original[0] = 'POST_' + original[0]) : undefined;
|
||||
};
|
||||
this.detectEnd(i + 1, condition, action);
|
||||
return this.scanTokens(function(token, i) {
|
||||
var _ref, original;
|
||||
if (!(('IF' === (_ref = token[0]) || 'UNLESS' === _ref))) {
|
||||
return 1;
|
||||
}
|
||||
original = token;
|
||||
this.detectEnd(i + 1, condition, function(token, i) {
|
||||
return token[0] !== 'INDENT' ? (original[0] = 'POST_' + original[0]) : undefined;
|
||||
});
|
||||
return 1;
|
||||
});
|
||||
};
|
||||
Rewriter.prototype.ensureBalance = function(pairs) {
|
||||
var _result, key, levels, line, open, openLine, unclosed, value;
|
||||
exports.Rewriter.prototype.ensureBalance = function(pairs) {
|
||||
var _result, key, levels, open, openLine, unclosed, value;
|
||||
levels = {};
|
||||
openLine = {};
|
||||
this.scanTokens(function(token, i) {
|
||||
var _i, _len, _ref, _ref2, close, open, pair;
|
||||
var _i, _len, _ref, _ref2, close, open, tag;
|
||||
tag = token[0];
|
||||
_ref = pairs;
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
pair = _ref[_i];
|
||||
_ref2 = pair, open = _ref2[0], close = _ref2[1];
|
||||
levels[open] || (levels[open] = 0);
|
||||
if (token[0] === open) {
|
||||
_ref2 = _ref[_i], open = _ref2[0], close = _ref2[1];
|
||||
levels[open] |= 0;
|
||||
if (tag === open) {
|
||||
if (levels[open] === 0) {
|
||||
openLine[open] = token[2];
|
||||
}
|
||||
levels[open] += 1;
|
||||
}
|
||||
if (token[0] === close) {
|
||||
} else if (tag === close) {
|
||||
levels[open] -= 1;
|
||||
}
|
||||
if (levels[open] < 0) {
|
||||
throw new Error("too many " + (token[1]) + " on line " + (token[2] + 1));
|
||||
throw Error("too many " + (token[1]) + " on line " + (token[2] + 1));
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
|
@ -300,7 +302,6 @@
|
|||
unclosed = (function() {
|
||||
_result = [];
|
||||
for (key in levels) {
|
||||
if (!__hasProp.call(levels, key)) continue;
|
||||
value = levels[key];
|
||||
if (value > 0) {
|
||||
_result.push(key);
|
||||
|
@ -309,34 +310,31 @@
|
|||
return _result;
|
||||
})();
|
||||
if (unclosed.length) {
|
||||
open = unclosed[0];
|
||||
line = openLine[open] + 1;
|
||||
throw new Error("unclosed " + (open) + " on line " + (line));
|
||||
throw Error("unclosed " + (open = unclosed[0]) + " on line " + (openLine[open] + 1));
|
||||
}
|
||||
};
|
||||
Rewriter.prototype.rewriteClosingParens = function() {
|
||||
var _ref, debt, key, stack, val;
|
||||
exports.Rewriter.prototype.rewriteClosingParens = function() {
|
||||
var _ref, debt, key, stack;
|
||||
stack = [];
|
||||
debt = {};
|
||||
_ref = INVERSES;
|
||||
for (key in _ref) {
|
||||
if (!__hasProp.call(_ref, key)) continue;
|
||||
val = _ref[key];
|
||||
(debt[key] = 0);
|
||||
}
|
||||
return this.scanTokens(function(token, i) {
|
||||
var _ref2, inv, match, mtag, oppos, tag;
|
||||
tag = token[0];
|
||||
inv = INVERSES[token[0]];
|
||||
if (include(EXPRESSION_START, tag)) {
|
||||
return this.scanTokens(function(token, i, tokens) {
|
||||
var inv, match, mtag, oppos, tag, val;
|
||||
if (include(EXPRESSION_START, tag = token[0])) {
|
||||
stack.push(token);
|
||||
return 1;
|
||||
} else if (include(EXPRESSION_END, tag)) {
|
||||
if (debt[inv] > 0) {
|
||||
}
|
||||
if (!(include(EXPRESSION_END, tag))) {
|
||||
return 1;
|
||||
}
|
||||
if (debt[(inv = INVERSES[tag])] > 0) {
|
||||
debt[inv] -= 1;
|
||||
this.tokens.splice(i, 1);
|
||||
tokens.splice(i, 1);
|
||||
return 0;
|
||||
} else {
|
||||
}
|
||||
match = stack.pop();
|
||||
mtag = match[0];
|
||||
oppos = INVERSES[mtag];
|
||||
|
@ -345,56 +343,37 @@
|
|||
}
|
||||
debt[mtag] += 1;
|
||||
val = [oppos, mtag === 'INDENT' ? match[1] : oppos];
|
||||
if ((((_ref2 = this.tokens[i + 2]) != null) ? _ref2[0] === mtag : undefined)) {
|
||||
this.tokens.splice(i + 3, 0, val);
|
||||
if (this.tag(i + 2) === mtag) {
|
||||
tokens.splice(i + 3, 0, val);
|
||||
stack.push(match);
|
||||
} else {
|
||||
this.tokens.splice(i, 0, val);
|
||||
tokens.splice(i, 0, val);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
};
|
||||
Rewriter.prototype.indentation = function(token) {
|
||||
exports.Rewriter.prototype.indentation = function(token) {
|
||||
return [['INDENT', 2, token[2]], ['OUTDENT', 2, token[2]]];
|
||||
};
|
||||
Rewriter.prototype.tag = function(i) {
|
||||
return this.tokens[i] && this.tokens[i][0];
|
||||
exports.Rewriter.prototype.tag = function(i) {
|
||||
var _ref;
|
||||
return (((_ref = this.tokens[i]) != null) ? _ref[0] : undefined);
|
||||
};
|
||||
return Rewriter;
|
||||
})();
|
||||
BALANCED_PAIRS = [['(', ')'], ['[', ']'], ['{', '}'], ['INDENT', 'OUTDENT'], ['PARAM_START', 'PARAM_END'], ['CALL_START', 'CALL_END'], ['INDEX_START', 'INDEX_END']];
|
||||
BALANCED_PAIRS = [['(', ')'], ['[', ']'], ['{', '}'], ['INDENT', 'OUTDENT'], ['CALL_START', 'CALL_END'], ['PARAM_START', 'PARAM_END'], ['INDEX_START', 'INDEX_END']];
|
||||
INVERSES = {};
|
||||
EXPRESSION_START = [];
|
||||
EXPRESSION_END = [];
|
||||
for (_i = 0, _len = BALANCED_PAIRS.length; _i < _len; _i++) {
|
||||
pair = BALANCED_PAIRS[_i];
|
||||
INVERSES[pair[0]] = pair[1];
|
||||
INVERSES[pair[1]] = pair[0];
|
||||
_ref = BALANCED_PAIRS[_i], left = _ref[0], rite = _ref[1];
|
||||
EXPRESSION_START.push(INVERSES[rite] = left);
|
||||
EXPRESSION_END.push(INVERSES[left] = rite);
|
||||
}
|
||||
EXPRESSION_START = (function() {
|
||||
_result = [];
|
||||
for (_i = 0, _len = BALANCED_PAIRS.length; _i < _len; _i++) {
|
||||
pair = BALANCED_PAIRS[_i];
|
||||
_result.push(pair[0]);
|
||||
}
|
||||
return _result;
|
||||
})();
|
||||
EXPRESSION_END = (function() {
|
||||
_result = [];
|
||||
for (_i = 0, _len = BALANCED_PAIRS.length; _i < _len; _i++) {
|
||||
pair = BALANCED_PAIRS[_i];
|
||||
_result.push(pair[1]);
|
||||
}
|
||||
return _result;
|
||||
})();
|
||||
EXPRESSION_CLOSE = ['CATCH', 'WHEN', 'ELSE', 'FINALLY'].concat(EXPRESSION_END);
|
||||
IMPLICIT_FUNC = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@', 'THIS'];
|
||||
IMPLICIT_CALL = ['IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'CLASS', 'IF', 'UNLESS', 'TRY', 'SWITCH', 'THIS', 'NULL', 'UNARY', 'TRUE', 'FALSE', 'YES', 'NO', 'ON', 'OFF', '@', '->', '=>', '[', '(', '{'];
|
||||
IMPLICIT_BLOCK = ['->', '=>', '{', '[', ','];
|
||||
IMPLICIT_END = ['POST_IF', 'POST_UNLESS', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'TERMINATOR', 'INDENT'];
|
||||
SINGLE_LINERS = ['ELSE', "->", "=>", 'TRY', 'FINALLY', 'THEN'];
|
||||
SINGLE_LINERS = ['ELSE', '->', '=>', 'TRY', 'FINALLY', 'THEN'];
|
||||
SINGLE_CLOSERS = ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN'];
|
||||
LINEBREAKS = ['TERMINATOR', 'INDENT', 'OUTDENT'];
|
||||
}).call(this);
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
# The **Rewriter** class is used by the [Lexer](lexer.html), directly against
|
||||
# its internal array of tokens.
|
||||
exports.Rewriter = class Rewriter
|
||||
class exports.Rewriter
|
||||
|
||||
# Helpful snippet for debugging:
|
||||
# puts (t[0] + '/' + t[1] for t in @tokens).join ' '
|
||||
|
@ -20,8 +20,7 @@ exports.Rewriter = class Rewriter
|
|||
# stream, with a big ol' efficient switch, but it's much nicer to work with
|
||||
# like this. The order of these passes matters -- indentation must be
|
||||
# corrected before implicit parentheses can be wrapped around blocks of code.
|
||||
rewrite: (tokens) ->
|
||||
@tokens = tokens
|
||||
rewrite: (@tokens) ->
|
||||
@adjustComments()
|
||||
@removeLeadingNewlines()
|
||||
@removeMidExpressionNewlines()
|
||||
|
@ -41,11 +40,9 @@ exports.Rewriter = class Rewriter
|
|||
# as tokens are inserted and removed, and the stream changes length under
|
||||
# our feet.
|
||||
scanTokens: (block) ->
|
||||
{tokens} = this
|
||||
i = 0
|
||||
loop
|
||||
break unless @tokens[i]
|
||||
move = block.call this, @tokens[i], i
|
||||
i += move
|
||||
i += block.call this, token, i, tokens while token = tokens[i]
|
||||
true
|
||||
|
||||
detectEnd: (i, condition, action) ->
|
||||
|
@ -54,175 +51,181 @@ exports.Rewriter = class Rewriter
|
|||
token = @tokens[i]
|
||||
return action.call this, token, i if levels is 0 and condition.call this, token, i
|
||||
return action.call this, token, i - 1 if not token or levels < 0
|
||||
levels += 1 if include EXPRESSION_START, token[0]
|
||||
levels -= 1 if include EXPRESSION_END, token[0]
|
||||
if include EXPRESSION_START, token[0]
|
||||
levels += 1
|
||||
else if include EXPRESSION_END, token[0]
|
||||
levels -= 1
|
||||
i += 1
|
||||
i - 1
|
||||
|
||||
# Massage newlines and indentations so that comments don't have to be
|
||||
# correctly indented, or appear on a line of their own.
|
||||
adjustComments: ->
|
||||
@scanTokens (token, i) ->
|
||||
@scanTokens (token, i, tokens) ->
|
||||
return 1 unless token[0] is 'HERECOMMENT'
|
||||
[before, prev, post, after] = [@tokens[i - 2], @tokens[i - 1], @tokens[i + 1], @tokens[i + 2]]
|
||||
if after and after[0] is 'INDENT'
|
||||
@tokens.splice i + 2, 1
|
||||
if before and before[0] is 'OUTDENT' and post and prev[0] is post[0] is 'TERMINATOR'
|
||||
@tokens.splice i - 2, 1
|
||||
before = tokens[i - 2]
|
||||
prev = tokens[i - 1]
|
||||
post = tokens[i + 1]
|
||||
after = tokens[i + 2]
|
||||
if after?[0] is 'INDENT'
|
||||
tokens.splice i + 2, 1
|
||||
if before?[0] is 'OUTDENT' and post?[0] is 'TERMINATOR'
|
||||
tokens.splice i - 2, 1
|
||||
else
|
||||
@tokens.splice i, 0, after
|
||||
else if prev and prev[0] not in ['TERMINATOR', 'INDENT', 'OUTDENT']
|
||||
if post and post[0] is 'TERMINATOR' and after and after[0] is 'OUTDENT'
|
||||
@tokens.splice(i + 2, 0, @tokens.splice(i, 2)...)
|
||||
if @tokens[i + 2][0] isnt 'TERMINATOR'
|
||||
@tokens.splice i + 2, 0, ['TERMINATOR', "\n", prev[2]]
|
||||
tokens.splice i, 0, after
|
||||
else if prev?[0] not in ['TERMINATOR', 'INDENT', 'OUTDENT']
|
||||
if post?[0] is 'TERMINATOR' and after?[0] is 'OUTDENT'
|
||||
tokens.splice i + 2, 0, tokens.splice(i, 2)...
|
||||
if tokens[i + 2][0] isnt 'TERMINATOR'
|
||||
tokens.splice i + 2, 0, ['TERMINATOR', '\n', prev[2]]
|
||||
else
|
||||
@tokens.splice i, 0, ['TERMINATOR', "\n", prev[2]]
|
||||
tokens.splice i, 0, ['TERMINATOR', '\n', prev[2]]
|
||||
return 2
|
||||
return 1
|
||||
1
|
||||
|
||||
# Leading newlines would introduce an ambiguity in the grammar, so we
|
||||
# dispatch them here.
|
||||
removeLeadingNewlines: ->
|
||||
@tokens.shift() while @tokens[0] and @tokens[0][0] is 'TERMINATOR'
|
||||
break for [tag], i in @tokens when tag isnt 'TERMINATOR'
|
||||
@tokens.splice 0, i if i
|
||||
|
||||
# Some blocks occur in the middle of expressions -- when we're expecting
|
||||
# this, remove their trailing newlines.
|
||||
removeMidExpressionNewlines: ->
|
||||
@scanTokens (token, i) ->
|
||||
return 1 unless include(EXPRESSION_CLOSE, @tag(i + 1)) and token[0] is 'TERMINATOR'
|
||||
@tokens.splice i, 1
|
||||
return 0
|
||||
@scanTokens (token, i, tokens) ->
|
||||
return 1 unless token[0] is 'TERMINATOR' and include EXPRESSION_CLOSE, @tag(i + 1)
|
||||
tokens.splice i, 1
|
||||
0
|
||||
|
||||
# The lexer has tagged the opening parenthesis of a method call. Match it with
|
||||
# its paired close. We have the mis-nested outdent case included here for
|
||||
# calls that close on the same line, just before their outdent.
|
||||
closeOpenCalls: ->
|
||||
@scanTokens (token, i) ->
|
||||
if token[0] is 'CALL_START'
|
||||
condition = (token, i) ->
|
||||
(token[0] in [')', 'CALL_END']) or (token[0] is 'OUTDENT' and @tokens[i - 1][0] is ')')
|
||||
(token[0] in [')', 'CALL_END']) or
|
||||
token[0] is 'OUTDENT' and @tag(i - 1) is ')'
|
||||
action = (token, i) ->
|
||||
idx = if token[0] is 'OUTDENT' then i - 1 else i
|
||||
@tokens[idx][0] = 'CALL_END'
|
||||
@detectEnd i + 1, condition, action
|
||||
return 1
|
||||
@tokens[if token[0] is 'OUTDENT' then i - 1 else i][0] = 'CALL_END'
|
||||
@scanTokens (token, i) ->
|
||||
@detectEnd i + 1, condition, action if token[0] is 'CALL_START'
|
||||
1
|
||||
|
||||
# The lexer has tagged the opening parenthesis of an indexing operation call.
|
||||
# Match it with its paired close.
|
||||
closeOpenIndexes: ->
|
||||
@scanTokens (token, i) ->
|
||||
if token[0] is 'INDEX_START'
|
||||
condition = (token, i) -> token[0] in [']', 'INDEX_END']
|
||||
action = (token, i) -> token[0] = 'INDEX_END'
|
||||
@detectEnd i + 1, condition, action
|
||||
return 1
|
||||
@scanTokens (token, i) ->
|
||||
@detectEnd i + 1, condition, action if token[0] is 'INDEX_START'
|
||||
1
|
||||
|
||||
# Object literals may be written with implicit braces, for simple cases.
|
||||
# Insert the missing braces here, so that the parser doesn't have to.
|
||||
addImplicitBraces: ->
|
||||
stack = []
|
||||
@scanTokens (token, i) ->
|
||||
if include EXPRESSION_START, token[0]
|
||||
stack.push(if (token[0] is 'INDENT' and (@tag(i - 1) is '{')) then '{' else token[0])
|
||||
if include EXPRESSION_END, token[0]
|
||||
condition = (token, i) ->
|
||||
return false if 'HERECOMMENT' in [@tag(i + 1), @tag(i - 1)]
|
||||
[one, two, three] = @tokens.slice i + 1, i + 4
|
||||
[tag] = token
|
||||
(tag in ['TERMINATOR', 'OUTDENT']) and not (two?[0] is ':' or one?[0] is '@' and three?[0] is ':') or
|
||||
tag is ',' and (one?[0] not in ['IDENTIFIER', 'STRING', '@', 'TERMINATOR', 'OUTDENT'])
|
||||
action = (token, i) -> @tokens.splice i, 0, ['}', '}', token[2]]
|
||||
@scanTokens (token, i, tokens) ->
|
||||
if include EXPRESSION_START, tag = token[0]
|
||||
stack.push(if tag is 'INDENT' and @tag(i - 1) is '{' then '{' else tag)
|
||||
if include EXPRESSION_END, tag
|
||||
stack.pop()
|
||||
last = stack[stack.length - 1]
|
||||
if token[0] is ':' and (not last or last[0] isnt '{')
|
||||
if tag is ':' and not (last and last[0] is '{')
|
||||
stack.push '{'
|
||||
idx = if @tag(i - 2) is '@' then i - 2 else i - 1
|
||||
idx -= 2 if @tag(idx - 2) is 'HERECOMMENT'
|
||||
tok = ['{', '{', token[2]]
|
||||
tok.generated = yes
|
||||
@tokens.splice idx, 0, tok
|
||||
condition = (token, i) ->
|
||||
[one, two, three] = @tokens.slice(i + 1, i + 4)
|
||||
return false if 'HERECOMMENT' in [@tag(i + 1), @tag(i - 1)]
|
||||
((token[0] in ['TERMINATOR', 'OUTDENT']) and not ((two and two[0] is ':') or (one and one[0] is '@' and three and three[0] is ':'))) or
|
||||
(token[0] is ',' and one and (one[0] not in ['IDENTIFIER', 'STRING', '@', 'TERMINATOR', 'OUTDENT']))
|
||||
action = (token, i) ->
|
||||
@tokens.splice i, 0, ['}', '}', token[2]]
|
||||
tokens.splice idx, 0, tok
|
||||
@detectEnd i + 2, condition, action
|
||||
return 2
|
||||
return 1
|
||||
1
|
||||
|
||||
# Methods may be optionally called without parentheses, for simple cases.
|
||||
# Insert the implicit parentheses here, so that the parser doesn't have to
|
||||
# deal with them.
|
||||
addImplicitParentheses: ->
|
||||
classLine = no
|
||||
@scanTokens (token, i) ->
|
||||
classLine = yes if token[0] is 'CLASS'
|
||||
prev = @tokens[i - 1]
|
||||
next = @tokens[i + 1]
|
||||
idx = 1
|
||||
callObject = not classLine and token[0] is 'INDENT' and next and next.generated and next[0] is '{' and prev and include(IMPLICIT_FUNC, prev[0])
|
||||
idx = 2 if callObject
|
||||
seenSingle = no
|
||||
classLine = no if include(LINEBREAKS, token[0])
|
||||
token.call = yes if prev and not prev.spaced and token[0] is '?'
|
||||
if prev and (prev.spaced and (include(IMPLICIT_FUNC, prev[0]) or prev.call) and include(IMPLICIT_CALL, token[0]) and
|
||||
not (token[0] is 'UNARY' and (@tag(i + 1) in ['IN', 'OF', 'INSTANCEOF']))) or callObject
|
||||
@tokens.splice i, 0, ['CALL_START', '(', token[2]]
|
||||
condition = (token, i) ->
|
||||
return yes if not seenSingle and token.fromThen
|
||||
seenSingle = yes if token[0] in ['IF', 'ELSE', 'UNLESS', '->', '=>']
|
||||
post = @tokens[i + 1]
|
||||
(not token.generated and @tokens[i - 1][0] isnt ',' and include(IMPLICIT_END, token[0]) and
|
||||
not (token[0] is 'INDENT' and (include(IMPLICIT_BLOCK, @tag(i - 1)) or @tag(i - 2) is 'CLASS' or (post and post.generated and post[0] is '{')))) or
|
||||
token[0] is 'PROPERTY_ACCESS' and @tag(i - 1) is 'OUTDENT'
|
||||
action = (token, i) ->
|
||||
idx = if token[0] is 'OUTDENT' then i + 1 else i
|
||||
@tokens.splice idx, 0, ['CALL_END', ')', token[2]]
|
||||
@scanTokens (token, i, tokens) ->
|
||||
tag = token[0]
|
||||
classLine = yes if tag is 'CLASS'
|
||||
prev = tokens[i - 1]
|
||||
next = tokens[i + 1]
|
||||
idx = 1
|
||||
callObject = not classLine and tag is 'INDENT' and
|
||||
next and next.generated and next[0] is '{' and
|
||||
prev and include(IMPLICIT_FUNC, prev[0])
|
||||
idx = 2 if callObject
|
||||
seenSingle = no
|
||||
classLine = no if include LINEBREAKS, tag
|
||||
token.call = yes if prev and not prev.spaced and tag is '?'
|
||||
if callObject or
|
||||
prev and prev.spaced and (prev.call or include(IMPLICIT_FUNC, prev[0])) and include(IMPLICIT_CALL, tag) and
|
||||
not (tag is 'UNARY' and (@tag(i + 1) in ['IN', 'OF', 'INSTANCEOF']))
|
||||
tokens.splice i, 0, ['CALL_START', '(', token[2]]
|
||||
condition = (token, i) ->
|
||||
return yes if not seenSingle and token.fromThen
|
||||
[tag] = token
|
||||
seenSingle = yes if tag in ['IF', 'ELSE', 'UNLESS', '->', '=>']
|
||||
return yes if tag is 'PROPERTY_ACCESS' and @tag(i - 1) is 'OUTDENT'
|
||||
not token.generated and @tag(i - 1) isnt ',' and include(IMPLICIT_END, tag) and
|
||||
(tag isnt 'INDENT' or
|
||||
(@tag(i - 2) isnt 'CLASS' and not include(IMPLICIT_BLOCK, @tag(i - 1)) and
|
||||
not ((post = @tokens[i + 1]) and post.generated and post[0] is '{')))
|
||||
@detectEnd i + idx, condition, action
|
||||
prev[0] = 'FUNC_EXIST' if prev[0] is '?'
|
||||
return 2
|
||||
return 1
|
||||
1
|
||||
|
||||
# Because our grammar is LALR(1), it can't handle some single-line
|
||||
# expressions that lack ending delimiters. The **Rewriter** adds the implicit
|
||||
# blocks, so it doesn't need to. ')' can close a single-line block,
|
||||
# but we need to make sure it's balanced.
|
||||
addImplicitIndentation: ->
|
||||
@scanTokens (token, i) ->
|
||||
if token[0] is 'ELSE' and @tag(i - 1) isnt 'OUTDENT'
|
||||
@tokens.splice i, 0, @indentation(token)...
|
||||
@scanTokens (token, i, tokens) ->
|
||||
[tag] = token
|
||||
if tag is 'ELSE' and @tag(i - 1) isnt 'OUTDENT'
|
||||
tokens.splice i, 0, @indentation(token)...
|
||||
return 2
|
||||
if token[0] is 'CATCH' and
|
||||
(@tag(i + 2) is 'TERMINATOR' or @tag(i + 2) is 'FINALLY')
|
||||
@tokens.splice i + 2, 0, @indentation(token)...
|
||||
if tag is 'CATCH' and (@tag(i + 2) in ['TERMINATOR', 'FINALLY'])
|
||||
tokens.splice i + 2, 0, @indentation(token)...
|
||||
return 4
|
||||
if include(SINGLE_LINERS, token[0]) and @tag(i + 1) isnt 'INDENT' and
|
||||
not (token[0] is 'ELSE' and @tag(i + 1) is 'IF')
|
||||
starter = token[0]
|
||||
if include(SINGLE_LINERS, tag) and @tag(i + 1) isnt 'INDENT' and
|
||||
not (tag is 'ELSE' and @tag(i + 1) is 'IF')
|
||||
starter = tag
|
||||
[indent, outdent] = @indentation token
|
||||
indent.fromThen = true if starter is 'THEN'
|
||||
indent.generated = outdent.generated = true
|
||||
@tokens.splice i + 1, 0, indent
|
||||
tokens.splice i + 1, 0, indent
|
||||
condition = (token, i) ->
|
||||
(include(SINGLE_CLOSERS, token[0]) and token[1] isnt ';') and
|
||||
token[1] isnt ';' and include(SINGLE_CLOSERS, token[0]) and
|
||||
not (token[0] is 'ELSE' and starter not in ['IF', 'THEN'])
|
||||
action = (token, i) ->
|
||||
idx = if @tokens[i - 1][0] is ',' then i - 1 else i
|
||||
@tokens.splice idx, 0, outdent
|
||||
@tokens.splice (if @tag(i - 1) is ',' then i - 1 else i), 0, outdent
|
||||
@detectEnd i + 2, condition, action
|
||||
@tokens.splice i, 1 if token[0] is 'THEN'
|
||||
tokens.splice i, 1 if tag is 'THEN'
|
||||
return 1
|
||||
return 1
|
||||
|
||||
# Tag postfix conditionals as such, so that we can parse them with a
|
||||
# different precedence.
|
||||
tagPostfixConditionals: ->
|
||||
condition = (token, i) -> token[0] in ['TERMINATOR', 'INDENT']
|
||||
@scanTokens (token, i) ->
|
||||
if token[0] in ['IF', 'UNLESS']
|
||||
return 1 unless token[0] in ['IF', 'UNLESS']
|
||||
original = token
|
||||
condition = (token, i) ->
|
||||
token[0] in ['TERMINATOR', 'INDENT']
|
||||
action = (token, i) ->
|
||||
@detectEnd i + 1, condition, (token, i) ->
|
||||
original[0] = 'POST_' + original[0] if token[0] isnt 'INDENT'
|
||||
@detectEnd i + 1, condition, action
|
||||
return 1
|
||||
return 1
|
||||
1
|
||||
|
||||
# Ensure that all listed pairs of tokens are correctly balanced throughout
|
||||
# the course of the token stream.
|
||||
|
@ -230,20 +233,19 @@ exports.Rewriter = class Rewriter
|
|||
levels = {}
|
||||
openLine = {}
|
||||
@scanTokens (token, i) ->
|
||||
for pair in pairs
|
||||
[open, close] = pair
|
||||
levels[open] or= 0
|
||||
if token[0] is open
|
||||
openLine[open] = token[2] if levels[open] == 0
|
||||
[tag] = token
|
||||
for [open, close] in pairs
|
||||
levels[open] |= 0
|
||||
if tag is open
|
||||
openLine[open] = token[2] if levels[open] is 0
|
||||
levels[open] += 1
|
||||
levels[open] -= 1 if token[0] is close
|
||||
throw new Error("too many #{token[1]} on line #{token[2] + 1}") if levels[open] < 0
|
||||
return 1
|
||||
unclosed = key for key, value of levels when value > 0
|
||||
else if tag is close
|
||||
levels[open] -= 1
|
||||
throw Error "too many #{token[1]} on line #{token[2] + 1}" if levels[open] < 0
|
||||
1
|
||||
unclosed = key for all key, value of levels when value > 0
|
||||
if unclosed.length
|
||||
open = unclosed[0]
|
||||
line = openLine[open] + 1
|
||||
throw new Error "unclosed #{open} on line #{line}"
|
||||
throw Error "unclosed #{ open = unclosed[0] } on line #{openLine[open] + 1}"
|
||||
|
||||
# We'd like to support syntax like this:
|
||||
#
|
||||
|
@ -264,61 +266,61 @@ exports.Rewriter = class Rewriter
|
|||
rewriteClosingParens: ->
|
||||
stack = []
|
||||
debt = {}
|
||||
(debt[key] = 0) for key, val of INVERSES
|
||||
@scanTokens (token, i) ->
|
||||
tag = token[0]
|
||||
inv = INVERSES[token[0]]
|
||||
if include EXPRESSION_START, tag
|
||||
(debt[key] = 0) for all key of INVERSES
|
||||
@scanTokens (token, i, tokens) ->
|
||||
if include EXPRESSION_START, tag = token[0]
|
||||
stack.push token
|
||||
return 1
|
||||
else if include EXPRESSION_END, tag
|
||||
if debt[inv] > 0
|
||||
return 1 unless include EXPRESSION_END, tag
|
||||
if debt[inv = INVERSES[tag]] > 0
|
||||
debt[inv] -= 1
|
||||
@tokens.splice i, 1
|
||||
tokens.splice i, 1
|
||||
return 0
|
||||
else
|
||||
match = stack.pop()
|
||||
mtag = match[0]
|
||||
oppos = INVERSES[mtag]
|
||||
return 1 if tag is oppos
|
||||
debt[mtag] += 1
|
||||
val = [oppos, if mtag is 'INDENT' then match[1] else oppos]
|
||||
if @tokens[i + 2]?[0] is mtag
|
||||
@tokens.splice i + 3, 0, val
|
||||
stack.push(match)
|
||||
if @tag(i + 2) is mtag
|
||||
tokens.splice i + 3, 0, val
|
||||
stack.push match
|
||||
else
|
||||
@tokens.splice i, 0, val
|
||||
return 1
|
||||
else
|
||||
return 1
|
||||
tokens.splice i, 0, val
|
||||
1
|
||||
|
||||
# Generate the indentation tokens, based on another token on the same line.
|
||||
indentation: (token) ->
|
||||
[['INDENT', 2, token[2]], ['OUTDENT', 2, token[2]]]
|
||||
|
||||
# Look up a tag by token index.
|
||||
tag: (i) ->
|
||||
@tokens[i] and @tokens[i][0]
|
||||
tag: (i) -> @tokens[i]?[0]
|
||||
|
||||
# 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']]
|
||||
BALANCED_PAIRS = [
|
||||
['(', ')']
|
||||
['[', ']']
|
||||
['{', '}']
|
||||
['INDENT', 'OUTDENT'],
|
||||
['CALL_START', 'CALL_END']
|
||||
['PARAM_START', 'PARAM_END']
|
||||
['INDEX_START', 'INDEX_END']
|
||||
]
|
||||
|
||||
# The inverse mappings of `BALANCED_PAIRS` we're trying to fix up, so we can
|
||||
# look things up from either end.
|
||||
INVERSES = {}
|
||||
for pair in BALANCED_PAIRS
|
||||
INVERSES[pair[0]] = pair[1]
|
||||
INVERSES[pair[1]] = pair[0]
|
||||
|
||||
# The tokens that signal the start of a balanced pair.
|
||||
EXPRESSION_START = pair[0] for pair in BALANCED_PAIRS
|
||||
# The tokens that signal the start/end of a balanced pair.
|
||||
EXPRESSION_START = []
|
||||
EXPRESSION_END = []
|
||||
|
||||
# The tokens that signal the end of a balanced pair.
|
||||
EXPRESSION_END = pair[1] for pair in BALANCED_PAIRS
|
||||
for [left, rite] in BALANCED_PAIRS
|
||||
EXPRESSION_START.push INVERSES[rite] = left
|
||||
EXPRESSION_END .push INVERSES[left] = rite
|
||||
|
||||
# Tokens that indicate the close of a clause of an expression.
|
||||
EXPRESSION_CLOSE = ['CATCH', 'WHEN', 'ELSE', 'FINALLY'].concat EXPRESSION_END
|
||||
|
@ -328,9 +330,9 @@ IMPLICIT_FUNC = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@
|
|||
|
||||
# If preceded by an `IMPLICIT_FUNC`, indicates a function invocation.
|
||||
IMPLICIT_CALL = [
|
||||
'IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'CLASS',
|
||||
'IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'CLASS'
|
||||
'IF', 'UNLESS', 'TRY', 'SWITCH', 'THIS', 'NULL', 'UNARY'
|
||||
'TRUE', 'FALSE', 'YES', 'NO', 'ON', 'OFF',
|
||||
'TRUE', 'FALSE', 'YES', 'NO', 'ON', 'OFF'
|
||||
'@', '->', '=>', '[', '(', '{'
|
||||
]
|
||||
|
||||
|
@ -342,7 +344,7 @@ IMPLICIT_END = ['POST_IF', 'POST_UNLESS', 'FOR', 'WHILE', 'UNTIL', 'LOOP', '
|
|||
|
||||
# Single-line flavors of block expressions that have unclosed endings.
|
||||
# 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']
|
||||
|
||||
# Tokens that end a line.
|
||||
|
|
Loading…
Add table
Reference in a new issue