more regexp and string interpolation tweaks

This commit is contained in:
Jeremy Ashkenas 2010-03-08 21:34:10 -05:00
parent 3396dce2bb
commit b5af5f66fb
5 changed files with 81 additions and 89 deletions

View File

@ -1,5 +1,5 @@
(function(){
var ACCESSORS, ASSIGNMENT, BEFORE_WHEN, CALLABLE, CODE, COFFEE_KEYWORDS, COMMENT, COMMENT_CLEANER, HEREDOC, HEREDOC_INDENT, IDENTIFIER, INTERPOLATION, JS_CLEANER, JS_FORBIDDEN, JS_KEYWORDS, KEYWORDS, LAST_DENT, LAST_DENTS, Lexer, MULTILINER, MULTI_DENT, NOT_REGEX, NO_NEWLINE, NUMBER, OPERATOR, RESERVED, Rewriter, STRING_NEWLINES, WHITESPACE, compact, count, include, starts;
var ACCESSORS, ASSIGNMENT, BEFORE_WHEN, CALLABLE, CODE, COFFEE_KEYWORDS, COMMENT, COMMENT_CLEANER, HEREDOC, HEREDOC_INDENT, IDENTIFIER, INTERPOLATION, JS_CLEANER, JS_FORBIDDEN, JS_KEYWORDS, KEYWORDS, LAST_DENT, LAST_DENTS, Lexer, MULTILINER, MULTI_DENT, NOT_REGEX, NO_NEWLINE, NUMBER, OPERATOR, REGEX_ESCAPE, REGEX_FLAGS, REGEX_INTERPOLATION, REGEX_START, RESERVED, Rewriter, STRING_NEWLINES, WHITESPACE, compact, count, include, starts;
// The CoffeeScript Lexer. Uses a series of token-matching regexes to attempt
// matches against the beginning of the source code. When a match is found,
// a token is produced, we consume the match, and start again. Tokens are in the
@ -171,8 +171,8 @@
// to distinguish from division, so we borrow some basic heuristics from
// JavaScript and Ruby.
Lexer.prototype.regex_token = function regex_token() {
var _a, _b, _c, _d, _e, each, flags, i, index, interp_tokens, merge, regex, str;
if (!(starts(this.chunk, '/'))) {
var _a, _b, _c, _d, each, flags, i, interp_tokens, merge, regex, str;
if (!(this.chunk.match(REGEX_START))) {
return false;
}
if (include(NOT_REGEX, this.tag())) {
@ -181,29 +181,20 @@
if (!((regex = this.balanced_token(['/', '/'])))) {
return false;
}
if (regex.length < 3 || regex.match(/^\/\s+/m)) {
return false;
}
flags = ['i', 'm', 'g', 'y'];
while (((index = flags.indexOf(this.chunk.substr(regex.length, 1)))) >= 0) {
regex += flags[index];
flags.splice(index, 1);
}
if (((0 < (_e = regex.indexOf('${'))) && (_e < regex.indexOf('}'))) || regex.match(/[^\\]\$[a-zA-Z_@]/)) {
_a = regex.substring(1).split('/');
str = _a[0];
flags = _a[1];
str = str.replace(/\\[^\$]/g, function(escaped) {
regex += ((flags = this.chunk.substr(regex.length).match(REGEX_FLAGS)));
if (((0 < (_d = regex.indexOf('${'))) && (_d < regex.indexOf('}'))) || regex.match(REGEX_INTERPOLATION)) {
str = regex.substring(1).split('/')[0];
str = str.replace(REGEX_ESCAPE, function(escaped) {
return '\\' + escaped;
});
this.tokens = this.tokens.concat([['(', '('], ['NEW', 'new'], ['IDENTIFIER', 'RegExp'], ['CALL_START', '(']]);
interp_tokens = this.interpolate_string("\"" + str + "\"", (merge = false));
_b = interp_tokens;
for (i = 0, _c = _b.length; i < _c; i++) {
each = _b[i];
if ((_d = each[0]) === 'TOKENS') {
_a = interp_tokens;
for (i = 0, _b = _a.length; i < _b; i++) {
each = _a[i];
if ((_c = each[0]) === 'TOKENS') {
this.tokens = this.tokens.concat(each[1]);
} else if (_d === 'STRING') {
} else if (_c === 'STRING') {
this.token(each[0], each[1].substring(0, 1) + each[1].substring(1, each[1].length - 1).replace(/"/g, '\\"') + each[1].substring(0, 1));
} else {
this.token(each[0], each[1]);
@ -481,7 +472,7 @@
// new Lexer, tokenize the interpolated contents, and merge them into the
// token stream.
Lexer.prototype.interpolate_string = function interpolate_string(str, merge) {
var _a, _b, _c, _d, _e, _f, _g, each, expr, group, has_string, i, inner, interp, lexer, match, nested, pi, quote, tokens;
var _a, _b, _c, _d, each, expr, group, i, inner, interp, lexer, match, nested, pi, quote, tokens;
if (str.length < 3 || !starts(str, '"')) {
return this.token('STRING', str);
} else {
@ -530,18 +521,13 @@
if (pi < i && pi < str.length - 1) {
tokens.push(['STRING', '' + quote + (str.substring(pi, i)) + quote]);
}
_c = tokens;
for (_d = 0, _e = _c.length; _d < _e; _d++) {
each = _c[_d];
each[0] === 'STRING' ? ((has_string = true)) : null;
}
if (!has_string) {
if (!(tokens[0][0] === 'STRING')) {
tokens.unshift(['STRING', "''"]);
}
if (((typeof merge !== "undefined" && merge !== null) ? merge : true)) {
_f = tokens;
for (i = 0, _g = _f.length; i < _g; i++) {
each = _f[i];
_c = tokens;
for (i = 0, _d = _c.length; i < _d; i++) {
each = _c[i];
each[0] === 'TOKENS' ? (this.tokens = this.tokens.concat(each[1])) : this.token(each[0], each[1]);
if (i < tokens.length - 1) {
this.token('+', '+');
@ -624,6 +610,11 @@
LAST_DENTS = /\n([ \t]*)/g;
LAST_DENT = /\n([ \t]*)/;
ASSIGNMENT = /^(:|=)$/;
// Regex-matching-regexes.
REGEX_START = /^\/[^\/ ]/;
REGEX_INTERPOLATION = /[^\\]\$[a-zA-Z_@]/;
REGEX_FLAGS = /^[imgy]{0,4}/;
REGEX_ESCAPE = /\\[^\$]/g;
// Token cleaning regexes.
JS_CLEANER = /(^`|`$)/g;
MULTILINER = /\n/g;

View File

@ -227,13 +227,13 @@ idt += TAB
return node instanceof ValueNode && node.is_arguments();
});
if (args) {
code = (this.tab) + "arguments = Array.prototype.slice.call(arguments, 0);\n" + code;
code = '' + (this.tab) + "arguments = Array.prototype.slice.call(arguments, 0);\n" + code;
}
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;
};
@ -255,7 +255,7 @@ idt += TAB
returns: true
}));
}
return (this.tab) + "return " + (node.compile(o)) + ";";
return '' + (this.tab) + "return " + (node.compile(o)) + ";";
};
return Expressions;
}).call(this);
@ -312,7 +312,7 @@ idt += TAB
returns: true
}));
}
return (this.tab) + "return " + (this.expression.compile(o)) + ";";
return '' + (this.tab) + "return " + (this.expression.compile(o)) + ";";
};
return ReturnNode;
}).call(this);
@ -413,7 +413,7 @@ idt += TAB
__extends(CommentNode, BaseNode);
CommentNode.prototype.type = 'Comment';
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);
@ -457,15 +457,15 @@ idt += TAB
if (this.variable === 'super') {
return this.compile_super(args, o);
}
return this.prefix + (this.variable.compile(o)) + "(" + args + ")";
return '' + this.prefix + (this.variable.compile(o)) + "(" + args + ")";
};
// `super()` is converted into a call against the superclass's implementation
// of the current function.
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";
return (meth) + ".call(this" + (args.length ? ', ' : '') + args + ")";
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
// `.apply()` call to allow the variable-length arguments.
@ -490,7 +490,7 @@ idt += TAB
}
return _a;
}).call(this);
return this.prefix + (meth) + ".apply(" + obj + ", " + (args.join('')) + ")";
return '' + this.prefix + (meth) + ".apply(" + obj + ", " + (args.join('')) + ")";
};
return CallNode;
}).call(this);
@ -573,7 +573,7 @@ idt += TAB
_b = [this.from.compile(o), this.to.compile(o)];
from = _b[0];
to = _b[1];
return this.from_var + " = " + from + "; " + this.to_var + " = " + to + ";\n" + this.tab;
return '' + this.from_var + " = " + from + "; " + this.to_var + " = " + to + ";\n" + this.tab;
};
// When compiled normally, the range returns the contents of the *for loop*
// needed to iterate over the values in the range. Used by comprehensions.
@ -584,13 +584,13 @@ idt += TAB
}
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 + ")";
return vars + "; " + compare + "; " + incr;
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
// future, the code this generates should probably be cleaned up by handwriting
@ -702,7 +702,7 @@ idt += TAB
} else if (i === this.objects.length - 1) {
return code;
} else {
return code + ", ";
return '' + code + ", ";
}
}).call(this));
}
@ -844,20 +844,20 @@ idt += TAB
}
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 || o.returns) {
val = "(" + val + ")";
}
if (o.returns) {
val = (this.tab) + "return " + val;
val = '' + (this.tab) + "return " + val;
}
return val;
};
@ -869,7 +869,7 @@ idt += TAB
var _a, _b, _c, access_class, assigns, code, i, idx, obj, 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;
_a = this.variable.base.objects;
@ -911,7 +911,7 @@ idt += TAB
from = range.from.compile(o);
to = range.to.compile(o) + ' - ' + from + plus;
val = this.value.compile(o);
return (name) + ".splice.apply(" + name + ", [" + from + ", " + to + "].concat(" + val + "))";
return '' + (name) + ".splice.apply(" + name + ", [" + from + ", " + to + "].concat(" + val + "))";
};
return AssignNode;
}).call(this);
@ -1030,7 +1030,7 @@ idt += TAB
var name;
name = this.name.compile(o);
o.scope.find(name);
return name + " = Array.prototype.slice.call(arguments, " + this.index + ")";
return '' + name + " = Array.prototype.slice.call(arguments, " + this.index + ")";
};
// A compiling a splat as a destructuring assignment means slicing arguments
// from the right-hand-side's corresponding array.
@ -1071,20 +1071,20 @@ idt += TAB
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);
}
}
post = returns ? "\n" + (this.tab) + "return " + rvar + ";" : '';
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)]);
}
return pre + " {\n" + (this.body.compile(o)) + "\n" + this.tab + "}" + post;
return '' + pre + " {\n" + (this.body.compile(o)) + "\n" + this.tab + "}" + post;
};
return WhileNode;
}).call(this);
@ -1171,9 +1171,9 @@ idt += TAB
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;
return '' + first + " = " + first + " " + (this.operator.substr(0, 2)) + " " + second;
};
// If this is an existence operator, we delegate to `ExistenceNode.compile_test`
// to give us the safe references for the variables.
@ -1183,7 +1183,7 @@ idt += TAB
first = _a[0];
second = _a[1];
test = ExistenceNode.compile_test(o, this.first);
return test + " ? " + first + " : " + second;
return '' + test + " ? " + first + " : " + second;
};
// Compile a unary **OpNode**.
OpNode.prototype.compile_unary = function compile_unary(o) {
@ -1216,11 +1216,11 @@ idt += TAB
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 + "}";
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, {
returns: null
})) + "\n" + this.tab + "}";
return (this.tab) + "try {\n" + attempt_part + "\n" + this.tab + "}" + catch_part + finally_part;
return '' + (this.tab) + "try {\n" + attempt_part + "\n" + this.tab + "}" + catch_part + finally_part;
};
return TryNode;
}).call(this);
@ -1235,7 +1235,7 @@ idt += TAB
__extends(ThrowNode, BaseNode);
ThrowNode.prototype.type = 'Throw';
ThrowNode.prototype.compile_node = function compile_node(o) {
return (this.tab) + "throw " + (this.expression.compile(o)) + ";";
return '' + (this.tab) + "throw " + (this.expression.compile(o)) + ";";
};
return ThrowNode;
}).call(this);
@ -1362,17 +1362,17 @@ idt += TAB
index: ivar,
step: this.step
}));
for_part = index_var + " = 0, " + for_part + ", " + index_var + "++";
for_part = '' + index_var + " = 0, " + for_part + ", " + index_var + "++";
} else {
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();
@ -1398,7 +1398,7 @@ idt += TAB
}
if (this.object) {
o.scope.assign('__hasProp', 'Object.prototype.hasOwnProperty', true);
for_part = ivar + " in " + svar + ") { if (__hasProp.call(" + svar + ", " + ivar + ")";
for_part = '' + ivar + " in " + svar + ") { if (__hasProp.call(" + svar + ", " + ivar + ")";
}
if (!(top_level)) {
return_result = "\n" + this.tab + return_result + ";";
@ -1407,9 +1407,9 @@ idt += TAB
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 + this.tab + return_result;
return '' + set_result + (source_part) + "for (" + for_part + ") {\n" + var_part + body + "\n" + this.tab + close + this.tab + return_result;
};
return ForNode;
}).call(this);
@ -1530,9 +1530,9 @@ idt += TAB
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;
}
@ -1547,7 +1547,7 @@ idt += TAB
var else_part, if_part;
if_part = this.condition.compile(o) + ' ? ' + this.body.compile(o);
else_part = this.else_body ? this.else_body.compile(o) : 'null';
return if_part + " : " + else_part;
return '' + if_part + " : " + else_part;
};
return IfNode;
}).call(this);

View File

@ -52,7 +52,7 @@
var _a, _b, _c, _d, _e, _f, _g, _h, i, let_part, lines, rule, spaces;
lines = ['Available options:'];
if (this.banner) {
lines.unshift(this.banner + "\n");
lines.unshift('' + this.banner + "\n");
}
_a = this.rules;
for (_b = 0, _c = _a.length; _b < _c; _b++) {

View File

@ -101,7 +101,7 @@
for (key in _b) { if (__hasProp.call(_b, key)) {
val = _b[key];
if (val.assigned) {
_a.push(key + " = " + (val.value));
_a.push('' + key + " = " + (val.value));
}
}}
return _a;

View File

@ -126,17 +126,13 @@ exports.Lexer: class Lexer
# to distinguish from division, so we borrow some basic heuristics from
# JavaScript and Ruby.
regex_token: ->
return false unless starts @chunk, '/'
return false if include NOT_REGEX, @tag()
return false unless @chunk.match REGEX_START
return false if include NOT_REGEX, @tag()
return false unless regex: @balanced_token ['/', '/']
return false if regex.length < 3 or regex.match /^\/\s+/m
flags: ['i', 'm', 'g', 'y']
while (index: flags.indexOf @chunk.substr regex.length, 1) >= 0
regex += flags[index]
flags.splice index, 1
if (0 < regex.indexOf('${') < regex.indexOf('}')) or regex.match /[^\\]\$[a-zA-Z_@]/
[str, flags]: regex.substring(1).split('/')
str: str.replace /\\[^\$]/g, (escaped) -> '\\' + escaped
regex += (flags: @chunk.substr(regex.length).match(REGEX_FLAGS))
if (0 < regex.indexOf('${') < regex.indexOf('}')) or regex.match REGEX_INTERPOLATION
str: regex.substring(1).split('/')[0]
str: str.replace REGEX_ESCAPE, (escaped) -> '\\' + escaped
@tokens: @tokens.concat [['(', '('], ['NEW', 'new'], ['IDENTIFIER', 'RegExp'], ['CALL_START', '(']]
interp_tokens: @interpolate_string "\"$str\"", merge: false
for each, i in interp_tokens
@ -383,8 +379,7 @@ exports.Lexer: class Lexer
pi: i + 1
i += 1
tokens.push ['STRING', "$quote${ str.substring(pi, i) }$quote"] if pi < i and pi < str.length - 1
(has_string: yes) for each in tokens when each[0] is 'STRING'
tokens.unshift ['STRING', "''"] if not has_string
tokens.unshift ['STRING', "''"] unless tokens[0][0] is 'STRING'
if (merge ? true)
for each, i in tokens
if each[0] is 'TOKENS'
@ -478,6 +473,12 @@ LAST_DENTS : /\n([ \t]*)/g
LAST_DENT : /\n([ \t]*)/
ASSIGNMENT : /^(:|=)$/
# Regex-matching-regexes.
REGEX_START : /^\/[^\/ ]/
REGEX_INTERPOLATION: /[^\\]\$[a-zA-Z_@]/
REGEX_FLAGS : /^[imgy]{0,4}/
REGEX_ESCAPE : /\\[^\$]/g
# Token cleaning regexes.
JS_CLEANER : /(^`|`$)/g
MULTILINER : /\n/g