mirror of
https://github.com/jashkenas/coffeescript.git
synced 2026-04-11 03:00:13 -04:00
unary-new: merged master
This commit is contained in:
5
Cakefile
5
Cakefile
@@ -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']
|
||||||
|
|||||||
6
bin/cake
6
bin/cake
@@ -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();
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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 = {};
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
252
lib/lexer.js
252
lib/lexer.js
@@ -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 = ['&', '|', '^', '&&', '||'];
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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, {
|
||||||
|
|||||||
@@ -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() {};
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
196
src/lexer.coffee
196
src/lexer.coffee
@@ -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 = ['-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?=', '<<=', '>>=', '>>>=', '&=', '^=', '|=']
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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**.
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
46
test/test_helpers.coffee
Normal 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
|
||||||
@@ -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"""
|
||||||
|
|||||||
Reference in New Issue
Block a user