diff --git a/lib/lexer.js b/lib/lexer.js index ee349cce..2ce29abc 100644 --- a/lib/lexer.js +++ b/lib/lexer.js @@ -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; diff --git a/lib/nodes.js b/lib/nodes.js index bca3d9a8..47664365 100644 --- a/lib/nodes.js +++ b/lib/nodes.js @@ -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); diff --git a/lib/optparse.js b/lib/optparse.js index 4095b885..4aa5107f 100755 --- a/lib/optparse.js +++ b/lib/optparse.js @@ -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++) { diff --git a/lib/scope.js b/lib/scope.js index b0930b62..0b0036d2 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -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; diff --git a/src/lexer.coffee b/src/lexer.coffee index 9e71baf3..cc2775fc 100644 --- a/src/lexer.coffee +++ b/src/lexer.coffee @@ -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