Interpolated strings are expressions.

This commit is contained in:
Chris Lloyd 2010-04-04 16:59:44 +10:00
parent 2e744a1c1b
commit 19ed63129e
11 changed files with 87 additions and 77 deletions

View File

@ -57,7 +57,7 @@
return path.exists('Cakefile', function(exists) {
var _a, _b, _c, _d, arg, args;
if (!(exists)) {
throw new Error("Cakefile not found in " + (process.cwd()));
throw new Error(("Cakefile not found in " + (process.cwd())));
}
args = process.argv.slice(2, process.argv.length);
CoffeeScript.run(fs.readFileSync('Cakefile'), {
@ -91,8 +91,8 @@
}
return _b;
}).call(this).join('') : '';
desc = task.description ? "# " + (task.description) : '';
puts("cake " + name + spaces + " " + desc);
desc = task.description ? ("# " + (task.description)) : '';
puts(("cake " + name + spaces + " " + desc));
}}
if (switches.length) {
return puts(oparse.help());
@ -100,7 +100,7 @@
};
// Print an error and exit when attempting to all an undefined task.
no_such_task = function no_such_task(task) {
process.stdio.writeError("No such task: \"" + task + "\"\n");
process.stdio.writeError(("No such task: \"" + task + "\"\n"));
return process.exit(1);
};
})();

View File

@ -34,7 +34,7 @@
return (parser.parse(lexer.tokenize(code))).compile(options);
} catch (err) {
if (options.source) {
err.message = "In " + (options.source) + ", " + (err.message);
err.message = ("In " + (options.source) + ", " + (err.message));
}
throw err;
}

View File

@ -61,7 +61,7 @@
compile = function compile(source) {
return path.exists(source, function(exists) {
if (!(exists)) {
throw new Error("File not found: " + source);
throw new Error(("File not found: " + source));
}
return fs.readFile(source, function(err, code) {
return compile_script(source, code);
@ -221,7 +221,7 @@
};
// Print the `--version` message and exit.
version = function version() {
puts("CoffeeScript version " + (CoffeeScript.VERSION));
puts(("CoffeeScript version " + (CoffeeScript.VERSION)));
return process.exit(0);
};
})();

View File

@ -33,8 +33,8 @@
if (!(action)) {
return [pattern_string, '$$ = $1;', options];
}
action = (match = (action + '').match(unwrap)) ? match[1] : "(" + action + "())";
return [pattern_string, "$$ = " + action + ";", options];
action = (match = (action + '').match(unwrap)) ? match[1] : ("(" + action + "())");
return [pattern_string, ("$$ = " + action + ";"), options];
};
// Grammatical Rules
// -----------------
@ -693,7 +693,7 @@
}
}
if (name === 'Root') {
alt[1] = "return " + (alt[1]);
alt[1] = ("return " + (alt[1]));
}
return alt;
}).call(this));

View File

@ -132,7 +132,7 @@
if (slash) {
return false;
}
throw new Error("SyntaxError: Unterminated " + (levels.pop()[0]) + " starting on line " + (this.line + 1));
throw new Error(("SyntaxError: Unterminated " + (levels.pop()[0]) + " starting on line " + (this.line + 1)));
}
if (!i) {
return false;

View File

@ -185,7 +185,7 @@
}
quote = match[1].substr(0, 1);
doc = this.sanitize_heredoc(match[2] || match[4], quote);
this.interpolate_string("" + quote + doc + quote);
this.interpolate_string(("" + quote + doc + quote));
this.line += count(match[1], "\n");
this.i += match[1].length;
return true;
@ -225,8 +225,8 @@
return '\\' + escaped;
});
this.tokens = this.tokens.concat([['(', '('], ['NEW', 'new'], ['IDENTIFIER', 'RegExp'], ['CALL_START', '(']]);
this.interpolate_string("\"" + str + "\"", true);
this.tokens = this.tokens.concat([[',', ','], ['STRING', "\"" + flags + "\""], [')', ')'], [')', ')']]);
this.interpolate_string(("\"" + str + "\""), true);
this.tokens = this.tokens.concat([[',', ','], ['STRING', ("\"" + flags + "\"")], [')', ')'], [')', ')']]);
} else {
this.token('REGEX', regex);
}
@ -412,7 +412,7 @@
Lexer.prototype.tag_half_assignment = function tag_half_assignment(tag) {
var last;
last = this.tokens.pop();
this.tokens.push(["" + tag + "=", "" + tag + "=", last[2]]);
this.tokens.push([("" + tag + "="), ("" + tag + "="), last[2]]);
return true;
};
// A source of ambiguity in our grammar used to be parameter lists in function
@ -448,12 +448,12 @@
// The error for when you try to use a forbidden word in JavaScript as
// an identifier.
Lexer.prototype.identifier_error = function identifier_error(word) {
throw new Error("SyntaxError: Reserved word \"" + word + "\" on line " + (this.line + 1));
throw new Error(("SyntaxError: Reserved word \"" + word + "\" on line " + (this.line + 1)));
};
// The error for when you try to assign to a reserved word in JavaScript,
// like "function" or "default".
Lexer.prototype.assignment_error = function assignment_error() {
throw new Error("SyntaxError: Reserved word \"" + (this.value()) + "\" on line " + (this.line + 1) + " can't be assigned");
throw new Error(("SyntaxError: Reserved word \"" + (this.value()) + "\" on line " + (this.line + 1) + " can't be assigned"));
};
// Expand variables and expressions inside double-quoted strings using
// [ECMA Harmony's interpolation syntax](http://wiki.ecmascript.org/doku.php?id=strawman:string_interpolation)
@ -464,7 +464,7 @@
// new Lexer, tokenize the interpolated contents, and merge them into the
// token stream.
Lexer.prototype.interpolate_string = function interpolate_string(str, escape_quotes) {
var _a, _b, _c, _d, _e, escaped, expr, group, i, inner, interp, lexer, match, nested, pi, quote, tag, token, tokens, value;
var _a, _b, _c, _d, _e, escaped, expr, group, i, inner, interp, interpolated, lexer, match, nested, pi, quote, tag, token, tokens, value;
if (str.length < 3 || !starts(str, '"')) {
return this.token('STRING', str);
} else {
@ -482,28 +482,28 @@
group = _b[0];
interp = _b[1];
if (starts(interp, '@')) {
interp = "this." + (interp.substring(1));
interp = ("this." + (interp.substring(1)));
}
if (pi < i) {
tokens.push(['STRING', "" + quote + (str.substring(pi, i)) + quote]);
tokens.push(['STRING', ("" + quote + (str.substring(pi, i)) + quote)]);
}
tokens.push(['IDENTIFIER', interp]);
i += group.length - 1;
pi = i + 1;
} else if ((expr = balanced_string(str.substring(i), [['${', '}']]))) {
if (pi < i) {
tokens.push(['STRING', "" + quote + (str.substring(pi, i)) + quote]);
tokens.push(['STRING', ("" + quote + (str.substring(pi, i)) + quote)]);
}
inner = expr.substring(2, expr.length - 1);
if (inner.length) {
nested = lexer.tokenize("(" + inner + ")", {
nested = lexer.tokenize(("(" + inner + ")"), {
rewrite: false,
line: this.line
});
nested.pop();
tokens.push(['TOKENS', nested]);
} else {
tokens.push(['STRING', "" + quote + quote]);
tokens.push(['STRING', ("" + quote + quote)]);
}
i += expr.length - 1;
pi = i + 1;
@ -511,11 +511,15 @@
i += 1;
}
if (pi < i && pi < str.length - 1) {
tokens.push(['STRING', "" + quote + (str.substring(pi, i)) + quote]);
tokens.push(['STRING', ("" + quote + (str.substring(pi, i)) + quote)]);
}
if (!(tokens[0][0] === 'STRING')) {
tokens.unshift(['STRING', '""']);
}
interpolated = tokens.length > 1;
if (interpolated) {
this.token('(', '(');
}
_c = tokens;
for (i = 0, _d = _c.length; i < _d; i++) {
token = _c[i];
@ -526,7 +530,7 @@
this.tokens = this.tokens.concat(value);
} else if (tag === 'STRING' && escape_quotes) {
escaped = value.substring(1, value.length - 1).replace(/"/g, '\\"');
this.token(tag, "\"" + escaped + "\"");
this.token(tag, ("\"" + escaped + "\""));
} else {
this.token(tag, value);
}
@ -534,6 +538,9 @@
this.token('+', '+');
}
}
if (interpolated) {
this.token(')', ')');
}
return tokens;
}
};

View File

@ -284,10 +284,10 @@
var code;
code = this.compile_node(o);
if (o.scope.has_assignments(this)) {
code = "" + (this.tab) + "var " + (o.scope.compiled_assignments()) + ";\n" + code;
code = ("" + (this.tab) + "var " + (o.scope.compiled_assignments()) + ";\n" + code);
}
if (o.scope.has_declarations(this)) {
code = "" + (this.tab) + "var " + (o.scope.compiled_declarations()) + ";\n" + code;
code = ("" + (this.tab) + "var " + (o.scope.compiled_declarations()) + ";\n" + code);
}
return code;
};
@ -433,7 +433,7 @@
props = only ? this.properties.slice(0, this.properties.length - 1) : this.properties;
baseline = this.base.compile(o);
if (this.base instanceof ObjectNode && this.has_properties()) {
baseline = "(" + baseline + ")";
baseline = ("(" + baseline + ")");
}
complete = (this.last = baseline);
_b = props;
@ -444,7 +444,7 @@
soaked = true;
if (this.base instanceof CallNode && prop === props[0]) {
temp = o.scope.free_variable();
complete = "(" + temp + " = " + complete + ")" + this.SOAK + (baseline = temp + prop.compile(o));
complete = ("(" + temp + " = " + complete + ")" + this.SOAK) + (baseline = temp + prop.compile(o));
} else {
complete = complete + this.SOAK + (baseline += prop.compile(o));
}
@ -477,7 +477,7 @@
return this;
};
CommentNode.prototype.compile_node = function compile_node(o) {
return "" + this.tab + "//" + this.lines.join("\n" + this.tab + "//");
return ("" + this.tab + "//") + this.lines.join(("\n" + this.tab + "//"));
};
return CommentNode;
}).call(this);
@ -535,7 +535,7 @@
CallNode.prototype.compile_super = function compile_super(args, o) {
var meth, methname;
methname = o.scope.method.name;
meth = o.scope.method.proto ? "" + (o.scope.method.proto) + ".__superClass__." + methname : "" + (methname) + ".__superClass__.constructor";
meth = o.scope.method.proto ? ("" + (o.scope.method.proto) + ".__superClass__." + methname) : ("" + (methname) + ".__superClass__.constructor");
return "" + (meth) + ".call(this" + (args.length ? ', ' : '') + args + ")";
};
// If you call a function with a splat, it's converted into a JavaScript
@ -547,7 +547,7 @@
if (obj.match(/\(/)) {
temp = o.scope.free_variable();
obj = temp;
meth = "(" + temp + " = " + (this.variable.source) + ")" + (this.variable.last);
meth = ("(" + temp + " = " + (this.variable.source) + ")" + (this.variable.last));
}
return "" + (this.prefix()) + (meth) + ".apply(" + obj + ", " + (this.compile_splat_arguments(o)) + ")";
};
@ -668,12 +668,12 @@
}
idx = del(o, 'index');
step = del(o, 'step');
vars = "" + idx + " = " + this.from_var;
vars = ("" + idx + " = " + this.from_var);
step = step ? step.compile(o) : '1';
equals = this.exclusive ? '' : '=';
intro = "(" + this.from_var + " <= " + this.to_var + " ? " + idx;
compare = "" + intro + " <" + equals + " " + this.to_var + " : " + idx + " >" + equals + " " + this.to_var + ")";
incr = "" + intro + " += " + step + " : " + idx + " -= " + step + ")";
intro = ("(" + this.from_var + " <= " + this.to_var + " ? " + idx);
compare = ("" + intro + " <" + equals + " " + this.to_var + " : " + idx + " >" + equals + " " + this.to_var + ")");
incr = ("" + intro + " += " + step + " : " + idx + " -= " + step + ")");
return "" + vars + "; " + compare + "; " + incr;
};
// When used as a value, expand the range into the equivalent array. In the
@ -778,15 +778,15 @@
if (obj instanceof SplatNode) {
return this.compile_splat_literal(this.objects, o);
} else if (obj instanceof CommentNode) {
objects.push("\n" + code + "\n" + o.indent);
objects.push(("\n" + code + "\n" + o.indent));
} else if (i === this.objects.length - 1) {
objects.push(code);
} else {
objects.push("" + code + ", ");
objects.push(("" + code + ", "));
}
}
objects = objects.join('');
ending = objects.indexOf('\n') >= 0 ? "\n" + this.tab + "]" : ']';
ending = objects.indexOf('\n') >= 0 ? ("\n" + this.tab + "]") : ']';
return "[" + objects + ending;
};
return ArrayNode;
@ -903,14 +903,14 @@
}
val = this.value.compile(o);
if (this.context === 'object') {
return "" + name + ": " + val;
return ("" + name + ": " + val);
}
if (!(this.is_value() && this.variable.has_properties())) {
o.scope.find(name);
}
val = "" + name + " = " + val;
val = ("" + name + " = " + val);
if (stmt) {
return "" + this.tab + val + ";";
return ("" + this.tab + val + ";");
}
if (top) {
return val;
@ -926,7 +926,7 @@
var _a, _b, _c, access_class, assigns, code, i, idx, obj, oindex, olength, splat, val, val_var, value;
val_var = o.scope.free_variable();
value = this.value.is_statement() ? ClosureNode.wrap(this.value) : this.value;
assigns = ["" + this.tab + val_var + " = " + (value.compile(o)) + ";"];
assigns = [("" + this.tab + val_var + " = " + (value.compile(o)) + ";")];
o.top = true;
o.as_statement = true;
splat = false;
@ -945,7 +945,7 @@
splat = true;
} else {
if (typeof idx !== 'object') {
idx = literal(splat ? "" + (val_var) + ".length - " + (olength - idx) : idx);
idx = literal(splat ? ("" + (val_var) + ".length - " + (olength - idx)) : idx);
}
val = new ValueNode(literal(val_var), [new access_class(idx)]);
}
@ -1029,11 +1029,11 @@
param = _i[_h];
(o.scope.parameter(param));
}
code = this.body.expressions.length ? "\n" + (this.body.compile_with_declarations(o)) + "\n" : '';
code = this.body.expressions.length ? ("\n" + (this.body.compile_with_declarations(o)) + "\n") : '';
name_part = this.name ? ' ' + this.name : '';
func = "function" + (this.bound ? '' : name_part) + "(" + (params.join(', ')) + ") {" + code + (this.idt(this.bound ? 1 : 0)) + "}";
func = ("function" + (this.bound ? '' : name_part) + "(" + (params.join(', ')) + ") {" + code + (this.idt(this.bound ? 1 : 0)) + "}");
if (top && !this.bound) {
func = "(" + func + ")";
func = ("(" + func + ")");
}
if (!(this.bound)) {
return func;
@ -1106,7 +1106,7 @@
_b = this.trailings;
for (_a = 0, _c = _b.length; _a < _c; _a++) {
trailing = _b[_a];
o.scope.assign(trailing.compile(o), "arguments[arguments.length - " + this.trailings.length + " + " + i + "]");
o.scope.assign(trailing.compile(o), ("arguments[arguments.length - " + this.trailings.length + " + " + i + "]"));
i += 1;
}
return "" + name + " = " + (utility('slice')) + ".call(arguments, " + this.index + ", arguments.length - " + (this.trailings.length) + ")";
@ -1115,7 +1115,7 @@
// from the right-hand-side's corresponding array.
SplatNode.prototype.compile_value = function compile_value(o, name, index, trailings) {
var trail;
trail = trailings ? ", " + (name) + ".length - " + trailings : '';
trail = trailings ? (", " + (name) + ".length - " + trailings) : '';
return "" + (utility('slice')) + ".call(" + name + ", " + index + trail + ")";
};
// Utility function that converts arbitrary number of elements, mixed with
@ -1131,16 +1131,16 @@
if (!(arg instanceof SplatNode)) {
prev = args[i - 1];
if (i === 1 && prev.substr(0, 1) === '[' && prev.substr(prev.length - 1, 1) === ']') {
args[i - 1] = "" + (prev.substr(0, prev.length - 1)) + ", " + code + "]";
args[i - 1] = ("" + (prev.substr(0, prev.length - 1)) + ", " + code + "]");
continue;
} else if (i > 1 && prev.substr(0, 9) === '.concat([' && prev.substr(prev.length - 2, 2) === '])') {
args[i - 1] = "" + (prev.substr(0, prev.length - 2)) + ", " + code + "])";
args[i - 1] = ("" + (prev.substr(0, prev.length - 2)) + ", " + code + "])");
continue;
} else {
code = "[" + code + "]";
code = ("[" + code + "]");
}
}
args.push(i === 0 ? code : ".concat(" + code + ")");
args.push(i === 0 ? code : (".concat(" + code + ")"));
i += 1;
}
return args.join('');
@ -1181,14 +1181,14 @@
set = '';
if (!top) {
rvar = o.scope.free_variable();
set = "" + this.tab + rvar + " = [];\n";
set = ("" + this.tab + rvar + " = [];\n");
if (this.body) {
this.body = PushNode.wrap(rvar, this.body);
}
}
pre = "" + set + (this.tab) + "while (" + cond + ")";
pre = ("" + set + (this.tab) + "while (" + cond + ")");
if (!this.body) {
return "" + pre + " null;" + post;
return ("" + pre + " null;" + post);
}
if (this.filter) {
this.body = Expressions.wrap([new IfNode(this.filter, this.body)]);
@ -1277,7 +1277,7 @@
o.scope.find(first);
}
if (this.operator === '?=') {
return "" + first + " = " + (ExistenceNode.compile_test(o, this.first)) + " ? " + first + " : " + second;
return ("" + first + " = " + (ExistenceNode.compile_test(o, this.first)) + " ? " + first + " : " + second);
}
return "" + first + " = " + first + " " + (this.operator.substr(0, 2)) + " " + second;
};
@ -1329,9 +1329,9 @@
o.indent = this.idt(1);
o.top = true;
attempt_part = this.attempt.compile(o);
error_part = this.error ? " (" + (this.error.compile(o)) + ") " : ' ';
catch_part = this.recovery ? " catch" + error_part + "{\n" + (this.recovery.compile(o)) + "\n" + this.tab + "}" : '';
finally_part = (this.ensure || '') && ' finally {\n' + this.ensure.compile(merge(o)) + "\n" + this.tab + "}";
error_part = this.error ? (" (" + (this.error.compile(o)) + ") ") : ' ';
catch_part = this.recovery ? (" catch" + error_part + "{\n" + (this.recovery.compile(o)) + "\n" + this.tab + "}") : '';
finally_part = (this.ensure || '') && ' finally {\n' + this.ensure.compile(merge(o)) + ("\n" + this.tab + "}");
return "" + (this.tab) + "try {\n" + attempt_part + "\n" + this.tab + "}" + catch_part + finally_part;
};
return TryNode;
@ -1495,18 +1495,18 @@
index: ivar,
step: this.step
}));
for_part = "" + index_var + " = 0, " + for_part + ", " + index_var + "++";
for_part = ("" + index_var + " = 0, " + for_part + ", " + index_var + "++");
} else {
svar = scope.free_variable();
index_var = null;
source_part = "" + svar + " = " + (this.source.compile(o)) + ";\n" + this.tab;
source_part = ("" + svar + " = " + (this.source.compile(o)) + ";\n" + this.tab);
if (name) {
var_part = "" + body_dent + name + " = " + svar + "[" + ivar + "];\n";
var_part = ("" + body_dent + name + " = " + svar + "[" + ivar + "];\n");
}
if (!this.object) {
lvar = scope.free_variable();
step_part = this.step ? "" + ivar + " += " + (this.step.compile(o)) : "" + ivar + "++";
for_part = "" + ivar + " = 0, " + lvar + " = " + (svar) + ".length; " + ivar + " < " + lvar + "; " + step_part;
step_part = this.step ? ("" + ivar + " += " + (this.step.compile(o))) : ("" + ivar + "++");
for_part = ("" + ivar + " = 0, " + lvar + " = " + (svar) + ".length; " + ivar + " < " + lvar + "; " + step_part);
}
}
set_result = rvar ? this.idt() + rvar + ' = []; ' : this.idt();
@ -1520,12 +1520,12 @@
body = PushNode.wrap(rvar, body);
}
this.filter ? (body = Expressions.wrap([new IfNode(this.filter, body)])) : null;
this.object ? (for_part = "" + ivar + " in " + svar + ") { if (" + (utility('hasProp')) + ".call(" + svar + ", " + ivar + ")") : null;
this.object ? (for_part = ("" + ivar + " in " + svar + ") { if (" + (utility('hasProp')) + ".call(" + svar + ", " + ivar + ")")) : null;
body = body.compile(merge(o, {
indent: body_dent,
top: true
}));
vars = range ? name : "" + name + ", " + ivar;
vars = range ? name : ("" + name + ", " + ivar);
close = this.object ? '}}\n' : '}\n';
return "" + set_result + (source_part) + "for (" + for_part + ") {\n" + var_part + body + "\n" + this.tab + close + return_result;
};
@ -1655,16 +1655,16 @@
o.top = true;
if_dent = child ? '' : this.idt();
com_dent = child ? this.idt() : '';
prefix = this.comment ? "" + (this.comment.compile(cond_o)) + "\n" + com_dent : '';
prefix = this.comment ? ("" + (this.comment.compile(cond_o)) + "\n" + com_dent) : '';
body = Expressions.wrap([this.body]).compile(o);
if_part = "" + prefix + (if_dent) + "if (" + (this.compile_condition(cond_o)) + ") {\n" + body + "\n" + this.tab + "}";
if_part = ("" + prefix + (if_dent) + "if (" + (this.compile_condition(cond_o)) + ") {\n" + body + "\n" + this.tab + "}");
if (!(this.else_body)) {
return if_part;
}
else_part = this.is_chain() ? ' else ' + this.else_body.compile(merge(o, {
indent: this.idt(),
chain_child: true
})) : " else {\n" + (Expressions.wrap([this.else_body]).compile(o)) + "\n" + this.tab + "}";
})) : (" else {\n" + (Expressions.wrap([this.else_body]).compile(o)) + "\n" + this.tab + "}");
return "" + if_part + else_part;
};
// Compile the IfNode as a ternary operator.
@ -1744,7 +1744,7 @@
// Helper for ensuring that utility functions are assigned at the top level.
utility = function utility(name) {
var ref;
ref = "__" + name;
ref = ("__" + name);
Scope.root.assign(ref, UTILITIES[ref]);
return ref;
};

View File

@ -37,7 +37,7 @@
}
}
if (is_option && !matched_rule) {
throw new Error("unrecognized option: " + arg);
throw new Error(("unrecognized option: " + arg));
}
if (!(is_option)) {
options.arguments.push(arg);
@ -51,7 +51,7 @@
var _a, _b, _c, _d, _e, _f, _g, i, let_part, lines, rule, spaces;
lines = ['Available options:'];
if (this.banner) {
lines.unshift("" + this.banner + "\n");
lines.unshift(("" + this.banner + "\n"));
}
_b = this.rules;
for (_a = 0, _c = _b.length; _a < _c; _a++) {
@ -65,7 +65,7 @@
return _d;
}).call(this).join('') : '';
let_part = rule.short_flag ? rule.short_flag + ', ' : ' ';
lines.push(" " + let_part + (rule.long_flag) + spaces + (rule.description));
lines.push((" " + let_part + (rule.long_flag) + spaces + (rule.description)));
}
return "\n" + (lines.join('\n')) + "\n";
};

View File

@ -255,7 +255,7 @@
levels[open] -= 1;
}
if (levels[open] < 0) {
throw new Error("too many " + (token[1]) + " on line " + (token[2] + 1));
throw new Error(("too many " + (token[1]) + " on line " + (token[2] + 1)));
}
}
return 1;
@ -271,7 +271,7 @@
if (unclosed.length) {
open = unclosed[0];
line = open_line[open] + 1;
throw new Error("unclosed " + open + " on line " + line);
throw new Error(("unclosed " + open + " on line " + line));
}
};
// We'd like to support syntax like this:

View File

@ -120,7 +120,7 @@
_a = []; _b = this.variables;
for (key in _b) { if (__hasProp.call(_b, key)) {
val = _b[key];
val.assigned ? _a.push("" + key + " = " + (val.value)) : null;
val.assigned ? _a.push(("" + key + " = " + (val.value))) : null;
}}
return _a;
};

View File

@ -376,6 +376,8 @@ exports.Lexer: class Lexer
i: + 1
tokens.push ['STRING', "$quote${ str.substring(pi, i) }$quote"] if pi < i and pi < str.length - 1
tokens.unshift ['STRING', '""'] unless tokens[0][0] is 'STRING'
interpolated: tokens.length > 1
@token '(', '(' if interpolated
for token, i in tokens
[tag, value]: token
if tag is 'TOKENS'
@ -386,6 +388,7 @@ exports.Lexer: class Lexer
else
@token tag, value
@token '+', '+' if i < tokens.length - 1
@token ')', ')' if interpolated
tokens
# Helpers