unary-new: merged master

This commit is contained in:
satyr
2010-09-27 01:22:33 +09:00
21 changed files with 383 additions and 344 deletions

View File

@@ -1,5 +1,5 @@
fs = require 'fs' fs = require 'fs'
{helpers} = require './lib/helpers' helpers = require './lib/helpers'
CoffeeScript = require './lib/coffee-script' CoffeeScript = require './lib/coffee-script'
{spawn, exec} = require 'child_process' {spawn, exec} = require 'child_process'
path = require 'path' path = require 'path'
@@ -87,6 +87,9 @@ task 'doc:underscore', 'rebuild the Underscore.coffee documentation page', ->
exec 'docco examples/underscore.coffee && cp -rf docs documentation && rm -r docs', (err) -> exec 'docco examples/underscore.coffee && cp -rf docs documentation && rm -r docs', (err) ->
throw err if err throw err if err
task 'bench', 'quick benchmark of compilation time (of everything in src)', ->
exec 'time bin/coffee -p src/ > /dev/null', (err, stdout, stderr) ->
print stderr
task 'loc', 'count the lines of source code in the CoffeeScript compiler', -> task 'loc', 'count the lines of source code in the CoffeeScript compiler', ->
sources = ['src/coffee-script.coffee', 'src/grammar.coffee', 'src/helpers.coffee', 'src/lexer.coffee', 'src/nodes.coffee', 'src/rewriter.coffee', 'src/scope.coffee'] sources = ['src/coffee-script.coffee', 'src/grammar.coffee', 'src/helpers.coffee', 'src/lexer.coffee', 'src/nodes.coffee', 'src/rewriter.coffee', 'src/scope.coffee']

View File

@@ -1,8 +1,8 @@
#!/usr/bin/env node #!/usr/bin/env node
var path = require('path'); var path = require('path');
var fs = require('fs'); var fs = require('fs');
var lib = path.join(path.dirname(fs.realpathSync(__filename)), '../lib'); var lib = path.join(path.dirname(fs.realpathSync(__filename)), '../lib');
require(lib + '/helpers').helpers.extend(global, require('sys')); require(lib + '/helpers').extend(global, require('sys'));
require(lib + '/cake').run(); require(lib + '/cake').run();

View File

@@ -1,8 +1,8 @@
#!/usr/bin/env node #!/usr/bin/env node
var path = require('path'); var path = require('path');
var fs = require('fs'); var fs = require('fs');
var lib = path.join(path.dirname(fs.realpathSync(__filename)), '../lib'); var lib = path.join(path.dirname(fs.realpathSync(__filename)), '../lib');
require(lib + '/helpers').helpers.extend(global, require('sys')); require(lib + '/helpers').extend(global, require('sys'));
require(lib + '/command').run(); require(lib + '/command').run();

View File

@@ -2,7 +2,7 @@
var CoffeeScript, fs, helpers, missingTask, oparse, options, optparse, path, printTasks, switches, tasks; var CoffeeScript, fs, helpers, missingTask, oparse, options, optparse, path, printTasks, switches, tasks;
fs = require('fs'); fs = require('fs');
path = require('path'); path = require('path');
helpers = require('./helpers').helpers; helpers = require('./helpers');
optparse = require('./optparse'); optparse = require('./optparse');
CoffeeScript = require('./coffee-script'); CoffeeScript = require('./coffee-script');
tasks = {}; tasks = {};

View File

@@ -4,8 +4,7 @@
path = require('path'); path = require('path');
optparse = require('./optparse'); optparse = require('./optparse');
CoffeeScript = require('./coffee-script'); CoffeeScript = require('./coffee-script');
_ref = require('./helpers'); helpers = require('./helpers');
helpers = _ref.helpers;
_ref = require('child_process'); _ref = require('child_process');
spawn = _ref.spawn; spawn = _ref.spawn;
exec = _ref.exec; exec = _ref.exec;

View File

@@ -1,11 +1,9 @@
(function() { (function() {
var compact, count, del, ends, extend, flatten, helpers, include, indexOf, merge, starts; var extend, indexOf;
helpers = (exports.helpers = {}); indexOf = (exports.indexOf = Array.indexOf || (Array.prototype.indexOf ? function(array, item, from) {
helpers.indexOf = (indexOf = function(array, item, from) { return array.indexOf(item, from);
} : function(array, item, from) {
var _len, _ref, index, other; var _len, _ref, index, other;
if (array.indexOf) {
return array.indexOf(item, from);
}
_ref = array; _ref = array;
for (index = 0, _len = _ref.length; index < _len; index++) { for (index = 0, _len = _ref.length; index < _len; index++) {
other = _ref[index]; other = _ref[index];
@@ -14,19 +12,19 @@
} }
} }
return -1; return -1;
}); }));
helpers.include = (include = function(list, value) { exports.include = function(list, value) {
return indexOf(list, value) >= 0; return indexOf(list, value) >= 0;
}); };
helpers.starts = (starts = function(string, literal, start) { exports.starts = function(string, literal, start) {
return string.substring(start, (start || 0) + literal.length) === literal; return literal === string.substr(start, literal.length);
}); };
helpers.ends = (ends = function(string, literal, back) { exports.ends = function(string, literal, back) {
var start; var len;
start = string.length - literal.length - ((typeof back !== "undefined" && back !== null) ? back : 0); len = literal.length;
return string.substring(start, start + literal.length) === literal; return literal === string.substr(string.length - len - (back || 0), len);
}); };
helpers.compact = (compact = function(array) { exports.compact = function(array) {
var _i, _len, _ref, _result, item; var _i, _len, _ref, _result, item;
_result = []; _ref = array; _result = []; _ref = array;
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
@@ -36,61 +34,34 @@
} }
} }
return _result; return _result;
}); };
helpers.count = (count = function(string, letter) { exports.count = function(string, letter) {
var num, pos; var num, pos;
num = 0; num = (pos = 0);
pos = indexOf(string, letter); while (0 < (pos = 1 + string.indexOf(letter, pos))) {
while (pos !== -1) { num++;
num += 1;
pos = indexOf(string, letter, pos + 1);
} }
return num; return num;
}); };
helpers.merge = (merge = function(options, overrides) { exports.merge = function(options, overrides) {
var _ref, fresh, key, val; return extend(extend({}, options), overrides);
fresh = {}; };
_ref = options; extend = (exports.extend = function(object, properties) {
var _ref, key, val;
_ref = properties;
for (key in _ref) { for (key in _ref) {
val = _ref[key]; val = _ref[key];
(fresh[key] = val); object[key] = val;
} }
if (overrides) { return object;
_ref = overrides;
for (key in _ref) {
val = _ref[key];
(fresh[key] = val);
}
}
return fresh;
}); });
helpers.extend = (extend = function(object, properties) { exports.flatten = function(array) {
var _ref, _result, key, val; return array.concat.apply([], array);
_result = []; _ref = properties; };
for (key in _ref) { exports.del = function(obj, key) {
val = _ref[key];
_result.push(object[key] = val);
}
return _result;
});
helpers.flatten = (flatten = function(array) {
var _i, _len, _ref, item, memo;
memo = [];
_ref = array;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
item = _ref[_i];
if (item instanceof Array) {
memo = memo.concat(item);
} else {
memo.push(item);
}
}
return memo;
});
helpers.del = (del = function(obj, key) {
var val; var val;
val = obj[key]; val = obj[key];
delete obj[key]; delete obj[key];
return val; return val;
}); };
}).call(this); }).call(this);

View File

@@ -3,7 +3,7 @@
var __slice = Array.prototype.slice; var __slice = Array.prototype.slice;
_ref = require('./rewriter'); _ref = require('./rewriter');
Rewriter = _ref.Rewriter; Rewriter = _ref.Rewriter;
_ref = require('./helpers').helpers; _ref = require('./helpers');
include = _ref.include; include = _ref.include;
count = _ref.count; count = _ref.count;
starts = _ref.starts; starts = _ref.starts;
@@ -23,7 +23,7 @@
this.indents = []; this.indents = [];
this.tokens = []; this.tokens = [];
while ((this.chunk = code.slice(this.i))) { while ((this.chunk = code.slice(this.i))) {
this.extractNextToken(); this.identifierToken() || this.commentToken() || this.whitespaceToken() || this.lineToken() || this.heredocToken() || this.stringToken() || this.numberToken() || this.regexToken() || this.jsToken() || this.literalToken();
} }
this.closeIndentation(); this.closeIndentation();
if (o.rewrite === false) { if (o.rewrite === false) {
@@ -31,20 +31,18 @@
} }
return (new Rewriter).rewrite(this.tokens); return (new Rewriter).rewrite(this.tokens);
}; };
Lexer.prototype.extractNextToken = function() {
return this.identifierToken() || this.commentToken() || this.whitespaceToken() || this.lineToken() || this.heredocToken() || this.stringToken() || this.numberToken() || this.regexToken() || this.jsToken() || this.literalToken();
};
Lexer.prototype.identifierToken = function() { Lexer.prototype.identifierToken = function() {
var closeIndex, forcedIdentifier, id, tag; var closeIndex, forcedIdentifier, id, match, tag;
if (!(id = this.match(IDENTIFIER))) { if (!(match = IDENTIFIER.exec(this.chunk))) {
return false; return false;
} }
id = match[0];
this.i += id.length; this.i += id.length;
if (id === 'all' && this.tag() === 'FOR') { if (id === 'all' && this.tag() === 'FOR') {
this.token('ALL', id); this.token('ALL', id);
return true; return true;
} }
forcedIdentifier = this.tagAccessor() || this.match(ASSIGNED, 1); forcedIdentifier = this.tagAccessor() || ASSIGNED.test(this.chunk);
tag = 'IDENTIFIER'; tag = 'IDENTIFIER';
if (include(JS_KEYWORDS, id) || !forcedIdentifier && include(COFFEE_KEYWORDS, id)) { if (include(JS_KEYWORDS, id) || !forcedIdentifier && include(COFFEE_KEYWORDS, id)) {
tag = id.toUpperCase(); tag = id.toUpperCase();
@@ -86,10 +84,11 @@
return true; return true;
}; };
Lexer.prototype.numberToken = function() { Lexer.prototype.numberToken = function() {
var number; var match, number;
if (!(number = this.match(NUMBER))) { if (!(match = NUMBER.exec(this.chunk))) {
return false; return false;
} }
number = match[0];
if (this.tag() === '.' && number.charAt(0) === '.') { if (this.tag() === '.' && number.charAt(0) === '.') {
return false; return false;
} }
@@ -98,19 +97,19 @@
return true; return true;
}; };
Lexer.prototype.stringToken = function() { Lexer.prototype.stringToken = function() {
var string; var match, string;
switch (this.chunk.charAt(0)) { switch (this.chunk.charAt(0)) {
case "'": case "'":
if (!(string = this.match(SIMPLESTR))) { if (!(match = SIMPLESTR.exec(this.chunk))) {
return false; return false;
} }
this.token('STRING', string.replace(MULTILINER, '\\\n')); this.token('STRING', (string = match[0]).replace(MULTILINER, '\\\n'));
break; break;
case '"': case '"':
if (!(string = this.balancedToken(['"', '"'], ['#{', '}']))) { if (!(string = this.balancedToken(['"', '"'], ['#{', '}']))) {
return false; return false;
} }
this.interpolateString(string.replace(MULTILINER, '\\\n')); this.interpolateString(string);
break; break;
default: default:
return false; return false;
@@ -127,7 +126,8 @@
heredoc = match[0]; heredoc = match[0];
quote = heredoc.charAt(0); quote = heredoc.charAt(0);
doc = this.sanitizeHeredoc(match[2], { doc = this.sanitizeHeredoc(match[2], {
quote: quote quote: quote,
indent: null
}); });
this.interpolateString(quote + doc + quote, { this.interpolateString(quote + doc + quote, {
heredoc: true heredoc: true
@@ -156,11 +156,11 @@
return true; return true;
}; };
Lexer.prototype.jsToken = function() { Lexer.prototype.jsToken = function() {
var script; var match, script;
if (!(this.chunk.charAt(0) === '`' && (script = this.match(JSTOKEN)))) { if (!(this.chunk.charAt(0) === '`' && (match = JSTOKEN.exec(this.chunk)))) {
return false; return false;
} }
this.token('JS', script.slice(1, -1)); this.token('JS', (script = match[0]).slice(1, -1));
this.i += script.length; this.i += script.length;
return true; return true;
}; };
@@ -205,16 +205,17 @@
return this.balancedString(this.chunk, delimited); return this.balancedString(this.chunk, delimited);
}; };
Lexer.prototype.lineToken = function() { Lexer.prototype.lineToken = function() {
var diff, indent, nextCharacter, noNewlines, prev, size; var diff, indent, match, nextCharacter, noNewlines, prev, size;
if (!(indent = this.match(MULTI_DENT))) { if (!(match = MULTI_DENT.exec(this.chunk))) {
return false; return false;
} }
indent = match[0];
this.line += count(indent, '\n'); this.line += count(indent, '\n');
this.i += indent.length; this.i += indent.length;
prev = this.prev(2); prev = this.prev(2);
size = indent.length - 1 - indent.lastIndexOf('\n'); size = indent.length - 1 - indent.lastIndexOf('\n');
nextCharacter = this.match(NEXT_CHARACTER, 1); nextCharacter = NEXT_CHARACTER.exec(this.chunk)[1];
noNewlines = nextCharacter === '.' || nextCharacter === ',' || this.unfinished(); noNewlines = (('.' === nextCharacter || ',' === nextCharacter)) || this.unfinished();
if (size - this.indebt === this.indent) { if (size - this.indebt === this.indent) {
if (noNewlines) { if (noNewlines) {
return this.suppressNewlines(); return this.suppressNewlines();
@@ -265,15 +266,15 @@
return true; return true;
}; };
Lexer.prototype.whitespaceToken = function() { Lexer.prototype.whitespaceToken = function() {
var prev, space; var match, prev;
if (!(space = this.match(WHITESPACE))) { if (!(match = WHITESPACE.exec(this.chunk))) {
return false; return false;
} }
prev = this.prev(); prev = this.prev();
if (prev) { if (prev) {
prev.spaced = true; prev.spaced = true;
} }
this.i += space.length; this.i += match[0].length;
return true; return true;
}; };
Lexer.prototype.newlineToken = function(newlines) { Lexer.prototype.newlineToken = function(newlines) {
@@ -369,25 +370,32 @@
return accessor ? 'accessor' : false; return accessor ? 'accessor' : false;
}; };
Lexer.prototype.sanitizeHeredoc = function(doc, options) { Lexer.prototype.sanitizeHeredoc = function(doc, options) {
var _ref2, attempt, indent, match; var _ref2, attempt, herecomment, indent, match;
indent = options.indent; _ref2 = options;
if (options.herecomment && !include(doc, '\n')) { indent = _ref2.indent;
herecomment = _ref2.herecomment;
if (herecomment && !include(doc, '\n')) {
return doc; return doc;
} }
if (!(options.herecomment)) { if (!(herecomment)) {
while ((match = HEREDOC_INDENT.exec(doc))) { while ((match = HEREDOC_INDENT.exec(doc))) {
attempt = (typeof (_ref2 = match[1]) !== "undefined" && _ref2 !== null) ? match[1] : match[2]; attempt = match[1];
if (!(typeof indent !== "undefined" && indent !== null) || (0 < attempt.length) && (attempt.length < indent.length)) { if (indent === null || (0 < attempt.length) && (attempt.length < indent.length)) {
indent = attempt; indent = attempt;
} }
} }
} }
indent || (indent = ''); if (indent) {
doc = doc.replace(new RegExp('^' + indent, 'gm'), ''); doc = doc.replace(RegExp("\\n" + (indent), "g"), '\n');
if (options.herecomment) { }
if (herecomment) {
return doc; return doc;
} }
return doc.replace(/^\n/, '').replace(MULTILINER, '\\n').replace(new RegExp(options.quote, 'g'), "\\" + (options.quote)); doc = doc.replace(/^\n/, '').replace(RegExp("" + (options.quote), "g"), '\\$&');
if (options.quote === "'") {
doc = this.escapeLines(doc, true);
}
return doc;
}; };
Lexer.prototype.tagParameters = function() { Lexer.prototype.tagParameters = function() {
var i, tok; var i, tok;
@@ -469,83 +477,86 @@
return !i ? false : str.slice(0, i); return !i ? false : str.slice(0, i);
}; };
Lexer.prototype.interpolateString = function(str, options) { Lexer.prototype.interpolateString = function(str, options) {
var _len, _ref2, _ref3, end, escaped, expr, i, idx, inner, interpolated, lexer, nested, pi, quote, tag, tok, token, tokens, value; var _len, _ref2, _ref3, end, escapeQuotes, escaped, expr, heredoc, i, idx, inner, interpolated, lexer, nested, pi, push, quote, s, tag, tok, token, tokens, value;
options || (options = {}); _ref2 = options || {};
if (str.length < 3 || str.charAt(0) !== '"') { heredoc = _ref2.heredoc;
escapeQuotes = _ref2.escapeQuotes;
quote = str.charAt(0);
if (quote !== '"' || str.length < 3) {
return this.token('STRING', str); return this.token('STRING', str);
} else {
lexer = new Lexer;
tokens = [];
quote = str.charAt(0);
_ref2 = [1, 1];
i = _ref2[0];
pi = _ref2[1];
end = str.length - 1;
while (i < end) {
if (str.charAt(i) === '\\') {
i += 1;
} else if (expr = this.balancedString(str.slice(i), [['#{', '}']])) {
if (pi < i) {
tokens.push(['STRING', quote + str.slice(pi, i) + quote]);
}
inner = expr.slice(2, -1);
if (inner.length) {
if (options.heredoc) {
inner = inner.replace(new RegExp('\\\\' + quote, 'g'), quote);
}
nested = lexer.tokenize("(" + (inner) + ")", {
line: this.line
});
_ref2 = nested;
for (idx = 0, _len = _ref2.length; idx < _len; idx++) {
tok = _ref2[idx];
if (tok[0] === 'CALL_END') {
(tok[0] = ')');
}
}
nested.pop();
tokens.push(['TOKENS', nested]);
} else {
tokens.push(['STRING', quote + quote]);
}
i += expr.length - 1;
pi = i + 1;
}
i += 1;
}
if ((i > pi) && (pi < str.length - 1)) {
tokens.push(['STRING', quote + str.slice(pi, i) + quote]);
}
if (tokens[0][0] !== 'STRING') {
tokens.unshift(['STRING', '""']);
}
interpolated = tokens.length > 1;
if (interpolated) {
this.token('(', '(');
}
_ref2 = tokens;
for (i = 0, _len = _ref2.length; i < _len; i++) {
token = _ref2[i];
_ref3 = token;
tag = _ref3[0];
value = _ref3[1];
if (tag === 'TOKENS') {
this.tokens = this.tokens.concat(value);
} else if (tag === 'STRING' && options.escapeQuotes) {
escaped = value.slice(1, -1).replace(/"/g, '\\"');
this.token(tag, "\"" + (escaped) + "\"");
} else {
this.token(tag, value);
}
if (i < tokens.length - 1) {
this.token('+', '+');
}
}
if (interpolated) {
this.token(')', ')');
}
return tokens;
} }
lexer = new Lexer;
tokens = [];
i = (pi = 1);
end = str.length - 1;
while (i < end) {
if (str.charAt(i) === '\\') {
i += 1;
} else if (expr = this.balancedString(str.slice(i), [['#{', '}']])) {
if (pi < i) {
s = quote + this.escapeLines(str.slice(pi, i), heredoc) + quote;
tokens.push(['STRING', s]);
}
inner = expr.slice(2, -1).replace(/^[ \t]*\n/, '');
if (inner.length) {
if (heredoc) {
inner = inner.replace(RegExp('\\\\' + quote, 'g'), quote);
}
nested = lexer.tokenize("(" + (inner) + ")", {
line: this.line
});
_ref2 = nested;
for (idx = 0, _len = _ref2.length; idx < _len; idx++) {
tok = _ref2[idx];
if (tok[0] === 'CALL_END') {
(tok[0] = ')');
}
}
nested.pop();
tokens.push(['TOKENS', nested]);
} else {
tokens.push(['STRING', quote + quote]);
}
i += expr.length - 1;
pi = i + 1;
}
i += 1;
}
if ((i > pi) && (pi < str.length - 1)) {
s = str.slice(pi, i).replace(MULTILINER, heredoc ? '\\n' : '');
tokens.push(['STRING', quote + s + quote]);
}
if (tokens[0][0] !== 'STRING') {
tokens.unshift(['STRING', '""']);
}
interpolated = tokens.length > 1;
if (interpolated) {
this.token('(', '(');
}
_ref2 = tokens;
push = _ref2.push;
_ref2 = tokens;
for (i = 0, _len = _ref2.length; i < _len; i++) {
token = _ref2[i];
_ref3 = token;
tag = _ref3[0];
value = _ref3[1];
if (tag === 'TOKENS') {
push.apply(this.tokens, value);
} else if (tag === 'STRING' && escapeQuotes) {
escaped = value.slice(1, -1).replace(/"/g, '\\"');
this.token(tag, "\"" + (escaped) + "\"");
} else {
this.token(tag, value);
}
if (i < tokens.length - 1) {
this.token('+', '+');
}
}
if (interpolated) {
this.token(')', ')');
}
return tokens;
}; };
Lexer.prototype.token = function(tag, value) { Lexer.prototype.token = function(tag, value) {
return this.tokens.push([tag, value, this.line]); return this.tokens.push([tag, value, this.line]);
@@ -573,15 +584,12 @@
Lexer.prototype.prev = function(index) { Lexer.prototype.prev = function(index) {
return this.tokens[this.tokens.length - (index || 1)]; return this.tokens[this.tokens.length - (index || 1)];
}; };
Lexer.prototype.match = function(regex, index) {
var m;
return (m = this.chunk.match(regex)) ? m[index || 0] : false;
};
Lexer.prototype.unfinished = function() { Lexer.prototype.unfinished = function() {
var prev, value; var prev, value;
prev = this.prev(2); return (prev = this.prev(2)) && prev[0] !== '.' && (value = this.value()) && NO_NEWLINE.test(value) && !CODE.test(value) && !ASSIGNED.test(this.chunk);
value = this.value(); };
return value && NO_NEWLINE.test(value) && prev && prev[0] !== '.' && !CODE.test(value) && !ASSIGNED.test(this.chunk); Lexer.prototype.escapeLines = function(str, heredoc) {
return str.replace(MULTILINER, heredoc ? '\\n' : '');
}; };
return Lexer; return Lexer;
})(); })();
@@ -591,11 +599,11 @@
RESERVED = ['case', 'default', 'do', 'function', 'var', 'void', 'with', 'const', 'let', 'enum', 'export', 'import', 'native', '__hasProp', '__extends', '__slice']; RESERVED = ['case', 'default', 'do', 'function', 'var', 'void', 'with', 'const', 'let', 'enum', 'export', 'import', 'native', '__hasProp', '__extends', '__slice'];
JS_FORBIDDEN = JS_KEYWORDS.concat(RESERVED); JS_FORBIDDEN = JS_KEYWORDS.concat(RESERVED);
IDENTIFIER = /^[a-zA-Z_$][\w$]*/; IDENTIFIER = /^[a-zA-Z_$][\w$]*/;
NUMBER = /^(?:0x[\da-f]+)|^(?:\d+(\.\d+)?|\.\d+)(?:e[+-]?\d+)?/i; NUMBER = /^0x[\da-f]+|^(?:\d+(\.\d+)?|\.\d+)(?:e[+-]?\d+)?/i;
HEREDOC = /^("""|''')([\s\S]*?)\n?[ \t]*\1/; HEREDOC = /^("""|''')([\s\S]*?)\n?[ \t]*\1/;
OPERATOR = /^(?:-[-=>]?|\+[+=]?|[*&|\/%=<>^:!?]+)(?=([ \t]*))/; OPERATOR = /^(?:-[-=>]?|\+[+=]?|[*&|\/%=<>^:!?]+)(?=([ \t]*))/;
WHITESPACE = /^[ \t]+/; WHITESPACE = /^[ \t]+/;
COMMENT = /^###([^#][\s\S]*?)(?:###[ \t]*\n|(?:###)?$)|^(?:\s*#(?!##[^#])[^\n]*)+/; COMMENT = /^###([^#][\s\S]*?)(?:###[ \t]*\n|(?:###)?$)|^(?:\s*#(?!##[^#]).*)+/;
CODE = /^[-=]>/; CODE = /^[-=]>/;
MULTI_DENT = /^(?:\n[ \t]*)+/; MULTI_DENT = /^(?:\n[ \t]*)+/;
SIMPLESTR = /^'[^\\']*(?:\\.[^\\']*)*'/; SIMPLESTR = /^'[^\\']*(?:\\.[^\\']*)*'/;
@@ -606,9 +614,9 @@
REGEX_ESCAPE = /\\[^#]/g; REGEX_ESCAPE = /\\[^#]/g;
MULTILINER = /\n/g; MULTILINER = /\n/g;
NO_NEWLINE = /^(?:[-+*&|\/%=<>!.\\][<>=&|]*|and|or|is(?:nt)?|n(?:ot|ew)|delete|typeof|instanceof)$/; NO_NEWLINE = /^(?:[-+*&|\/%=<>!.\\][<>=&|]*|and|or|is(?:nt)?|n(?:ot|ew)|delete|typeof|instanceof)$/;
HEREDOC_INDENT = /\n+([ \t]*)|^([ \t]+)/g; HEREDOC_INDENT = /\n+([ \t]*)/g;
ASSIGNED = /^\s*((?:[a-zA-Z$_@]\w*|["'][^\n]+?["']|\d+)[ \t]*?[:=][^:=])/; ASSIGNED = /^\s*@?[$A-Za-z_][$\w]*[ \t]*?[:=][^:=>]/;
NEXT_CHARACTER = /^\s*(\S)/; NEXT_CHARACTER = /^\s*(\S?)/;
COMPOUND_ASSIGN = ['-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?=', '<<=', '>>=', '>>>=', '&=', '^=', '|=']; COMPOUND_ASSIGN = ['-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?=', '<<=', '>>=', '>>>=', '&=', '^=', '|='];
UNARY = ['UMINUS', 'UPLUS', '!', '!!', '~', 'NEW', 'TYPEOF', 'DELETE']; UNARY = ['UMINUS', 'UPLUS', '!', '!!', '~', 'NEW', 'TYPEOF', 'DELETE'];
LOGIC = ['&', '|', '^', '&&', '||']; LOGIC = ['&', '|', '^', '&&', '||'];

View File

@@ -10,7 +10,7 @@
}; };
_ref = require('./scope'); _ref = require('./scope');
Scope = _ref.Scope; Scope = _ref.Scope;
_ref = require('./helpers').helpers; _ref = require('./helpers');
compact = _ref.compact; compact = _ref.compact;
flatten = _ref.flatten; flatten = _ref.flatten;
merge = _ref.merge; merge = _ref.merge;

View File

@@ -1,7 +1,7 @@
(function() { (function() {
var CoffeeScript, helpers, readline, repl, run, stdio; var CoffeeScript, helpers, readline, repl, run, stdio;
CoffeeScript = require('./coffee-script'); CoffeeScript = require('./coffee-script');
helpers = require('./helpers').helpers; helpers = require('./helpers');
readline = require('readline'); readline = require('readline');
stdio = process.openStdin(); stdio = process.openStdin();
helpers.extend(global, { helpers.extend(global, {

View File

@@ -1,7 +1,7 @@
(function() { (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, _ref, _result, include, pair; 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, _ref, _result, include, pair;
var __hasProp = Object.prototype.hasOwnProperty; var __hasProp = Object.prototype.hasOwnProperty;
_ref = require('./helpers').helpers; _ref = require('./helpers');
include = _ref.include; include = _ref.include;
exports.Rewriter = (function() { exports.Rewriter = (function() {
Rewriter = function() {}; Rewriter = function() {};

View File

@@ -1,7 +1,7 @@
(function() { (function() {
var Scope, _ref, extend; var Scope, _ref, extend;
var __hasProp = Object.prototype.hasOwnProperty; var __hasProp = Object.prototype.hasOwnProperty;
_ref = require('./helpers').helpers; _ref = require('./helpers');
extend = _ref.extend; extend = _ref.extend;
exports.Scope = (function() { exports.Scope = (function() {
Scope = function(parent, expressions, method) { Scope = function(parent, expressions, method) {

View File

@@ -9,7 +9,7 @@
# External dependencies. # External dependencies.
fs = require 'fs' fs = require 'fs'
path = require 'path' path = require 'path'
helpers = require('./helpers').helpers helpers = require './helpers'
optparse = require './optparse' optparse = require './optparse'
CoffeeScript = require './coffee-script' CoffeeScript = require './coffee-script'

View File

@@ -9,7 +9,7 @@ fs = require 'fs'
path = require 'path' path = require 'path'
optparse = require './optparse' optparse = require './optparse'
CoffeeScript = require './coffee-script' CoffeeScript = require './coffee-script'
{helpers} = require './helpers' helpers = require './helpers'
{spawn, exec} = require 'child_process' {spawn, exec} = require 'child_process'
{EventEmitter} = require 'events' {EventEmitter} = require 'events'

View File

@@ -2,66 +2,62 @@
# the **Lexer**, **Rewriter**, and the **Nodes**. Merge objects, flatten # the **Lexer**, **Rewriter**, and the **Nodes**. Merge objects, flatten
# arrays, count characters, that sort of thing. # arrays, count characters, that sort of thing.
helpers = exports.helpers = {} # Cross-engine `indexOf`, so that JScript can join the party. Use SpiderMonkey's
# functional-style `indexOf`, if it's available.
# Cross-browser indexOf, so that IE can join the party. indexOf = exports.indexOf = Array.indexOf or
helpers.indexOf = indexOf = (array, item, from) -> if Array::indexOf
return array.indexOf item, from if array.indexOf (array, item, from) -> array.indexOf item, from
for other, index in array else
if other is item and (not from or (from <= index)) (array, item, from) ->
return index for other, index in array
-1 if other is item and (not from or from <= index)
return index
-1
# Does a list include a value? # Does a list include a value?
helpers.include = include = (list, value) -> exports.include = (list, value) ->
indexOf(list, value) >= 0 indexOf(list, value) >= 0
# Peek at the beginning of a given string to see if it matches a sequence. # Peek at the beginning of a given string to see if it matches a sequence.
helpers.starts = starts = (string, literal, start) -> exports.starts = (string, literal, start) ->
string.substring(start, (start or 0) + literal.length) is literal literal is string.substr start, literal.length
# Peek at the end of a given string to see if it matches a sequence. # Peek at the end of a given string to see if it matches a sequence.
helpers.ends = ends = (string, literal, back) -> exports.ends = (string, literal, back) ->
start = string.length - literal.length - (back ? 0) len = literal.length
string.substring(start, start + literal.length) is literal literal is string.substr string.length - len - (back or 0), len
# Trim out all falsy values from an array. # Trim out all falsy values from an array.
helpers.compact = compact = (array) -> item for item in array when item exports.compact = (array) ->
item for item in array when item
# Count the number of occurences of a character in a string. # Count the number of occurences of a character in a string.
helpers.count = count = (string, letter) -> exports.count = (string, letter) ->
num = 0 num = pos = 0
pos = indexOf string, letter num++ while 0 < pos = 1 + string.indexOf letter, pos
while pos isnt -1
num += 1
pos = indexOf string, letter, pos + 1
num num
# Merge objects, returning a fresh copy with attributes from both sides. # Merge objects, returning a fresh copy with attributes from both sides.
# Used every time `BaseNode#compile` is called, to allow properties in the # Used every time `BaseNode#compile` is called, to allow properties in the
# options hash to propagate down the tree without polluting other branches. # options hash to propagate down the tree without polluting other branches.
helpers.merge = merge = (options, overrides) -> exports.merge = (options, overrides) ->
fresh = {} extend (extend {}, options), overrides
(fresh[key] = val) for all key, val of options
(fresh[key] = val) for all key, val of overrides if overrides
fresh
# Extend a source object with the properties of another object (shallow copy). # Extend a source object with the properties of another object (shallow copy).
# We use this to simulate Node's deprecated `process.mixin` # We use this to simulate Node's deprecated `process.mixin`.
helpers.extend = extend = (object, properties) -> extend = exports.extend = (object, properties) ->
(object[key] = val) for all key, val of properties for all key, val of properties
object[key] = val
object
# Return a completely flattened version of an array. Handy for getting a # Return a flattened version of an array (shallow and nonrecursive).
# list of `children` from the nodes. # Handy for getting a list of `children` from the nodes.
helpers.flatten = flatten = (array) -> exports.flatten = (array) ->
memo = [] array.concat.apply [], array
for item in array
if item instanceof Array then memo = memo.concat(item) else memo.push(item)
memo
# Delete a key from an object, returning the value. Useful when a node is # Delete a key from an object, returning the value. Useful when a node is
# looking for a particular method in an options hash. # looking for a particular method in an options hash.
helpers.del = del = (obj, key) -> exports.del = (obj, key) ->
val = obj[key] val = obj[key]
delete obj[key] delete obj[key]
val val

View File

@@ -10,7 +10,7 @@
{Rewriter} = require './rewriter' {Rewriter} = require './rewriter'
# Import the helpers we need. # Import the helpers we need.
{include, count, starts, compact} = require('./helpers').helpers {include, count, starts, compact} = require './helpers'
# The Lexer Class # The Lexer Class
# --------------- # ---------------
@@ -43,27 +43,24 @@ exports.Lexer = class Lexer
@outdebt = 0 # The under-outdentation at the current level. @outdebt = 0 # The under-outdentation at the current level.
@indents = [] # The stack of all current indentation levels. @indents = [] # The stack of all current indentation levels.
@tokens = [] # Stream of parsed tokens in the form ['TYPE', value, line] @tokens = [] # Stream of parsed tokens in the form ['TYPE', value, line]
# At every position, run through this list of attempted matches,
# short-circuiting if any of them succeed. Their order determines precedence:
# `@literalToken` is the fallback catch-all.
while (@chunk = code[@i..]) while (@chunk = code[@i..])
@extractNextToken() @identifierToken() or
@commentToken() or
@whitespaceToken() or
@lineToken() or
@heredocToken() or
@stringToken() or
@numberToken() or
@regexToken() or
@jsToken() or
@literalToken()
@closeIndentation() @closeIndentation()
return @tokens if o.rewrite is off return @tokens if o.rewrite is off
(new Rewriter).rewrite @tokens (new Rewriter).rewrite @tokens
# At every position, run through this list of attempted matches,
# short-circuiting if any of them succeed. Their order determines precedence:
# `@literalToken` is the fallback catch-all.
extractNextToken: ->
@identifierToken() or
@commentToken() or
@whitespaceToken() or
@lineToken() or
@heredocToken() or
@stringToken() or
@numberToken() or
@regexToken() or
@jsToken() or
@literalToken()
# Tokenizers # Tokenizers
# ---------- # ----------
@@ -74,12 +71,13 @@ exports.Lexer = class Lexer
# referenced as property names here, so you can still do `jQuery.is()` even # referenced as property names here, so you can still do `jQuery.is()` even
# though `is` means `===` otherwise. # though `is` means `===` otherwise.
identifierToken: -> identifierToken: ->
return false unless id = @match IDENTIFIER return false unless match = IDENTIFIER.exec @chunk
id = match[0]
@i += id.length @i += id.length
if id is 'all' and @tag() is 'FOR' if id is 'all' and @tag() is 'FOR'
@token 'ALL', id @token 'ALL', id
return true return true
forcedIdentifier = @tagAccessor() or @match ASSIGNED, 1 forcedIdentifier = @tagAccessor() or ASSIGNED.test @chunk
tag = 'IDENTIFIER' tag = 'IDENTIFIER'
if include(JS_KEYWORDS, id) or if include(JS_KEYWORDS, id) or
not forcedIdentifier and include(COFFEE_KEYWORDS, id) not forcedIdentifier and include(COFFEE_KEYWORDS, id)
@@ -111,7 +109,8 @@ exports.Lexer = class Lexer
# Matches numbers, including decimals, hex, and exponential notation. # Matches numbers, including decimals, hex, and exponential notation.
# Be careful not to interfere with ranges-in-progress. # Be careful not to interfere with ranges-in-progress.
numberToken: -> numberToken: ->
return false unless number = @match NUMBER return false unless match = NUMBER.exec @chunk
number = match[0]
return false if @tag() is '.' and number.charAt(0) is '.' return false if @tag() is '.' and number.charAt(0) is '.'
@i += number.length @i += number.length
@token 'NUMBER', number @token 'NUMBER', number
@@ -122,11 +121,11 @@ exports.Lexer = class Lexer
stringToken: -> stringToken: ->
switch @chunk.charAt 0 switch @chunk.charAt 0
when "'" when "'"
return false unless string = @match SIMPLESTR return false unless match = SIMPLESTR.exec @chunk
@token 'STRING', string.replace MULTILINER, '\\\n' @token 'STRING', (string = match[0]).replace MULTILINER, '\\\n'
when '"' when '"'
return false unless string = @balancedToken ['"', '"'], ['#{', '}'] return false unless string = @balancedToken ['"', '"'], ['#{', '}']
@interpolateString string.replace MULTILINER, '\\\n' @interpolateString string
else else
return false return false
@line += count string, '\n' @line += count string, '\n'
@@ -139,7 +138,7 @@ exports.Lexer = class Lexer
return false unless match = @chunk.match HEREDOC return false unless match = @chunk.match HEREDOC
heredoc = match[0] heredoc = match[0]
quote = heredoc.charAt 0 quote = heredoc.charAt 0
doc = @sanitizeHeredoc match[2], {quote} doc = @sanitizeHeredoc match[2], {quote, indent: null}
@interpolateString quote + doc + quote, heredoc: yes @interpolateString quote + doc + quote, heredoc: yes
@line += count heredoc, '\n' @line += count heredoc, '\n'
@i += heredoc.length @i += heredoc.length
@@ -159,8 +158,8 @@ exports.Lexer = class Lexer
# Matches JavaScript interpolated directly into the source via backticks. # Matches JavaScript interpolated directly into the source via backticks.
jsToken: -> jsToken: ->
return false unless @chunk.charAt(0) is '`' and script = @match JSTOKEN return false unless @chunk.charAt(0) is '`' and match = JSTOKEN.exec @chunk
@token 'JS', script.slice 1, -1 @token 'JS', (script = match[0]).slice 1, -1
@i += script.length @i += script.length
true true
@@ -203,13 +202,14 @@ exports.Lexer = class Lexer
# Keeps track of the level of indentation, because a single outdent token # Keeps track of the level of indentation, because a single outdent token
# can close multiple indents, so we need to know how far in we happen to be. # can close multiple indents, so we need to know how far in we happen to be.
lineToken: -> lineToken: ->
return false unless indent = @match MULTI_DENT return false unless match = MULTI_DENT.exec @chunk
indent = match[0]
@line += count indent, '\n' @line += count indent, '\n'
@i += indent.length @i += indent.length
prev = @prev 2 prev = @prev 2
size = indent.length - 1 - indent.lastIndexOf '\n' size = indent.length - 1 - indent.lastIndexOf '\n'
nextCharacter = @match NEXT_CHARACTER, 1 nextCharacter = NEXT_CHARACTER.exec(@chunk)[1]
noNewlines = nextCharacter is '.' or nextCharacter is ',' or @unfinished() noNewlines = (nextCharacter in ['.', ',']) or @unfinished()
if size - @indebt is @indent if size - @indebt is @indent
return @suppressNewlines() if noNewlines return @suppressNewlines() if noNewlines
return @newlineToken indent return @newlineToken indent
@@ -253,10 +253,10 @@ exports.Lexer = class Lexer
# Matches and consumes non-meaningful whitespace. Tag the previous token # Matches and consumes non-meaningful whitespace. Tag the previous token
# as being "spaced", because there are some cases where it makes a difference. # as being "spaced", because there are some cases where it makes a difference.
whitespaceToken: -> whitespaceToken: ->
return false unless space = @match WHITESPACE return false unless match = WHITESPACE.exec @chunk
prev = @prev() prev = @prev()
prev.spaced = true if prev prev.spaced = true if prev
@i += space.length @i += match[0].length
true true
# Generate a newline token. Consecutive newlines get merged together. # Generate a newline token. Consecutive newlines get merged together.
@@ -331,18 +331,17 @@ exports.Lexer = class Lexer
# Sanitize a heredoc or herecomment by escaping internal double quotes and # Sanitize a heredoc or herecomment by escaping internal double quotes and
# erasing all external indentation on the left-hand side. # erasing all external indentation on the left-hand side.
sanitizeHeredoc: (doc, options) -> sanitizeHeredoc: (doc, options) ->
indent = options.indent {indent, herecomment} = options
return doc if options.herecomment and not include doc, '\n' return doc if herecomment and not include doc, '\n'
unless options.herecomment unless herecomment
while (match = HEREDOC_INDENT.exec doc) while (match = HEREDOC_INDENT.exec doc)
attempt = if match[1]? then match[1] else match[2] attempt = match[1]
indent = attempt if not indent? or 0 < attempt.length < indent.length indent = attempt if indent is null or 0 < attempt.length < indent.length
indent or= '' doc = doc.replace /\n#{ indent }/g, '\n' if indent
doc = doc.replace(new RegExp('^' + indent, 'gm'), '') return doc if herecomment
return doc if options.herecomment doc = doc.replace(/^\n/, '').replace(/#{ options.quote }/g, '\\$&')
doc.replace(/^\n/, '') doc = @escapeLines doc, yes if options.quote is "'"
.replace(MULTILINER, '\\n') doc
.replace(new RegExp(options.quote, 'g'), "\\#{options.quote}")
# A source of ambiguity in our grammar used to be parameter lists in function # A source of ambiguity in our grammar used to be parameter lists in function
# definitions versus argument lists in function calls. Walk backwards, tagging # definitions versus argument lists in function calls. Walk backwards, tagging
@@ -407,8 +406,7 @@ exports.Lexer = class Lexer
if not i then false else str[0...i] if not i then false else str[0...i]
# Expand variables and expressions inside double-quoted strings using # Expand variables and expressions inside double-quoted strings using
# [ECMA Harmony's interpolation syntax](http://wiki.ecmascript.org/doku.php?id=strawman:string_interpolation) # Ruby-like notation for substitution of arbitrary expressions.
# for substitution of bare variables as well as arbitrary expressions.
# #
# "Hello #{name.capitalize()}." # "Hello #{name.capitalize()}."
# #
@@ -416,48 +414,51 @@ exports.Lexer = class Lexer
# new Lexer, tokenize the interpolated contents, and merge them into the # new Lexer, tokenize the interpolated contents, and merge them into the
# token stream. # token stream.
interpolateString: (str, options) -> interpolateString: (str, options) ->
options or= {} {heredoc, escapeQuotes} = options or {}
if str.length < 3 or str.charAt(0) isnt '"' quote = str.charAt 0
@token 'STRING', str return @token 'STRING', str if quote isnt '"' or str.length < 3
else lexer = new Lexer
lexer = new Lexer tokens = []
tokens = [] i = pi = 1
quote = str.charAt 0 end = str.length - 1
[i, pi] = [1, 1] while i < end
end = str.length - 1 if str.charAt(i) is '\\'
while i < end
if str.charAt(i) is '\\'
i += 1
else if expr = @balancedString str[i..], [['#{', '}']]
tokens.push ['STRING', quote + str[pi...i] + quote] if pi < i
inner = expr.slice 2, -1
if inner.length
inner = inner.replace new RegExp('\\\\' + quote, 'g'), quote if options.heredoc
nested = lexer.tokenize "(#{inner})", line: @line
(tok[0] = ')') for tok, idx in nested when tok[0] is 'CALL_END'
nested.pop()
tokens.push ['TOKENS', nested]
else
tokens.push ['STRING', quote + quote]
i += expr.length - 1
pi = i + 1
i += 1 i += 1
tokens.push ['STRING', quote + str[pi...i] + quote] if i > pi < str.length - 1 else if expr = @balancedString str[i..], [['#{', '}']]
tokens.unshift ['STRING', '""'] unless tokens[0][0] is 'STRING' if pi < i
interpolated = tokens.length > 1 s = quote + @escapeLines(str[pi...i], heredoc) + quote
@token '(', '(' if interpolated tokens.push ['STRING', s]
for token, i in tokens inner = expr.slice(2, -1).replace /^[ \t]*\n/, ''
[tag, value] = token if inner.length
if tag is 'TOKENS' inner = inner.replace RegExp('\\\\' + quote, 'g'), quote if heredoc
@tokens = @tokens.concat value nested = lexer.tokenize "(#{inner})", line: @line
else if tag is 'STRING' and options.escapeQuotes (tok[0] = ')') for tok, idx in nested when tok[0] is 'CALL_END'
escaped = value.slice(1, -1).replace(/"/g, '\\"') nested.pop()
@token tag, "\"#{escaped}\"" tokens.push ['TOKENS', nested]
else else
@token tag, value tokens.push ['STRING', quote + quote]
@token '+', '+' if i < tokens.length - 1 i += expr.length - 1
@token ')', ')' if interpolated pi = i + 1
tokens i += 1
if i > pi < str.length - 1
s = str[pi...i].replace MULTILINER, if heredoc then '\\n' else ''
tokens.push ['STRING', quote + s + quote]
tokens.unshift ['STRING', '""'] unless tokens[0][0] is 'STRING'
interpolated = tokens.length > 1
@token '(', '(' if interpolated
{push} = tokens
for token, i in tokens
[tag, value] = token
if tag is 'TOKENS'
push.apply @tokens, value
else if tag is 'STRING' and escapeQuotes
escaped = value.slice(1, -1).replace(/"/g, '\\"')
@token tag, "\"#{escaped}\""
else
@token tag, value
@token '+', '+' if i < tokens.length - 1
@token ')', ')' if interpolated
tokens
# Helpers # Helpers
# ------- # -------
@@ -482,18 +483,15 @@ exports.Lexer = class Lexer
prev: (index) -> prev: (index) ->
@tokens[@tokens.length - (index or 1)] @tokens[@tokens.length - (index or 1)]
# Attempt to match a string against the current chunk, returning the indexed
# match if successful, and `false` otherwise.
match: (regex, index) ->
if m = @chunk.match regex then m[index or 0] else false
# Are we in the midst of an unfinished expression? # Are we in the midst of an unfinished expression?
unfinished: -> unfinished: ->
prev = @prev 2 (prev = @prev 2 ) and prev[0] isnt '.' and
value = @value() (value = @value()) and NO_NEWLINE.test(value) and not CODE.test(value) and
value and NO_NEWLINE.test(value) and not ASSIGNED.test(@chunk)
prev and prev[0] isnt '.' and not CODE.test(value) and
not ASSIGNED.test(@chunk) # Converts newlines for string literals.
escapeLines: (str, heredoc) ->
str.replace MULTILINER, if heredoc then '\\n' else ''
# Constants # Constants
# --------- # ---------
@@ -535,11 +533,11 @@ JS_FORBIDDEN = JS_KEYWORDS.concat RESERVED
# Token matching regexes. # Token matching regexes.
IDENTIFIER = /^[a-zA-Z_$][\w$]*/ IDENTIFIER = /^[a-zA-Z_$][\w$]*/
NUMBER = /^(?:0x[\da-f]+)|^(?:\d+(\.\d+)?|\.\d+)(?:e[+-]?\d+)?/i NUMBER = /^0x[\da-f]+|^(?:\d+(\.\d+)?|\.\d+)(?:e[+-]?\d+)?/i
HEREDOC = /^("""|''')([\s\S]*?)\n?[ \t]*\1/ HEREDOC = /^("""|''')([\s\S]*?)\n?[ \t]*\1/
OPERATOR = /^(?:-[-=>]?|\+[+=]?|[*&|\/%=<>^:!?]+)(?=([ \t]*))/ OPERATOR = /^(?:-[-=>]?|\+[+=]?|[*&|\/%=<>^:!?]+)(?=([ \t]*))/
WHITESPACE = /^[ \t]+/ WHITESPACE = /^[ \t]+/
COMMENT = /^###([^#][\s\S]*?)(?:###[ \t]*\n|(?:###)?$)|^(?:\s*#(?!##[^#])[^\n]*)+/ COMMENT = /^###([^#][\s\S]*?)(?:###[ \t]*\n|(?:###)?$)|^(?:\s*#(?!##[^#]).*)+/
CODE = /^[-=]>/ CODE = /^[-=]>/
MULTI_DENT = /^(?:\n[ \t]*)+/ MULTI_DENT = /^(?:\n[ \t]*)+/
SIMPLESTR = /^'[^\\']*(?:\\.[^\\']*)*'/ SIMPLESTR = /^'[^\\']*(?:\\.[^\\']*)*'/
@@ -554,9 +552,9 @@ REGEX_ESCAPE = /\\[^#]/g
# Token cleaning regexes. # Token cleaning regexes.
MULTILINER = /\n/g MULTILINER = /\n/g
NO_NEWLINE = /^(?:[-+*&|\/%=<>!.\\][<>=&|]*|and|or|is(?:nt)?|n(?:ot|ew)|delete|typeof|instanceof)$/ NO_NEWLINE = /^(?:[-+*&|\/%=<>!.\\][<>=&|]*|and|or|is(?:nt)?|n(?:ot|ew)|delete|typeof|instanceof)$/
HEREDOC_INDENT = /\n+([ \t]*)|^([ \t]+)/g HEREDOC_INDENT = /\n+([ \t]*)/g
ASSIGNED = /^\s*((?:[a-zA-Z$_@]\w*|["'][^\n]+?["']|\d+)[ \t]*?[:=][^:=])/ ASSIGNED = /^\s*@?[$A-Za-z_][$\w]*[ \t]*?[:=][^:=>]/
NEXT_CHARACTER = /^\s*(\S)/ NEXT_CHARACTER = /^\s*(\S?)/
# Compound assignment tokens. # Compound assignment tokens.
COMPOUND_ASSIGN = ['-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?=', '<<=', '>>=', '>>>=', '&=', '^=', '|='] COMPOUND_ASSIGN = ['-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?=', '<<=', '>>=', '>>>=', '&=', '^=', '|=']

View File

@@ -6,7 +6,7 @@
{Scope} = require './scope' {Scope} = require './scope'
# Import the helpers we plan to use. # Import the helpers we plan to use.
{compact, flatten, merge, del, include, indexOf, starts, ends} = require('./helpers').helpers {compact, flatten, merge, del, include, indexOf, starts, ends} = require './helpers'
#### BaseNode #### BaseNode

View File

@@ -6,7 +6,7 @@
# Require the **coffee-script** module to get access to the compiler. # Require the **coffee-script** module to get access to the compiler.
CoffeeScript = require './coffee-script' CoffeeScript = require './coffee-script'
helpers = require('./helpers').helpers helpers = require './helpers'
readline = require 'readline' readline = require 'readline'
# Start by opening up **stdio**. # Start by opening up **stdio**.

View File

@@ -6,7 +6,7 @@
# parentheses, balance incorrect nestings, and generally clean things up. # parentheses, balance incorrect nestings, and generally clean things up.
# Import the helpers we need. # Import the helpers we need.
{include} = require('./helpers').helpers {include} = require './helpers'
# The **Rewriter** class is used by the [Lexer](lexer.html), directly against # The **Rewriter** class is used by the [Lexer](lexer.html), directly against
# its internal array of tokens. # its internal array of tokens.

View File

@@ -6,7 +6,7 @@
# with the outside. # with the outside.
# Import the helpers we plan to use. # Import the helpers we plan to use.
{extend} = require('./helpers').helpers {extend} = require './helpers'
exports.Scope = class Scope exports.Scope = class Scope

46
test/test_helpers.coffee Normal file
View File

@@ -0,0 +1,46 @@
{indexOf, include, starts, ends, compact, count, merge, extend, flatten, del} = require '../lib/helpers'
# Test `indexOf`
array = [0..4]
ok indexOf(array, 0) is 0
ok indexOf(array, 2) is 2
ok indexOf(array, 4) is 4
ok indexOf(array, 6) is -1
# Test `include`
ok include array, 0
ok include array, 2
ok include array, 4
ok not include array, 6
# Test `starts`
string = array.join ''
ok starts string, '012'
ok starts string, '34', 3
ok not starts string, '42'
ok not starts string, '42', 6
# Test `ends`
ok ends string, '234'
ok ends string, '01', 3
ok not ends string, '42'
ok not ends string, '42', 6
# Test `merge`
object = {}
merged = merge object, array
ok merged isnt object
ok merged[3] is 3
# Test `extend`
ok object is extend object, array
ok object[3] is 3
# Test `flatten`
ok "#{ flatten [0, [1, 2], 3, [4]] }" is "#{ array }"
ok 1 is del object, 1
ok 1 not of object

View File

@@ -88,3 +88,21 @@ a = """
""" """
ok a is "one\ntwo\n" ok a is "one\ntwo\n"
equal ''' line 0
should not be relevant
to the indent level
''', '
line 0\n
should not be relevant\n
to the indent level
'
equal 'multiline nested interpolations work', """multiline #{
"nested #{(->
ok yes
"interpolations"
)()}"
} work"""