Skip to content
Browse files

This seems like a cleaner and simpler way to tame the insides of a wh…

…ile loop.
  • Loading branch information...
1 parent b4b6cba commit 590e72fef215c5febb9d7bbf03c46fcc45c80e05 @maxtaco committed Nov 22, 2011
Showing with 757 additions and 76 deletions.
  1. +49 −27 lib/coffee-script/nodes.js
  2. +1 −0 lib/coffee-script/parser.js
  3. +683 −0 lib/coffee-script/tamer.js
  4. +24 −49 src/nodes.coffee
View
76 lib/coffee-script/nodes.js
@@ -43,7 +43,7 @@
if (lvl) o.level = lvl;
node = this.unfoldSoak(o) || this;
node.tab = o.indent;
- if (node.hasTaming && !node.gotCpsSplit && node.isStatement(o)) {
+ if (node.needsCpsRotation() && !node.gotCpsSplit && node.isStatement(o)) {
return node.compileCps(o);
} else if (o.level === LEVEL_TOP || !node.isStatement(o)) {
return node.compileNode(o);
@@ -135,8 +135,8 @@
if (idt == null) idt = '';
if (name == null) name = this.constructor.name;
extras = "";
- if (this.hasTaming) extras += "T";
- if (this.isCpsTranslated) extras += "C";
+ if (this.tameNodeFlag) extras += "T";
+ if (this.cpsNodeFlag) extras += "C";
if (extras.length) extras = " (" + extras + ")";
tree = '\n' + idt + name;
if (this.soak) tree += '?';
@@ -206,22 +206,27 @@
return out;
};
- Base.prototype.walkTaming = function() {
+ Base.prototype.walkAstTame = function() {
var child, _i, _len, _ref2;
- this.hasTaming = false;
_ref2 = this.flattenChildren();
for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
child = _ref2[_i];
- if (child.walkTaming()) this.hasTaming = true;
+ if (child.walkAstTame()) this.tameNodeFlag = true;
}
- return this.hasTaming;
+ return this.tameNodeFlag;
};
- Base.prototype.floodCpsTranslation = function() {
- this.isCpsTranslated = true;
- return this.traverseChildren(false, function(x) {
- return x.floodCpsTranslation();
- });
+ Base.prototype.walkAstCps = function(flood) {
+ var child, _i, _len, _ref2;
+ if (this.isLoop()) flood = true;
+ if (this.isAwait()) flood = false;
+ this.cpsNodeFlag = flood;
+ _ref2 = this.flattenChildren();
+ for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
+ child = _ref2[_i];
+ if (child.walkAstCps(flood)) this.cpsNodeFlag = true;
+ }
+ return this.cpsNodeFlag;
};
Base.prototype.children = [];
@@ -238,6 +243,10 @@
return this;
};
+ Base.prototype.needsCpsRotation = function() {
+ return this.tameNodeFlag || this.cpsNodeFlag;
+ };
+
Base.prototype.tameNestContinuationBlock = function(b) {
return this.tameContinuationBlock = b;
};
@@ -258,9 +267,13 @@
Base.prototype.isTamedFunc = NO;
- Base.prototype.hasTaming = false;
+ Base.prototype.isLoop = NO;
- Base.prototype.isCpsTranslated = false;
+ Base.prototype.isAwait = NO;
+
+ Base.prototype.cpsNodeFlag = false;
+
+ Base.prototype.tameNodeFlag = false;
Base.prototype.gotCpsSplit = false;
@@ -446,29 +459,28 @@
var child, e, i, pivot, rest, _i, _j, _len, _len2, _ref2;
pivot = null;
child = null;
- if (this.hasTaming) {
+ if (this.needsCpsRotation()) {
i = 0;
_ref2 = this.expressions;
for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
e = _ref2[_i];
- if (e.hasTaming) {
+ if (e.needsCpsRotation()) {
pivot = e;
break;
}
i++;
}
}
if (pivot) {
- console.log("found pvito");
- pivot.floodCpsTranslation();
rest = this.expressions.slice(i + 1);
this.expressions = this.expressions.slice(0, i + 1);
if (rest.length) {
child = new Block(rest);
pivot.tameNestContinuationBlock(child);
for (_j = 0, _len2 = rest.length; _j < _len2; _j++) {
e = rest[_j];
- if (e.hasTaming) child.hasTaming = true;
+ if (e.tameNodeFlag) child.tameNodeFlag = true;
+ if (e.cpsNodeFlag) child.cpsNodeFlag = true;
}
child.cpsRotate();
}
@@ -488,7 +500,8 @@
};
Block.prototype.tameTransform = function() {
- this.walkTaming();
+ this.walkAstTame();
+ this.walkAstCps(false);
return this.cpsRotate();
};
@@ -1653,7 +1666,7 @@
code = 'function';
if (this.ctor) code += ' ' + this.name;
code += '(' + vars.join(', ') + ') {';
- if (this.hasTaming) code += ' /* TAMED */ ';
+ if (this.tameNodeFlag) code += ' /* TAMED */ ';
if (!this.body.isEmpty()) {
code += "\n" + (this.body.compileWithDeclarations(o)) + "\n" + this.tab;
}
@@ -1672,8 +1685,13 @@
}
};
- Code.prototype.walkTaming = function() {
- this.hasTaming = Code.__super__.walkTaming.call(this);
+ Code.prototype.walkAstTame = function() {
+ if (Code.__super__.walkAstTame.call(this)) this.tameNodeFlag = true;
+ return false;
+ };
+
+ Code.prototype.walkAstCps = function(flood) {
+ this.cpsNodeFlag = Code.__super__.walkAstCps.call(this, false);
return false;
};
@@ -1799,6 +1817,8 @@
While.prototype.isStatement = YES;
+ While.prototype.isLoop = YES;
+
While.prototype.makeReturn = function(res) {
if (res) {
return While.__super__.makeReturn.apply(this, arguments);
@@ -2111,16 +2131,18 @@
Await.prototype.isStatement = YES;
+ Await.prototype.isAwait = YES;
+
Await.prototype.makeReturn = THIS;
Await.prototype.compileNode = function(o) {
o.indent += TAB;
return this.body.compile(o);
};
- Await.prototype.walkTaming = function() {
- Await.__super__.walkTaming.call(this);
- return this.hasTaming = true;
+ Await.prototype.walkAstTame = function() {
+ Await.__super__.walkAstTame.call(this);
+ return this.tameNodeFlag = true;
};
return Await;
@@ -2528,7 +2550,7 @@
};
If.prototype.compileNode = function(o) {
- if (this.isStatement(o || this.isCpsTranslated)) {
+ if (this.isStatement(o || this.needsCpsRotation())) {
return this.compileStatement(o);
} else {
return this.compileExpression(o);
View
1 lib/coffee-script/parser.js
@@ -1,5 +1,6 @@
/* Jison generated parser */
var parser = (function(){
+undefined
var parser = {trace: function trace() { },
yy: {},
<<<<<<< HEAD
View
683 lib/coffee-script/tamer.js
@@ -0,0 +1,683 @@
+(function() {
+ var AstTamer, BOOL, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_ALIAS_MAP, COFFEE_KEYWORDS, COMMENT, COMPARE, COMPOUND_ASSIGN, HEREDOC, HEREDOC_ILLEGAL, HEREDOC_INDENT, HEREGEX, HEREGEX_OMIT, IDENTIFIER, INDEXABLE, JSTOKEN, JS_FORBIDDEN, JS_KEYWORDS, LINE_BREAK, LINE_CONTINUER, LOGIC, MATH, MULTILINER, MULTI_DENT, NOT_REGEX, NOT_SPACED_REGEX, NUMBER, OPERATOR, REGEX, RELATION, RESERVED, SHIFT, SIMPLESTR, TRAILING_SPACES, UNARY, WHITESPACE, key;
+ var __hasProp = Object.prototype.hasOwnProperty, __indexOf = Array.prototype.indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (__hasProp.call(this, i) && this[i] === item) return i; } return -1; };
+
+ exports.AstTamer = AstTamer = (function() {
+
+ function AstTamer() {}
+
+ AstTamer.prototype.tokenize = function(code, opts) {
+ var i, tag;
+ if (opts == null) opts = {};
+ if (WHITESPACE.test(code)) code = "\n" + code;
+ code = code.replace(/\r/g, '').replace(TRAILING_SPACES, '');
+ this.code = code;
+ this.line = opts.line || 0;
+ this.indent = 0;
+ this.indebt = 0;
+ this.outdebt = 0;
+ this.indents = [];
+ this.ends = [];
+ this.tokens = [];
+ i = 0;
+ while (this.chunk = code.slice(i)) {
+ i += this.identifierToken() || this.commentToken() || this.whitespaceToken() || this.lineToken() || this.heredocToken() || this.stringToken() || this.numberToken() || this.regexToken() || this.jsToken() || this.literalToken();
+ }
+ this.closeIndentation();
+ if (tag = this.ends.pop()) this.error("missing " + tag);
+ if (opts.rewrite === false) return this.tokens;
+ return (new Rewriter).rewrite(this.tokens);
+ };
+
+ AstTamer.prototype.identifierToken = function() {
+ var colon, forcedIdentifier, id, input, match, prev, tag, _ref, _ref2;
+ if (!(match = IDENTIFIER.exec(this.chunk))) return 0;
+ input = match[0], id = match[1], colon = match[2];
+ if (id === 'own' && this.tag() === 'FOR') {
+ this.token('OWN', id);
+ return id.length;
+ }
+ forcedIdentifier = colon || (prev = last(this.tokens)) && (((_ref = prev[0]) === '.' || _ref === '?.' || _ref === '::') || !prev.spaced && prev[0] === '@');
+ tag = 'IDENTIFIER';
+ if (!forcedIdentifier && (__indexOf.call(JS_KEYWORDS, id) >= 0 || __indexOf.call(COFFEE_KEYWORDS, id) >= 0)) {
+ tag = id.toUpperCase();
+ if (tag === 'WHEN' && (_ref2 = this.tag(), __indexOf.call(LINE_BREAK, _ref2) >= 0)) {
+ tag = 'LEADING_WHEN';
+ } else if (tag === 'FOR') {
+ this.seenFor = true;
+ } else if (tag === 'UNLESS') {
+ tag = 'IF';
+ } else if (__indexOf.call(UNARY, tag) >= 0) {
+ tag = 'UNARY';
+ } else if (__indexOf.call(RELATION, tag) >= 0) {
+ if (tag !== 'INSTANCEOF' && this.seenFor) {
+ tag = 'FOR' + tag;
+ this.seenFor = false;
+ } else {
+ tag = 'RELATION';
+ if (this.value() === '!') {
+ this.tokens.pop();
+ id = '!' + id;
+ }
+ }
+ }
+ }
+ if (__indexOf.call(['eval', 'arguments'].concat(JS_FORBIDDEN), id) >= 0) {
+ if (forcedIdentifier) {
+ tag = 'IDENTIFIER';
+ id = new String(id);
+ id.reserved = true;
+ } else if (__indexOf.call(RESERVED, id) >= 0) {
+ this.error("reserved word \"" + word + "\"");
+ }
+ }
+ if (!forcedIdentifier) {
+ if (__indexOf.call(COFFEE_ALIASES, id) >= 0) id = COFFEE_ALIAS_MAP[id];
+ tag = (function() {
+ switch (id) {
+ case '!':
+ return 'UNARY';
+ case '==':
+ case '!=':
+ return 'COMPARE';
+ case '&&':
+ case '||':
+ return 'LOGIC';
+ case 'true':
+ case 'false':
+ case 'null':
+ case 'undefined':
+ return 'BOOL';
+ case 'break':
+ case 'continue':
+ case 'debugger':
+ return 'STATEMENT';
+ default:
+ return tag;
+ }
+ })();
+ }
+ this.token(tag, id);
+ if (colon) this.token(':', ':');
+ return input.length;
+ };
+
+ AstTamer.prototype.numberToken = function() {
+ var binaryLiteral, lexedLength, match, number;
+ if (!(match = NUMBER.exec(this.chunk))) return 0;
+ number = match[0];
+ lexedLength = number.length;
+ if (binaryLiteral = /0b([01]+)/.exec(number)) {
+ number = (parseInt(binaryLiteral[1], 2)).toString();
+ }
+ this.token('NUMBER', number);
+ return lexedLength;
+ };
+
+ AstTamer.prototype.stringToken = function() {
+ var match, string;
+ switch (this.chunk.charAt(0)) {
+ case "'":
+ if (!(match = SIMPLESTR.exec(this.chunk))) return 0;
+ this.token('STRING', (string = match[0]).replace(MULTILINER, '\\\n'));
+ break;
+ case '"':
+ if (!(string = this.balancedString(this.chunk, '"'))) return 0;
+ if (0 < string.indexOf('#{', 1)) {
+ this.interpolateString(string.slice(1, -1));
+ } else {
+ this.token('STRING', this.escapeLines(string));
+ }
+ break;
+ default:
+ return 0;
+ }
+ this.line += count(string, '\n');
+ return string.length;
+ };
+
+ AstTamer.prototype.heredocToken = function() {
+ var doc, heredoc, match, quote;
+ if (!(match = HEREDOC.exec(this.chunk))) return 0;
+ heredoc = match[0];
+ quote = heredoc.charAt(0);
+ doc = this.sanitizeHeredoc(match[2], {
+ quote: quote,
+ indent: null
+ });
+ if (quote === '"' && 0 <= doc.indexOf('#{')) {
+ this.interpolateString(doc, {
+ heredoc: true
+ });
+ } else {
+ this.token('STRING', this.makeString(doc, quote, true));
+ }
+ this.line += count(heredoc, '\n');
+ return heredoc.length;
+ };
+
+ AstTamer.prototype.commentToken = function() {
+ var comment, here, match;
+ if (!(match = this.chunk.match(COMMENT))) return 0;
+ comment = match[0], here = match[1];
+ if (here) {
+ this.token('HERECOMMENT', this.sanitizeHeredoc(here, {
+ herecomment: true,
+ indent: Array(this.indent + 1).join(' ')
+ }));
+ this.token('TERMINATOR', '\n');
+ }
+ this.line += count(comment, '\n');
+ return comment.length;
+ };
+
+ AstTamer.prototype.jsToken = function() {
+ var match, script;
+ if (!(this.chunk.charAt(0) === '`' && (match = JSTOKEN.exec(this.chunk)))) {
+ return 0;
+ }
+ this.token('JS', (script = match[0]).slice(1, -1));
+ return script.length;
+ };
+
+ AstTamer.prototype.regexToken = function() {
+ var flags, length, match, prev, regex, _ref, _ref2;
+ if (this.chunk.charAt(0) !== '/') return 0;
+ if (match = HEREGEX.exec(this.chunk)) {
+ length = this.heregexToken(match);
+ this.line += count(match[0], '\n');
+ return length;
+ }
+ prev = last(this.tokens);
+ if (prev && (_ref = prev[0], __indexOf.call((prev.spaced ? NOT_REGEX : NOT_SPACED_REGEX), _ref) >= 0)) {
+ return 0;
+ }
+ if (!(match = REGEX.exec(this.chunk))) return 0;
+ _ref2 = match, match = _ref2[0], regex = _ref2[1], flags = _ref2[2];
+ if (regex.slice(0, 2) === '/*') {
+ this.error('regular expressions cannot begin with `*`');
+ }
+ if (regex === '//') regex = '/(?:)/';
+ this.token('REGEX', "" + regex + flags);
+ return match.length;
+ };
+
+ AstTamer.prototype.heregexToken = function(match) {
+ var body, flags, heregex, re, tag, tokens, value, _i, _len, _ref, _ref2, _ref3, _ref4;
+ heregex = match[0], body = match[1], flags = match[2];
+ if (0 > body.indexOf('#{')) {
+ re = body.replace(HEREGEX_OMIT, '').replace(/\//g, '\\/');
+ if (re.match(/^\*/)) {
+ this.error('regular expressions cannot begin with `*`');
+ }
+ this.token('REGEX', "/" + (re || '(?:)') + "/" + flags);
+ return heregex.length;
+ }
+ this.token('IDENTIFIER', 'RegExp');
+ this.tokens.push(['CALL_START', '(']);
+ tokens = [];
+ _ref = this.interpolateString(body, {
+ regex: true
+ });
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ _ref2 = _ref[_i], tag = _ref2[0], value = _ref2[1];
+ if (tag === 'TOKENS') {
+ tokens.push.apply(tokens, value);
+ } else {
+ if (!(value = value.replace(HEREGEX_OMIT, ''))) continue;
+ value = value.replace(/\\/g, '\\\\');
+ tokens.push(['STRING', this.makeString(value, '"', true)]);
+ }
+ tokens.push(['+', '+']);
+ }
+ tokens.pop();
+ if (((_ref3 = tokens[0]) != null ? _ref3[0] : void 0) !== 'STRING') {
+ this.tokens.push(['STRING', '""'], ['+', '+']);
+ }
+ (_ref4 = this.tokens).push.apply(_ref4, tokens);
+ if (flags) this.tokens.push([',', ','], ['STRING', '"' + flags + '"']);
+ this.token(')', ')');
+ return heregex.length;
+ };
+
+ AstTamer.prototype.lineToken = function() {
+ var diff, indent, match, noNewlines, prev, size;
+ if (!(match = MULTI_DENT.exec(this.chunk))) return 0;
+ indent = match[0];
+ this.line += count(indent, '\n');
+ this.seenFor = false;
+ prev = last(this.tokens, 1);
+ size = indent.length - 1 - indent.lastIndexOf('\n');
+ noNewlines = this.unfinished();
+ if (size - this.indebt === this.indent) {
+ if (noNewlines) {
+ this.suppressNewlines();
+ } else {
+ this.newlineToken();
+ }
+ return indent.length;
+ }
+ if (size > this.indent) {
+ if (noNewlines) {
+ this.indebt = size - this.indent;
+ this.suppressNewlines();
+ return indent.length;
+ }
+ diff = size - this.indent + this.outdebt;
+ this.token('INDENT', diff);
+ this.indents.push(diff);
+ this.ends.push('OUTDENT');
+ this.outdebt = this.indebt = 0;
+ } else {
+ this.indebt = 0;
+ this.outdentToken(this.indent - size, noNewlines);
+ }
+ this.indent = size;
+ return indent.length;
+ };
+
+ AstTamer.prototype.outdentToken = function(moveOut, noNewlines) {
+ var dent, len;
+ while (moveOut > 0) {
+ len = this.indents.length - 1;
+ if (this.indents[len] === void 0) {
+ moveOut = 0;
+ } else if (this.indents[len] === this.outdebt) {
+ moveOut -= this.outdebt;
+ this.outdebt = 0;
+ } else if (this.indents[len] < this.outdebt) {
+ this.outdebt -= this.indents[len];
+ moveOut -= this.indents[len];
+ } else {
+ dent = this.indents.pop() - this.outdebt;
+ moveOut -= dent;
+ this.outdebt = 0;
+ this.pair('OUTDENT');
+ this.token('OUTDENT', dent);
+ }
+ }
+ if (dent) this.outdebt -= moveOut;
+ while (this.value() === ';') {
+ this.tokens.pop();
+ }
+ if (!(this.tag() === 'TERMINATOR' || noNewlines)) {
+ this.token('TERMINATOR', '\n');
+ }
+ return this;
+ };
+
+ AstTamer.prototype.whitespaceToken = function() {
+ var match, nline, prev;
+ if (!((match = WHITESPACE.exec(this.chunk)) || (nline = this.chunk.charAt(0) === '\n'))) {
+ return 0;
+ }
+ prev = last(this.tokens);
+ if (prev) prev[match ? 'spaced' : 'newLine'] = true;
+ if (match) {
+ return match[0].length;
+ } else {
+ return 0;
+ }
+ };
+
+ AstTamer.prototype.newlineToken = function() {
+ while (this.value() === ';') {
+ this.tokens.pop();
+ }
+ if (this.tag() !== 'TERMINATOR') this.token('TERMINATOR', '\n');
+ return this;
+ };
+
+ AstTamer.prototype.suppressNewlines = function() {
+ if (this.value() === '\\') this.tokens.pop();
+ return this;
+ };
+
+ AstTamer.prototype.literalToken = function() {
+ var match, prev, tag, value, _ref, _ref2, _ref3, _ref4;
+ if (match = OPERATOR.exec(this.chunk)) {
+ value = match[0];
+ if (CODE.test(value)) this.tagParameters();
+ } else {
+ value = this.chunk.charAt(0);
+ }
+ tag = value;
+ prev = last(this.tokens);
+ if (value === '=' && prev) {
+ if (!prev[1].reserved && (_ref = prev[1], __indexOf.call(JS_FORBIDDEN, _ref) >= 0)) {
+ this.error("reserved word \"" + (this.value()) + "\" can't be assigned");
+ }
+ if ((_ref2 = prev[1]) === '||' || _ref2 === '&&') {
+ prev[0] = 'COMPOUND_ASSIGN';
+ prev[1] += '=';
+ return value.length;
+ }
+ }
+ if (value === ';') {
+ this.seenFor = false;
+ tag = 'TERMINATOR';
+ } else if (__indexOf.call(MATH, value) >= 0) {
+ tag = 'MATH';
+ } else if (__indexOf.call(COMPARE, value) >= 0) {
+ tag = 'COMPARE';
+ } else if (__indexOf.call(COMPOUND_ASSIGN, value) >= 0) {
+ tag = 'COMPOUND_ASSIGN';
+ } else if (__indexOf.call(UNARY, value) >= 0) {
+ tag = 'UNARY';
+ } else if (__indexOf.call(SHIFT, value) >= 0) {
+ tag = 'SHIFT';
+ } else if (__indexOf.call(LOGIC, value) >= 0 || value === '?' && (prev != null ? prev.spaced : void 0)) {
+ tag = 'LOGIC';
+ } else if (prev && !prev.spaced) {
+ if (value === '(' && (_ref3 = prev[0], __indexOf.call(CALLABLE, _ref3) >= 0)) {
+ if (prev[0] === '?') prev[0] = 'FUNC_EXIST';
+ tag = 'CALL_START';
+ } else if (value === '[' && (_ref4 = prev[0], __indexOf.call(INDEXABLE, _ref4) >= 0)) {
+ tag = 'INDEX_START';
+ switch (prev[0]) {
+ case '?':
+ prev[0] = 'INDEX_SOAK';
+ }
+ }
+ }
+ switch (value) {
+ case '(':
+ case '{':
+ case '[':
+ this.ends.push(INVERSES[value]);
+ break;
+ case ')':
+ case '}':
+ case ']':
+ this.pair(value);
+ }
+ this.token(tag, value);
+ return value.length;
+ };
+
+ AstTamer.prototype.sanitizeHeredoc = function(doc, options) {
+ var attempt, herecomment, indent, match, _ref;
+ indent = options.indent, herecomment = options.herecomment;
+ if (herecomment) {
+ if (HEREDOC_ILLEGAL.test(doc)) {
+ this.error("block comment cannot contain \"*/\", starting");
+ }
+ if (doc.indexOf('\n') <= 0) return doc;
+ } else {
+ while (match = HEREDOC_INDENT.exec(doc)) {
+ attempt = match[1];
+ if (indent === null || (0 < (_ref = attempt.length) && _ref < indent.length)) {
+ indent = attempt;
+ }
+ }
+ }
+ if (indent) doc = doc.replace(RegExp("\\n" + indent, "g"), '\n');
+ if (!herecomment) doc = doc.replace(/^\n/, '');
+ return doc;
+ };
+
+ AstTamer.prototype.tagParameters = function() {
+ var i, stack, tok, tokens;
+ if (this.tag() !== ')') return this;
+ stack = [];
+ tokens = this.tokens;
+ i = tokens.length;
+ tokens[--i][0] = 'PARAM_END';
+ while (tok = tokens[--i]) {
+ switch (tok[0]) {
+ case ')':
+ stack.push(tok);
+ break;
+ case '(':
+ case 'CALL_START':
+ if (stack.length) {
+ stack.pop();
+ } else if (tok[0] === '(') {
+ tok[0] = 'PARAM_START';
+ return this;
+ } else {
+ return this;
+ }
+ }
+ }
+ return this;
+ };
+
+ AstTamer.prototype.closeIndentation = function() {
+ return this.outdentToken(this.indent);
+ };
+
+ AstTamer.prototype.balancedString = function(str, end) {
+ var i, letter, match, prev, stack, _ref;
+ stack = [end];
+ for (i = 1, _ref = str.length; 1 <= _ref ? i < _ref : i > _ref; 1 <= _ref ? i++ : i--) {
+ switch (letter = str.charAt(i)) {
+ case '\\':
+ i++;
+ continue;
+ case end:
+ stack.pop();
+ if (!stack.length) return str.slice(0, i + 1);
+ end = stack[stack.length - 1];
+ continue;
+ }
+ if (end === '}' && (letter === '"' || letter === "'")) {
+ stack.push(end = letter);
+ } else if (end === '}' && letter === '/' && (match = HEREGEX.exec(str.slice(i)) || REGEX.exec(str.slice(i)))) {
+ i += match[0].length - 1;
+ } else if (end === '}' && letter === '{') {
+ stack.push(end = '}');
+ } else if (end === '"' && prev === '#' && letter === '{') {
+ stack.push(end = '}');
+ }
+ prev = letter;
+ }
+ return this.error("missing " + (stack.pop()) + ", starting");
+ };
+
+ AstTamer.prototype.interpolateString = function(str, options) {
+ var expr, heredoc, i, inner, interpolated, len, letter, nested, pi, regex, tag, tokens, value, _len, _ref, _ref2, _ref3;
+ if (options == null) options = {};
+ heredoc = options.heredoc, regex = options.regex;
+ tokens = [];
+ pi = 0;
+ i = -1;
+ while (letter = str.charAt(i += 1)) {
+ if (letter === '\\') {
+ i += 1;
+ continue;
+ }
+ if (!(letter === '#' && str.charAt(i + 1) === '{' && (expr = this.balancedString(str.slice(i + 1), '}')))) {
+ continue;
+ }
+ if (pi < i) tokens.push(['NEOSTRING', str.slice(pi, i)]);
+ inner = expr.slice(1, -1);
+ if (inner.length) {
+ nested = new Lexer().tokenize(inner, {
+ line: this.line,
+ rewrite: false
+ });
+ nested.pop();
+ if (((_ref = nested[0]) != null ? _ref[0] : void 0) === 'TERMINATOR') {
+ nested.shift();
+ }
+ if (len = nested.length) {
+ if (len > 1) {
+ nested.unshift(['(', '(', this.line]);
+ nested.push([')', ')', this.line]);
+ }
+ tokens.push(['TOKENS', nested]);
+ }
+ }
+ i += expr.length;
+ pi = i + 1;
+ }
+ if ((i > pi && pi < str.length)) tokens.push(['NEOSTRING', str.slice(pi)]);
+ if (regex) return tokens;
+ if (!tokens.length) return this.token('STRING', '""');
+ if (tokens[0][0] !== 'NEOSTRING') tokens.unshift(['', '']);
+ if (interpolated = tokens.length > 1) this.token('(', '(');
+ for (i = 0, _len = tokens.length; i < _len; i++) {
+ _ref2 = tokens[i], tag = _ref2[0], value = _ref2[1];
+ if (i) this.token('+', '+');
+ if (tag === 'TOKENS') {
+ (_ref3 = this.tokens).push.apply(_ref3, value);
+ } else {
+ this.token('STRING', this.makeString(value, '"', heredoc));
+ }
+ }
+ if (interpolated) this.token(')', ')');
+ return tokens;
+ };
+
+ AstTamer.prototype.pair = function(tag) {
+ var size, wanted;
+ if (tag !== (wanted = last(this.ends))) {
+ if ('OUTDENT' !== wanted) this.error("unmatched " + tag);
+ this.indent -= size = last(this.indents);
+ this.outdentToken(size, true);
+ return this.pair(tag);
+ }
+ return this.ends.pop();
+ };
+
+ AstTamer.prototype.token = function(tag, value) {
+ return this.tokens.push([tag, value, this.line]);
+ };
+
+ AstTamer.prototype.tag = function(index, tag) {
+ var tok;
+ return (tok = last(this.tokens, index)) && (tag ? tok[0] = tag : tok[0]);
+ };
+
+ AstTamer.prototype.value = function(index, val) {
+ var tok;
+ return (tok = last(this.tokens, index)) && (val ? tok[1] = val : tok[1]);
+ };
+
+ AstTamer.prototype.unfinished = function() {
+ var _ref;
+ return LINE_CONTINUER.test(this.chunk) || ((_ref = this.tag()) === '\\' || _ref === '.' || _ref === '?.' || _ref === 'UNARY' || _ref === 'MATH' || _ref === '+' || _ref === '-' || _ref === 'SHIFT' || _ref === 'RELATION' || _ref === 'COMPARE' || _ref === 'LOGIC' || _ref === 'COMPOUND_ASSIGN' || _ref === 'THROW' || _ref === 'EXTENDS');
+ };
+
+ AstTamer.prototype.escapeLines = function(str, heredoc) {
+ return str.replace(MULTILINER, heredoc ? '\\n' : '');
+ };
+
+ AstTamer.prototype.makeString = function(body, quote, heredoc) {
+ if (!body) return quote + quote;
+ body = body.replace(/\\([\s\S])/g, function(match, contents) {
+ if (contents === '\n' || contents === quote) {
+ return contents;
+ } else {
+ return match;
+ }
+ });
+ body = body.replace(RegExp("" + quote, "g"), '\\$&');
+ return quote + this.escapeLines(body, heredoc) + quote;
+ };
+
+ AstTamer.prototype.error = function(message) {
+ throw SyntaxError("" + message + " on line " + (this.line + 1));
+ };
+
+ return AstTamer;
+
+ })();
+
+ JS_KEYWORDS = ['true', 'false', 'null', 'this', 'new', 'delete', 'typeof', 'in', 'instanceof', 'return', 'throw', 'break', 'continue', 'debugger', 'if', 'else', 'switch', 'for', 'while', 'do', 'try', 'catch', 'finally', 'class', 'extends', 'super'];
+
+ COFFEE_KEYWORDS = ['undefined', 'then', 'unless', 'until', 'loop', 'of', 'by', 'when'];
+
+ COFFEE_ALIAS_MAP = {
+ and: '&&',
+ or: '||',
+ is: '==',
+ isnt: '!=',
+ not: '!',
+ yes: 'true',
+ no: 'false',
+ on: 'true',
+ off: 'false'
+ };
+
+ COFFEE_ALIASES = (function() {
+ var _results;
+ _results = [];
+ for (key in COFFEE_ALIAS_MAP) {
+ _results.push(key);
+ }
+ return _results;
+ })();
+
+ COFFEE_KEYWORDS = COFFEE_KEYWORDS.concat(COFFEE_ALIASES);
+
+ RESERVED = ['case', 'default', 'function', 'var', 'void', 'with', 'const', 'let', 'enum', 'export', 'import', 'native', '__hasProp', '__extends', '__slice', '__bind', '__indexOf'];
+
+ JS_FORBIDDEN = JS_KEYWORDS.concat(RESERVED);
+
+ exports.RESERVED = RESERVED.concat(JS_KEYWORDS).concat(COFFEE_KEYWORDS);
+
+ IDENTIFIER = /^([$A-Za-z_\x7f-\uffff][$\w\x7f-\uffff]*)([^\n\S]*:(?!:))?/;
+
+ NUMBER = /^0x[\da-f]+|^0b[01]+|^\d*\.?\d+(?:e[+-]?\d+)?/i;
+
+ HEREDOC = /^("""|''')([\s\S]*?)(?:\n[^\n\S]*)?\1/;
+
+ OPERATOR = /^(?:[-=]>|[-+*\/%<>&|^!?=]=|>>>=?|([-+:])\1|([&|<>])\2=?|\?\.|\.{2,3})/;
+
+ WHITESPACE = /^[^\n\S]+/;
+
+ COMMENT = /^###([^#][\s\S]*?)(?:###[^\n\S]*|(?:###)?$)|^(?:\s*#(?!##[^#]).*)+/;
+
+ CODE = /^[-=]>/;
+
+ MULTI_DENT = /^(?:\n[^\n\S]*)+/;
+
+ SIMPLESTR = /^'[^\\']*(?:\\.[^\\']*)*'/;
+
+ JSTOKEN = /^`[^\\`]*(?:\\.[^\\`]*)*`/;
+
+ REGEX = /^(\/(?![\s=])[^[\/\n\\]*(?:(?:\\[\s\S]|\[[^\]\n\\]*(?:\\[\s\S][^\]\n\\]*)*])[^[\/\n\\]*)*\/)([imgy]{0,4})(?!\w)/;
+
+ HEREGEX = /^\/{3}([\s\S]+?)\/{3}([imgy]{0,4})(?!\w)/;
+
+ HEREGEX_OMIT = /\s+(?:#.*)?/g;
+
+ MULTILINER = /\n/g;
+
+ HEREDOC_INDENT = /\n+([^\n\S]*)/g;
+
+ HEREDOC_ILLEGAL = /\*\//;
+
+ LINE_CONTINUER = /^\s*(?:,|\??\.(?![.\d])|::)/;
+
+ TRAILING_SPACES = /\s+$/;
+
+ COMPOUND_ASSIGN = ['-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?=', '<<=', '>>=', '>>>=', '&=', '^=', '|='];
+
+ UNARY = ['!', '~', 'NEW', 'TYPEOF', 'DELETE', 'DO'];
+
+ LOGIC = ['&&', '||', '&', '|', '^'];
+
+ SHIFT = ['<<', '>>', '>>>'];
+
+ COMPARE = ['==', '!=', '<', '>', '<=', '>='];
+
+ MATH = ['*', '/', '%'];
+
+ RELATION = ['IN', 'OF', 'INSTANCEOF'];
+
+ BOOL = ['TRUE', 'FALSE', 'NULL', 'UNDEFINED'];
+
+ NOT_REGEX = ['NUMBER', 'REGEX', 'BOOL', '++', '--', ']'];
+
+ NOT_SPACED_REGEX = NOT_REGEX.concat(')', '}', 'THIS', 'IDENTIFIER', 'STRING');
+
+ CALLABLE = ['IDENTIFIER', 'STRING', 'REGEX', ')', ']', '}', '?', '::', '@', 'THIS', 'SUPER'];
+
+ INDEXABLE = CALLABLE.concat('NUMBER', 'BOOL');
+
+ LINE_BREAK = ['INDENT', 'OUTDENT', 'TERMINATOR'];
+
+}).call(this);
View
73 src/nodes.coffee
@@ -21,14 +21,6 @@ NEGATE = -> @negated = not @negated; this
CALL_CONTINUATION = -> new Call(new Literal tame.const.k, [])
-
-TAME_NODE =
- NONE : 0x0
- BLOCK : 0x1
- CODE : 0x2
- FULL : 0x3
- AWAIT : 0x4
-
#### Base
# The **Base** is the abstract base class for all nodes in the syntax tree.
@@ -131,12 +123,10 @@ exports.Base = class Base
# This is what `coffee --nodes` prints out.
toString: (idt = '', name = @constructor.name) ->
extras = ""
- if @tameNodeFlag == TAME_NODE.FULL || @tameNodeFlag == TAME_NODE.AWAIT
+ if @tameNodeFlag
extras += "T"
- else if @tameNodeFlag == TAME_NODE.BLOCK || @tameNodeFlag == TAME_NODE.CODE
- extras += "t"
if @cpsNodeFlag
- extras += "c"
+ extras += "C"
if extras.length
extras = " (" + extras + ")"
tree = '\n' + idt + name
@@ -185,16 +175,16 @@ exports.Base = class Base
# tamed, and all other nodes who have a child that's tamed
walkAstTame : ->
for child in @flattenChildren()
- @tameNodeFlag = TAME_NODE.FULL if child.walkAstTame()
+ @tameNodeFlag = true if child.walkAstTame()
@tameNodeFlag
- # See if having paths in the AST marked as "tamed" mean that
- # other paths with interesting jumps need to be marked for
- # CPS rotations too.
- walkAstCps : (tame) ->
- tame = true if @tameNodeFlag == TAME_NODE.FULL
+ walkAstCps : (flood) ->
+ flood = true if @isLoop()
+ if @isAwait()
+ flood = false
+ @cpsNodeFlag = flood
for child in @flattenChildren()
- @cpsNodeFlag = true if child.walkAstCps(tame)
+ @cpsNodeFlag = true if child.walkAstCps(flood)
@cpsNodeFlag
# Default implementations of the common node properties and methods. Nodes
@@ -225,9 +215,11 @@ exports.Base = class Base
isAssignable : NO
isControlBreak : NO
isTamedFunc : NO
-
- tameNodeFlag : TAME_NODE.NONE
+ isLoop : NO
+ isAwait : NO
+
cpsNodeFlag : false
+ tameNodeFlag : false
gotCpsSplit : false
unwrap : THIS
@@ -413,8 +405,8 @@ exports.Block = class Block extends Base
# we have to set the taming bit on the new Block
for e in rest
- child.tameNodeFlag = e.tameNodeFlag
- child.cpsNodeFlag = e.cpsNodeFlag
+ child.tameNodeFlag = true if e.tameNodeFlag
+ child.cpsNodeFlag = true if e.cpsNodeFlag
# now recursive apply the transformation to the new child,
# this being especially import in blocks that have multiple
@@ -444,15 +436,9 @@ exports.Block = class Block extends Base
# Perform all steps of the Tame transform
tameTransform : ->
@walkAstTame()
- @walkAstCps()
+ @walkAstCps(false)
@cpsRotate()
- walkAstTame : ->
- f = super()
- # If it's a block, it need not "taint" its brothers in the AST
- f = TAME_NODE.BLOCK if f
- @tameNodeFlag = f
-
#### Literal
# Literals are static values that can be passed through directly into
@@ -479,9 +465,6 @@ exports.Literal = class Literal extends Base
return this if @value is 'break' and not (o?.loop or o?.block)
return this if @value is 'continue' and not o?.loop
- walkAstCps: (tame) ->
- @cpsNodeFlag = tame and @isStatement()
-
compileNode: (o) ->
code = if @isUndefined
if o.level >= LEVEL_ACCESS then '(void 0)' else 'void 0'
@@ -517,10 +500,6 @@ exports.Return = class Return extends Base
compileNode: (o) ->
@tab + "return#{[" #{@expression.compile o, LEVEL_PAREN}" if @expression]};"
- walkAstCps: (tame) ->
- console.log("Return.walkAstCps -> #{tame}")
- @cpsNodeFlag = tame
-
#### Value
# A value, variable or literal or parenthesized, indexed or dotted into,
@@ -1389,11 +1368,11 @@ exports.Code = class Code extends Base
# we are taming as a feature of all of our children. However, if we
# are tamed, it's not the case that our parent is tamed!
walkAstTame : ->
- @tameNodeFlag = TAME_NODE.CODE if super()
- TAME_NODE.NONE
+ @tameNodeFlag = true if super()
+ false
- walkAstCps : (tame) ->
- @cpsNodeFlag = true if super(false)
+ walkAstCps : (flood) ->
+ @cpsNodeFlag = super(false)
false
#### Param
@@ -1511,6 +1490,7 @@ exports.While = class While extends Base
children: ['condition', 'guard', 'body']
isStatement: YES
+ isLoop : YES
makeReturn: (res) ->
if res
@@ -1756,8 +1736,8 @@ exports.Await = class Await extends Base
children: ['body']
isStatement: YES
-
- makeReturn: THIS
+ isAwait : YES
+ makeReturn : THIS
compileNode: (o) ->
o.indent += TAB
@@ -1768,12 +1748,7 @@ exports.Await = class Await extends Base
# to our parent that we are tamed, since we are!
walkAstTame : ->
super()
- @tameNodeFlag = TAME_NODE.AWAIT
- TAME_NODE.FULL
-
- walkAstCps : (tame) ->
- super(false)
- @cpsNodeFlag = false
+ @tameNodeFlag = true
#### Try

0 comments on commit 590e72f

Please sign in to comment.
Something went wrong with that request. Please try again.