1
0
Fork 0
mirror of https://github.com/jashkenas/coffeescript.git synced 2022-11-09 12:23:24 -05:00

Merge remote-tracking branch 'origin/master'

Conflicts:
	lib/coffee-script/coffee-script.js
	lib/coffee-script/lexer.js
	lib/coffee-script/parser.js
	src/lexer.coffee
This commit is contained in:
Jason Walton 2013-01-14 14:26:06 -05:00
commit a1ba0a89f8
31 changed files with 613 additions and 445 deletions

1
.gitignore vendored
View file

@ -1,6 +1,7 @@
raw raw
presentation presentation
test.coffee test.coffee
test.litcoffee
parser.output parser.output
test/fixtures/underscore test/fixtures/underscore
test/*.js test/*.js

View file

@ -68,7 +68,7 @@ task 'install', 'install CoffeeScript into /usr/local (or --prefix)', (options)
task 'build', 'build the CoffeeScript language from source', build = (cb) -> task 'build', 'build the CoffeeScript language from source', build = (cb) ->
files = fs.readdirSync 'src' files = fs.readdirSync 'src'
files = ('src/' + file for file in files when file.match(/\.coffee$/)) files = ('src/' + file for file in files when file.match(/\.(lit)?coffee$/))
run ['-c', '-o', 'lib/coffee-script'].concat(files), cb run ['-c', '-o', 'lib/coffee-script'].concat(files), cb
@ -119,8 +119,7 @@ task 'build:browser', 'rebuild the merged script for inclusion in the browser',
}(this)); }(this));
""" """
unless process.env.MINIFY is 'false' unless process.env.MINIFY is 'false'
{parser, uglify} = require 'uglify-js' {code} = require('uglify-js').minify code, fromString: true
code = uglify.gen_code uglify.ast_squeeze uglify.ast_mangle parser.parse code
fs.writeFileSync 'extras/coffee-script.js', header + '\n' + code fs.writeFileSync 'extras/coffee-script.js', header + '\n' + code
console.log "built ... running browser tests:" console.log "built ... running browser tests:"
invoke 'test:browser' invoke 'test:browser'
@ -227,11 +226,12 @@ runTests = (CoffeeScript) ->
# Run every test in the `test` folder, recording failures. # Run every test in the `test` folder, recording failures.
files = fs.readdirSync 'test' files = fs.readdirSync 'test'
for file in files when file.match /\.coffee$/i for file in files when file.match /\.(lit)?coffee$/i
literate = path.extname(file) is '.litcoffee'
currentFile = filename = path.join 'test', file currentFile = filename = path.join 'test', file
code = fs.readFileSync filename code = fs.readFileSync filename
try try
CoffeeScript.run code.toString(), {filename} CoffeeScript.run code.toString(), {filename, literate}
catch error catch error
failures.push {filename, error} failures.push {filename, error}
return !failures.length return !failures.length

View file

@ -318,8 +318,9 @@ Expressions
<td><code>--nodejs</code></td> <td><code>--nodejs</code></td>
<td> <td>
The <tt>node</tt> executable has some useful options you can set, The <tt>node</tt> executable has some useful options you can set,
such as<br /> <tt>--debug</tt>, <tt>--debug-brk</tt> and <tt>--max-stack-size</tt>. Use this such as<br /> <tt>--debug</tt>, <tt>--debug-brk</tt>, <tt>--max-stack-size</tt>,
flag to forward options directly to Node.js. and <tt>--expose-gc</tt>. Use this flag to forward options directly to Node.js.
To pass multiple flags, use <tt>--nodejs</tt> multiple times.
</td> </td>
</tr> </tr>
</table> </table>

View file

@ -1,4 +1,4 @@
// Generated by CoffeeScript 1.4.0 // Generated by CoffeeScript 1.5.0-pre
(function() { (function() {
var CoffeeScript, runScripts; var CoffeeScript, runScripts;

View file

@ -1,4 +1,4 @@
// Generated by CoffeeScript 1.4.0 // Generated by CoffeeScript 1.5.0-pre
(function() { (function() {
var CoffeeScript, cakefileDirectory, existsSync, fatalError, fs, helpers, missingTask, oparse, options, optparse, path, printTasks, switches, tasks; var CoffeeScript, cakefileDirectory, existsSync, fatalError, fs, helpers, missingTask, oparse, options, optparse, path, printTasks, switches, tasks;

View file

@ -1,39 +1,38 @@
// Generated by CoffeeScript 1.4.0 // Generated by CoffeeScript 1.5.0-pre
(function() { (function() {
var Lexer, RESERVED, compile, fs, lexer, parser, path, stripBOM, vm, _ref, var Lexer, compile, ext, extensions, fs, lexer, loadFile, parser, path, vm, _i, _len,
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
__hasProp = {}.hasOwnProperty; __hasProp = {}.hasOwnProperty;
fs = require('fs'); fs = require('fs');
path = require('path'); path = require('path');
_ref = require('./lexer'), Lexer = _ref.Lexer, RESERVED = _ref.RESERVED; Lexer = require('./lexer').Lexer;
parser = require('./parser').parser; parser = require('./parser').parser;
vm = require('vm'); vm = require('vm');
stripBOM = function(content) { extensions = ['.coffee', '.litcoffee'];
if (content.charCodeAt(0) === 0xFEFF) {
return content.substring(1); loadFile = function(module, filename) {
} else { var raw, stripped;
return content; raw = fs.readFileSync(filename, 'utf8');
} stripped = raw.charCodeAt(0) === 0xFEFF ? raw.substring(1) : raw;
return module._compile(compile(stripped, {
filename: filename
}), filename);
}; };
if (require.extensions) { if (require.extensions) {
require.extensions['.coffee'] = function(module, filename) { for (_i = 0, _len = extensions.length; _i < _len; _i++) {
var content; ext = extensions[_i];
content = compile(stripBOM(fs.readFileSync(filename, 'utf8')), { require.extensions[ext] = loadFile;
filename: filename }
});
return module._compile(content, filename);
};
} }
exports.VERSION = '1.4.0'; exports.VERSION = '1.5.0-pre';
exports.RESERVED = RESERVED;
exports.helpers = require('./helpers'); exports.helpers = require('./helpers');
@ -44,7 +43,7 @@
} }
merge = exports.helpers.merge; merge = exports.helpers.merge;
try { try {
js = (parser.parse(lexer.tokenize(code))).compile(options); js = (parser.parse(lexer.tokenize(code, options))).compile(options);
if (!options.header) { if (!options.header) {
return js; return js;
} }
@ -71,7 +70,7 @@
}; };
exports.run = function(code, options) { exports.run = function(code, options) {
var mainModule; var mainModule, _ref;
if (options == null) { if (options == null) {
options = {}; options = {};
} }
@ -79,7 +78,7 @@
mainModule.filename = process.argv[1] = options.filename ? fs.realpathSync(options.filename) : '.'; mainModule.filename = process.argv[1] = options.filename ? fs.realpathSync(options.filename) : '.';
mainModule.moduleCache && (mainModule.moduleCache = {}); mainModule.moduleCache && (mainModule.moduleCache = {});
mainModule.paths = require('module')._nodeModulePaths(path.dirname(fs.realpathSync(options.filename))); mainModule.paths = require('module')._nodeModulePaths(path.dirname(fs.realpathSync(options.filename)));
if (path.extname(mainModule.filename) !== '.coffee' || require.extensions) { if ((_ref = path.extname(mainModule.filename), __indexOf.call(extensions, _ref) < 0) || require.extensions) {
return mainModule._compile(compile(code, options), mainModule.filename); return mainModule._compile(compile(code, options), mainModule.filename);
} else { } else {
return mainModule._compile(code, mainModule.filename); return mainModule._compile(code, mainModule.filename);
@ -87,7 +86,7 @@
}; };
exports["eval"] = function(code, options) { exports["eval"] = function(code, options) {
var Module, Script, js, k, o, r, sandbox, v, _i, _len, _module, _ref1, _ref2, _require; var Module, Script, js, k, o, r, sandbox, v, _j, _len1, _module, _ref, _ref1, _require;
if (options == null) { if (options == null) {
options = {}; options = {};
} }
@ -101,10 +100,10 @@
sandbox = options.sandbox; sandbox = options.sandbox;
} else { } else {
sandbox = Script.createContext(); sandbox = Script.createContext();
_ref1 = options.sandbox; _ref = options.sandbox;
for (k in _ref1) { for (k in _ref) {
if (!__hasProp.call(_ref1, k)) continue; if (!__hasProp.call(_ref, k)) continue;
v = _ref1[k]; v = _ref[k];
sandbox[k] = v; sandbox[k] = v;
} }
} }
@ -121,9 +120,9 @@
return Module._load(path, _module, true); return Module._load(path, _module, true);
}; };
_module.filename = sandbox.__filename; _module.filename = sandbox.__filename;
_ref2 = Object.getOwnPropertyNames(require); _ref1 = Object.getOwnPropertyNames(require);
for (_i = 0, _len = _ref2.length; _i < _len; _i++) { for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
r = _ref2[_i]; r = _ref1[_j];
if (r !== 'paths') { if (r !== 'paths') {
_require[r] = require[r]; _require[r] = require[r];
} }

View file

@ -1,6 +1,7 @@
// Generated by CoffeeScript 1.4.0 // Generated by CoffeeScript 1.5.0-pre
(function() { (function() {
var BANNER, CoffeeScript, EventEmitter, SWITCHES, compileJoin, compileOptions, compilePath, compileScript, compileStdio, exec, exists, forkNode, fs, helpers, hidden, joinTimeout, lint, loadRequires, notSources, optionParser, optparse, opts, outputPath, parseOptions, path, printLine, printTokens, printWarn, removeSource, sourceCode, sources, spawn, timeLog, unwatchDir, usage, version, wait, watch, watchDir, watchers, writeJs, _ref; var BANNER, CoffeeScript, EventEmitter, SWITCHES, coffee_exts, compileJoin, compileOptions, compilePath, compileScript, compileStdio, exec, exists, forkNode, fs, helpers, hidden, joinTimeout, lint, loadRequires, notSources, optionParser, optparse, opts, outputPath, parseOptions, path, printLine, printTokens, printWarn, removeSource, sourceCode, sources, spawn, timeLog, unwatchDir, usage, version, wait, watch, watchDir, watchers, writeJs, _ref,
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
fs = require('fs'); fs = require('fs');
@ -48,6 +49,8 @@
optionParser = null; optionParser = null;
coffee_exts = ['.coffee', '.litcoffee'];
exports.run = function() { exports.run = function() {
var literals, source, _i, _len, _results; var literals, source, _i, _len, _results;
parseOptions(); parseOptions();
@ -92,11 +95,12 @@
compilePath = function(source, topLevel, base) { compilePath = function(source, topLevel, base) {
return fs.stat(source, function(err, stats) { return fs.stat(source, function(err, stats) {
var _ref1, _ref2;
if (err && err.code !== 'ENOENT') { if (err && err.code !== 'ENOENT') {
throw err; throw err;
} }
if ((err != null ? err.code : void 0) === 'ENOENT') { if ((err != null ? err.code : void 0) === 'ENOENT') {
if (topLevel && source.slice(-7) !== '.coffee') { if (topLevel && (_ref1 = path.extname(source), __indexOf.call(coffee_exts, _ref1) < 0)) {
source = sources[sources.indexOf(source)] = "" + source + ".coffee"; source = sources[sources.indexOf(source)] = "" + source + ".coffee";
return compilePath(source, topLevel, base); return compilePath(source, topLevel, base);
} }
@ -111,7 +115,7 @@
watchDir(source, base); watchDir(source, base);
} }
return fs.readdir(source, function(err, files) { return fs.readdir(source, function(err, files) {
var file, index, _ref1, _ref2; var file, index, _ref2, _ref3;
if (err && err.code !== 'ENOENT') { if (err && err.code !== 'ENOENT') {
throw err; throw err;
} }
@ -122,7 +126,7 @@
files = files.filter(function(file) { files = files.filter(function(file) {
return !hidden(file); return !hidden(file);
}); });
[].splice.apply(sources, [index, index - index + 1].concat(_ref1 = (function() { [].splice.apply(sources, [index, index - index + 1].concat(_ref2 = (function() {
var _i, _len, _results; var _i, _len, _results;
_results = []; _results = [];
for (_i = 0, _len = files.length; _i < _len; _i++) { for (_i = 0, _len = files.length; _i < _len; _i++) {
@ -130,15 +134,15 @@
_results.push(path.join(source, file)); _results.push(path.join(source, file));
} }
return _results; return _results;
})())), _ref1; })())), _ref2;
[].splice.apply(sourceCode, [index, index - index + 1].concat(_ref2 = files.map(function() { [].splice.apply(sourceCode, [index, index - index + 1].concat(_ref3 = files.map(function() {
return null; return null;
}))), _ref2; }))), _ref3;
return files.forEach(function(file) { return files.forEach(function(file) {
return compilePath(path.join(source, file), false, base); return compilePath(path.join(source, file), false, base);
}); });
}); });
} else if (topLevel || path.extname(source) === '.coffee') { } else if (topLevel || (_ref2 = path.extname(source), __indexOf.call(coffee_exts, _ref2) >= 0)) {
if (opts.watch) { if (opts.watch) {
watch(source, base); watch(source, base);
} }
@ -170,9 +174,9 @@
}; };
CoffeeScript.emit('compile', task); CoffeeScript.emit('compile', task);
if (o.tokens) { if (o.tokens) {
return printTokens(CoffeeScript.tokens(t.input)); return printTokens(CoffeeScript.tokens(t.input, t.options));
} else if (o.nodes) { } else if (o.nodes) {
return printLine(CoffeeScript.nodes(t.input).toString().trim()); return printLine(CoffeeScript.nodes(t.input, t.options).toString().trim());
} else if (o.run) { } else if (o.run) {
return CoffeeScript.run(t.input, t.options); return CoffeeScript.run(t.input, t.options);
} else if (o.join && t.file !== o.join) { } else if (o.join && t.file !== o.join) {
@ -472,8 +476,11 @@
}; };
compileOptions = function(filename) { compileOptions = function(filename) {
var literate;
literate = path.extname(filename) === '.litcoffee';
return { return {
filename: filename, filename: filename,
literate: literate,
bare: opts.bare, bare: opts.bare,
header: opts.compile header: opts.compile
}; };

View file

@ -1,4 +1,4 @@
// Generated by CoffeeScript 1.4.0 // Generated by CoffeeScript 1.5.0-pre
(function() { (function() {
var Parser, alt, alternatives, grammar, name, o, operators, token, tokens, unwrap; var Parser, alt, alternatives, grammar, name, o, operators, token, tokens, unwrap;
@ -575,6 +575,9 @@
}), o('SimpleAssignable COMPOUND_ASSIGN\ }), o('SimpleAssignable COMPOUND_ASSIGN\
INDENT Expression OUTDENT', function() { INDENT Expression OUTDENT', function() {
return new Assign($1, $4, $2); return new Assign($1, $4, $2);
}), o('SimpleAssignable COMPOUND_ASSIGN TERMINATOR\
Expression', function() {
return new Assign($1, $4, $2);
}), o('SimpleAssignable EXTENDS Expression', function() { }), o('SimpleAssignable EXTENDS Expression', function() {
return new Extends($1, $3); return new Extends($1, $3);
}) })

View file

@ -1,4 +1,4 @@
// Generated by CoffeeScript 1.4.0 // Generated by CoffeeScript 1.5.0-pre
(function() { (function() {
var buildLocationData, extend, flatten, _ref; var buildLocationData, extend, flatten, _ref;

View file

@ -1,4 +1,4 @@
// Generated by CoffeeScript 1.4.0 // Generated by CoffeeScript 1.5.0-pre
(function() { (function() {
var key, val, _ref; var key, val, _ref;

View file

@ -1,6 +1,6 @@
// Generated by CoffeeScript 1.4.0 // Generated by CoffeeScript 1.5.0-pre
(function() { (function() {
var BOOL, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_ALIAS_MAP, COFFEE_KEYWORDS, COMMENT, COMPARE, COMPOUND_ASSIGN, HEREDOC, HEREDOC_ILLEGAL, HEREDOC_INDENT, HEREGEX, HEREGEX_OMIT, IDENTIFIER, INDEXABLE, INVERSES, JSTOKEN, JS_FORBIDDEN, JS_KEYWORDS, LINE_BREAK, LINE_CONTINUER, LOGIC, Lexer, MATH, MULTILINER, MULTI_DENT, NOT_REGEX, NOT_SPACED_REGEX, NUMBER, OPERATOR, REGEX, RELATION, RESERVED, Rewriter, SHIFT, SIMPLESTR, STRICT_PROSCRIBED, TRAILING_SPACES, UNARY, WHITESPACE, compact, count, key, last, locationDataToString, starts, _ref, _ref1, var BOM, BOOL, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_ALIAS_MAP, COFFEE_KEYWORDS, COMMENT, COMPARE, COMPOUND_ASSIGN, HEREDOC, HEREDOC_ILLEGAL, HEREDOC_INDENT, HEREGEX, HEREGEX_OMIT, IDENTIFIER, INDEXABLE, INVERSES, JSTOKEN, JS_FORBIDDEN, JS_KEYWORDS, LINE_BREAK, LINE_CONTINUER, LITERATE, LOGIC, Lexer, MATH, MULTILINER, MULTI_DENT, NOT_REGEX, NOT_SPACED_REGEX, NUMBER, OPERATOR, REGEX, RELATION, RESERVED, Rewriter, SHIFT, SIMPLESTR, STRICT_PROSCRIBED, TRAILING_SPACES, UNARY, WHITESPACE, compact, count, key, last, locationDataToString, starts, _ref, _ref1,
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
_ref = require('./rewriter'), Rewriter = _ref.Rewriter, INVERSES = _ref.INVERSES; _ref = require('./rewriter'), Rewriter = _ref.Rewriter, INVERSES = _ref.INVERSES;
@ -16,19 +16,16 @@
if (opts == null) { if (opts == null) {
opts = {}; opts = {};
} }
if (WHITESPACE.test(code)) { this.literate = opts.literate;
code = "\n" + code; code = this.clean(code);
}
code = code.replace(/\r/g, '').replace(TRAILING_SPACES, '');
this.code = code;
this.chunkLine = opts.line || 0;
this.chunkColumn = opts.column || 0;
this.indent = 0; this.indent = 0;
this.indebt = 0; this.indebt = 0;
this.outdebt = 0; this.outdebt = 0;
this.indents = []; this.indents = [];
this.ends = []; this.ends = [];
this.tokens = []; this.tokens = [];
this.chunkLine = opts.line || 0;
this.chunkColumn = opts.column || 0;
i = 0; i = 0;
while (this.chunk = code.slice(i)) { while (this.chunk = code.slice(i)) {
consumed = this.identifierToken() || this.commentToken() || this.whitespaceToken() || this.lineToken() || this.heredocToken() || this.stringToken() || this.numberToken() || this.regexToken() || this.jsToken() || this.literalToken(); consumed = this.identifierToken() || this.commentToken() || this.whitespaceToken() || this.lineToken() || this.heredocToken() || this.stringToken() || this.numberToken() || this.regexToken() || this.jsToken() || this.literalToken();
@ -45,6 +42,35 @@
return (new Rewriter).rewrite(this.tokens); return (new Rewriter).rewrite(this.tokens);
}; };
Lexer.prototype.clean = function(code) {
var line, lines, match;
if (code.charCodeAt(0) === BOM) {
code = code.slice(1);
}
if (WHITESPACE.test(code)) {
code = "\n" + code;
}
code = code.replace(/\r/g, '').replace(TRAILING_SPACES, '');
if (this.literate) {
lines = (function() {
var _i, _len, _ref2, _results;
_ref2 = code.split('\n');
_results = [];
for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
line = _ref2[_i];
if (match = LITERATE.exec(line)) {
_results.push(line.slice(match[0].length));
} else {
_results.push('# ' + line);
}
}
return _results;
})();
code = lines.join('\n');
}
return code;
};
Lexer.prototype.identifierToken = function() { Lexer.prototype.identifierToken = function() {
var colon, colonOffset, forcedIdentifier, id, idLength, input, match, poppedToken, prev, tag, tagToken, _ref2, _ref3, _ref4; var colon, colonOffset, forcedIdentifier, id, idLength, input, match, poppedToken, prev, tag, tagToken, _ref2, _ref3, _ref4;
if (!(match = IDENTIFIER.exec(this.chunk))) { if (!(match = IDENTIFIER.exec(this.chunk))) {
@ -486,7 +512,7 @@
if (HEREDOC_ILLEGAL.test(doc)) { if (HEREDOC_ILLEGAL.test(doc)) {
this.error("block comment cannot contain \"*/\", starting"); this.error("block comment cannot contain \"*/\", starting");
} }
if (doc.indexOf('\n') <= 0) { if (doc.indexOf('\n') < 0) {
return doc; return doc;
} }
} else { } else {
@ -804,6 +830,8 @@
exports.STRICT_PROSCRIBED = STRICT_PROSCRIBED; exports.STRICT_PROSCRIBED = STRICT_PROSCRIBED;
BOM = 65279;
IDENTIFIER = /^([$A-Za-z_\x7f-\uffff][$\w\x7f-\uffff]*)([^\n\S]*:(?!:))?/; IDENTIFIER = /^([$A-Za-z_\x7f-\uffff][$\w\x7f-\uffff]*)([^\n\S]*:(?!:))?/;
NUMBER = /^0b[01]+|^0o[0-7]+|^0x[\da-f]+|^\d*\.?\d+(?:e[+-]?\d+)?/i; NUMBER = /^0b[01]+|^0o[0-7]+|^0x[\da-f]+|^\d*\.?\d+(?:e[+-]?\d+)?/i;
@ -814,7 +842,9 @@
WHITESPACE = /^[^\n\S]+/; WHITESPACE = /^[^\n\S]+/;
COMMENT = /^###([^#][\s\S]*?)(?:###[^\n\S]*|(?:###)?$)|^(?:\s*#(?!##[^#]).*)+/; COMMENT = /^###([^#][\s\S]*?)(?:###[^\n\S]*|(?:###)$)|^(?:\s*#(?!##[^#]).*)+/;
LITERATE = /^([ ]{4}|\t)/;
CODE = /^[-=]>/; CODE = /^[-=]>/;

View file

@ -1,4 +1,4 @@
// Generated by CoffeeScript 1.4.0 // Generated by CoffeeScript 1.5.0-pre
(function() { (function() {
var Access, Arr, Assign, Base, Block, Call, Class, Closure, Code, Comment, Existence, Extends, For, IDENTIFIER, IDENTIFIER_STR, IS_STRING, If, In, Index, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, METHOD_DEF, NEGATE, NO, Obj, Op, Param, Parens, RESERVED, Range, Return, SIMPLENUM, STRICT_PROSCRIBED, Scope, Slice, Splat, Switch, TAB, THIS, Throw, Try, UTILITIES, Value, While, YES, addLocationDataFn, compact, del, ends, extend, flatten, last, merge, multident, some, starts, unfoldSoak, utility, _ref, _ref1, var Access, Arr, Assign, Base, Block, Call, Class, Closure, Code, Comment, Existence, Extends, For, IDENTIFIER, IDENTIFIER_STR, IS_STRING, If, In, Index, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, METHOD_DEF, NEGATE, NO, Obj, Op, Param, Parens, RESERVED, Range, Return, SIMPLENUM, STRICT_PROSCRIBED, Scope, Slice, Splat, Switch, TAB, THIS, Throw, Try, UTILITIES, Value, While, YES, addLocationDataFn, compact, del, ends, extend, flatten, last, merge, multident, some, starts, unfoldSoak, utility, _ref, _ref1,
__hasProp = {}.hasOwnProperty, __hasProp = {}.hasOwnProperty,
@ -1937,7 +1937,7 @@
for (_i = 0, _len = _ref2.length; _i < _len; _i++) { for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
obj = _ref2[_i]; obj = _ref2[_i];
if (obj instanceof Assign) { if (obj instanceof Assign) {
names.push(obj.value.unwrap().value); names.push.apply(names, this.names(obj.value.unwrap()));
} else if (obj instanceof Splat) { } else if (obj instanceof Splat) {
names.push(obj.name.unwrap().value); names.push(obj.name.unwrap().value);
} else if (obj instanceof Value) { } else if (obj instanceof Value) {
@ -2904,6 +2904,9 @@
func = new Code([], Block.wrap([expressions])); func = new Code([], Block.wrap([expressions]));
args = []; args = [];
if ((mentionsArgs = expressions.contains(this.literalArgs)) || expressions.contains(this.literalThis)) { if ((mentionsArgs = expressions.contains(this.literalArgs)) || expressions.contains(this.literalThis)) {
if (mentionsArgs && expressions.classBody) {
throw SyntaxError("Class bodies shouldn't reference arguments");
}
meth = new Literal(mentionsArgs ? 'apply' : 'call'); meth = new Literal(mentionsArgs ? 'apply' : 'call');
args = [new Literal('this')]; args = [new Literal('this')];
if (mentionsArgs) { if (mentionsArgs) {

View file

@ -1,4 +1,4 @@
// Generated by CoffeeScript 1.4.0 // Generated by CoffeeScript 1.5.0-pre
(function() { (function() {
var LONG_FLAG, MULTI_FLAG, OPTIONAL, OptionParser, SHORT_FLAG, buildRule, buildRules, normalizeArguments; var LONG_FLAG, MULTI_FLAG, OPTIONAL, OptionParser, SHORT_FLAG, buildRule, buildRules, normalizeArguments;

File diff suppressed because one or more lines are too long

View file

@ -1,4 +1,4 @@
// Generated by CoffeeScript 1.4.0 // Generated by CoffeeScript 1.5.0-pre
(function() { (function() {
var ACCESSOR, CoffeeScript, Module, REPL_PROMPT, REPL_PROMPT_CONTINUATION, REPL_PROMPT_MULTILINE, SIMPLEVAR, Script, autocomplete, backlog, completeAttribute, completeVariable, enableColours, error, getCompletions, inspect, multilineMode, pipedInput, readline, repl, run, stdin, stdout, var ACCESSOR, CoffeeScript, Module, REPL_PROMPT, REPL_PROMPT_CONTINUATION, REPL_PROMPT_MULTILINE, SIMPLEVAR, Script, autocomplete, backlog, completeAttribute, completeVariable, enableColours, error, getCompletions, inspect, multilineMode, pipedInput, readline, repl, run, stdin, stdout,
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };

View file

@ -1,4 +1,4 @@
// Generated by CoffeeScript 1.4.0 // Generated by CoffeeScript 1.5.0-pre
(function() { (function() {
var BALANCED_PAIRS, EXPRESSION_CLOSE, EXPRESSION_END, EXPRESSION_START, IMPLICIT_BLOCK, IMPLICIT_CALL, IMPLICIT_END, IMPLICIT_FUNC, IMPLICIT_UNSPACED_CALL, INVERSES, LINEBREAKS, SINGLE_CLOSERS, SINGLE_LINERS, left, rite, _i, _len, _ref, var BALANCED_PAIRS, EXPRESSION_CLOSE, EXPRESSION_END, EXPRESSION_START, IMPLICIT_BLOCK, IMPLICIT_CALL, IMPLICIT_END, IMPLICIT_FUNC, IMPLICIT_UNSPACED_CALL, INVERSES, LINEBREAKS, SINGLE_CLOSERS, SINGLE_LINERS, left, rite, _i, _len, _ref,
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },

View file

@ -1,4 +1,4 @@
// Generated by CoffeeScript 1.4.0 // Generated by CoffeeScript 1.5.0-pre
(function() { (function() {
var Scope, extend, last, _ref; var Scope, extend, last, _ref;

View file

@ -1,4 +1,4 @@
// Generated by CoffeeScript 1.4.0 // Generated by CoffeeScript 1.5.0-pre
(function() { (function() {
var BASE64_CHARS, LineMapping, MAX_BASE64_VALUE, VLQ_CONTINUATION_BIT, VLQ_MASK, VLQ_SHIFT, encodeBase64Char; var BASE64_CHARS, LineMapping, MAX_BASE64_VALUE, VLQ_CONTINUATION_BIT, VLQ_MASK, VLQ_SHIFT, encodeBase64Char;

View file

@ -3,7 +3,7 @@
"description": "Unfancy JavaScript", "description": "Unfancy JavaScript",
"keywords": ["javascript", "language", "coffeescript", "compiler"], "keywords": ["javascript", "language", "coffeescript", "compiler"],
"author": "Jeremy Ashkenas", "author": "Jeremy Ashkenas",
"version": "1.4.0", "version": "1.5.0-pre",
"licenses": [{ "licenses": [{
"type": "MIT", "type": "MIT",
"url": "https://raw.github.com/jashkenas/coffee-script/master/LICENSE" "url": "https://raw.github.com/jashkenas/coffee-script/master/LICENSE"
@ -29,7 +29,7 @@
"url": "git://github.com/jashkenas/coffee-script.git" "url": "git://github.com/jashkenas/coffee-script.git"
}, },
"devDependencies": { "devDependencies": {
"uglify-js": ">=1.0.0", "uglify-js": "~2.2",
"jison": ">=0.2.0" "jison": ">=0.2.0"
} }
} }

View file

@ -8,23 +8,25 @@
fs = require 'fs' fs = require 'fs'
path = require 'path' path = require 'path'
{Lexer,RESERVED} = require './lexer' {Lexer} = require './lexer'
{parser} = require './parser' {parser} = require './parser'
vm = require 'vm' vm = require 'vm'
stripBOM = (content) -> # The file extensions that are considered to be CoffeeScript.
if content.charCodeAt(0) is 0xFEFF then content.substring 1 else content extensions = ['.coffee', '.litcoffee']
# Load and run a CoffeeScript file for Node, stripping any `BOM`s.
loadFile = (module, filename) ->
raw = fs.readFileSync filename, 'utf8'
stripped = if raw.charCodeAt(0) is 0xFEFF then raw.substring 1 else raw
module._compile compile(stripped, {filename}), filename
if require.extensions if require.extensions
require.extensions['.coffee'] = (module, filename) -> for ext in extensions
content = compile stripBOM(fs.readFileSync filename, 'utf8'), {filename} require.extensions[ext] = loadFile
module._compile content, filename
# The current CoffeeScript version number. # The current CoffeeScript version number.
exports.VERSION = '1.4.0' exports.VERSION = '1.5.0-pre'
# Words that cannot be used as identifiers in CoffeeScript code
exports.RESERVED = RESERVED
# Expose helpers for testing. # Expose helpers for testing.
exports.helpers = require './helpers' exports.helpers = require './helpers'
@ -34,7 +36,7 @@ exports.helpers = require './helpers'
exports.compile = compile = (code, options = {}) -> exports.compile = compile = (code, options = {}) ->
{merge} = exports.helpers {merge} = exports.helpers
try try
js = (parser.parse lexer.tokenize code).compile options js = (parser.parse lexer.tokenize(code, options)).compile options
return js unless options.header return js unless options.header
catch err catch err
err.message = "In #{options.filename}, #{err.message}" if options.filename err.message = "In #{options.filename}, #{err.message}" if options.filename
@ -71,7 +73,7 @@ exports.run = (code, options = {}) ->
mainModule.paths = require('module')._nodeModulePaths path.dirname fs.realpathSync options.filename mainModule.paths = require('module')._nodeModulePaths path.dirname fs.realpathSync options.filename
# Compile. # Compile.
if path.extname(mainModule.filename) isnt '.coffee' or require.extensions if (path.extname(mainModule.filename) not in extensions) or require.extensions
mainModule._compile compile(code, options), mainModule.filename mainModule._compile compile(code, options), mainModule.filename
else else
mainModule._compile code, mainModule.filename mainModule._compile code, mainModule.filename

View file

@ -57,6 +57,7 @@ sourceCode = []
notSources = {} notSources = {}
watchers = {} watchers = {}
optionParser = null optionParser = null
coffee_exts = ['.coffee', '.litcoffee']
# Run `coffee` by parsing passed options and determining what action to take. # Run `coffee` by parsing passed options and determining what action to take.
# Many flags cause us to divert before compiling anything. Flags passed after # Many flags cause us to divert before compiling anything. Flags passed after
@ -81,13 +82,13 @@ exports.run = ->
compilePath source, yes, path.normalize source compilePath source, yes, path.normalize source
# Compile a path, which could be a script or a directory. If a directory # Compile a path, which could be a script or a directory. If a directory
# is passed, recursively compile all '.coffee' extension source files in it # is passed, recursively compile all '.coffee' and '.litcoffee' extension source
# and all subdirectories. # files in it and all subdirectories.
compilePath = (source, topLevel, base) -> compilePath = (source, topLevel, base) ->
fs.stat source, (err, stats) -> fs.stat source, (err, stats) ->
throw err if err and err.code isnt 'ENOENT' throw err if err and err.code isnt 'ENOENT'
if err?.code is 'ENOENT' if err?.code is 'ENOENT'
if topLevel and source[-7..] isnt '.coffee' if topLevel and path.extname(source) not in coffee_exts
source = sources[sources.indexOf(source)] = "#{source}.coffee" source = sources[sources.indexOf(source)] = "#{source}.coffee"
return compilePath source, topLevel, base return compilePath source, topLevel, base
if topLevel if topLevel
@ -105,7 +106,7 @@ compilePath = (source, topLevel, base) ->
sourceCode[index..index] = files.map -> null sourceCode[index..index] = files.map -> null
files.forEach (file) -> files.forEach (file) ->
compilePath (path.join source, file), no, base compilePath (path.join source, file), no, base
else if topLevel or path.extname(source) is '.coffee' else if topLevel or path.extname(source) in coffee_exts
watch source, base if opts.watch watch source, base if opts.watch
fs.readFile source, (err, code) -> fs.readFile source, (err, code) ->
throw err if err and err.code isnt 'ENOENT' throw err if err and err.code isnt 'ENOENT'
@ -125,8 +126,8 @@ compileScript = (file, input, base) ->
try try
t = task = {file, input, options} t = task = {file, input, options}
CoffeeScript.emit 'compile', task CoffeeScript.emit 'compile', task
if o.tokens then printTokens CoffeeScript.tokens t.input if o.tokens then printTokens CoffeeScript.tokens t.input, t.options
else if o.nodes then printLine CoffeeScript.nodes(t.input).toString().trim() else if o.nodes then printLine CoffeeScript.nodes(t.input, t.options).toString().trim()
else if o.run then CoffeeScript.run t.input, t.options else if o.run then CoffeeScript.run t.input, t.options
else if o.join and t.file isnt o.join else if o.join and t.file isnt o.join
sourceCode[sources.indexOf(t.file)] = t.input sourceCode[sources.indexOf(t.file)] = t.input
@ -318,7 +319,8 @@ parseOptions = ->
# The compile-time options to pass to the CoffeeScript compiler. # The compile-time options to pass to the CoffeeScript compiler.
compileOptions = (filename) -> compileOptions = (filename) ->
{filename, bare: opts.bare, header: opts.compile} literate = path.extname(filename) is '.litcoffee'
{filename, literate, bare: opts.bare, header: opts.compile}
# Start up a new Node.js instance with the arguments in `--nodejs` passed to # Start up a new Node.js instance with the arguments in `--nodejs` passed to
# the `node` binary, preserving the other options. # the `node` binary, preserving the other options.

View file

@ -558,6 +558,8 @@ grammar =
Expression', -> new Assign $1, $3, $2 Expression', -> new Assign $1, $3, $2
o 'SimpleAssignable COMPOUND_ASSIGN o 'SimpleAssignable COMPOUND_ASSIGN
INDENT Expression OUTDENT', -> new Assign $1, $4, $2 INDENT Expression OUTDENT', -> new Assign $1, $4, $2
o 'SimpleAssignable COMPOUND_ASSIGN TERMINATOR
Expression', -> new Assign $1, $4, $2
o 'SimpleAssignable EXTENDS Expression', -> new Extends $1, $3 o 'SimpleAssignable EXTENDS Expression', -> new Extends $1, $3
] ]

View file

@ -32,14 +32,8 @@ exports.Lexer = class Lexer
# Before returning the token stream, run it through the [Rewriter](rewriter.html) # Before returning the token stream, run it through the [Rewriter](rewriter.html)
# unless explicitly asked not to. # unless explicitly asked not to.
tokenize: (code, opts = {}) -> tokenize: (code, opts = {}) ->
code = "\n#{code}" if WHITESPACE.test code @literate = opts.literate # Are we lexing literate CoffeeScript?
code = code.replace(/\r/g, '').replace TRAILING_SPACES, '' code = @clean code # The stripped, cleaned original source code.
@code = code # The source code.
@chunkLine =
opts.line or 0 # The start line for the current chunk.
@chunkColumn =
opts.column or 0 # The start column of the current chunk.
@indent = 0 # The current indentation level. @indent = 0 # The current indentation level.
@indebt = 0 # The over-indentation at the current level. @indebt = 0 # The over-indentation at the current level.
@outdebt = 0 # The under-outdentation at the current level. @outdebt = 0 # The under-outdentation at the current level.
@ -47,6 +41,11 @@ exports.Lexer = class Lexer
@ends = [] # The stack for pairing up tokens. @ends = [] # The stack for pairing up tokens.
@tokens = [] # Stream of parsed tokens in the form `['TYPE', value, line]`. @tokens = [] # Stream of parsed tokens in the form `['TYPE', value, line]`.
@chunkLine =
opts.line or 0 # The start line for the current @chunk.
@chunkColumn =
opts.column or 0 # The start column of the current @chunk.
# At every position, run through this list of attempted matches, # At every position, run through this list of attempted matches,
# short-circuiting if any of them succeed. Their order determines precedence: # short-circuiting if any of them succeed. Their order determines precedence:
# `@literalToken` is the fallback catch-all. # `@literalToken` is the fallback catch-all.
@ -74,6 +73,22 @@ exports.Lexer = class Lexer
return @tokens if opts.rewrite is off return @tokens if opts.rewrite is off
(new Rewriter).rewrite @tokens (new Rewriter).rewrite @tokens
# Preprocess the code to remove leading and trailing whitespace, carriage
# returns, etc. If we're lexing literate CoffeeScript, strip external Markdown
# by removing all lines that aren't indented by at least four spaces or a tab.
clean: (code) ->
code = code.slice(1) if code.charCodeAt(0) is BOM
code = "\n#{code}" if WHITESPACE.test code
code = code.replace(/\r/g, '').replace TRAILING_SPACES, ''
if @literate
lines = for line in code.split('\n')
if match = LITERATE.exec line
line[match[0].length..]
else
'# ' + line
code = lines.join '\n'
code
# Tokenizers # Tokenizers
# ---------- # ----------
@ -87,6 +102,7 @@ exports.Lexer = class Lexer
return 0 unless match = IDENTIFIER.exec @chunk return 0 unless match = IDENTIFIER.exec @chunk
[input, id, colon] = match [input, id, colon] = match
# Preserve lenght of id for location data
idLength = id.length idLength = id.length
poppedToken = undefined poppedToken = undefined
@ -417,7 +433,7 @@ exports.Lexer = class Lexer
if herecomment if herecomment
if HEREDOC_ILLEGAL.test doc if HEREDOC_ILLEGAL.test doc
@error "block comment cannot contain \"*/\", starting" @error "block comment cannot contain \"*/\", starting"
return doc if doc.indexOf('\n') <= 0 return doc if doc.indexOf('\n') < 0
else else
while match = HEREDOC_INDENT.exec doc while match = HEREDOC_INDENT.exec doc
attempt = match[1] attempt = match[1]
@ -596,6 +612,8 @@ exports.Lexer = class Lexer
# ------- # -------
# Returns the line and column number from an offset into the current chunk. # Returns the line and column number from an offset into the current chunk.
#
# `offset` is a number of characters into @chunk.
getLineAndColumnFromChunk: (offset) -> getLineAndColumnFromChunk: (offset) ->
if offset is 0 if offset is 0
return [@chunkLine, @chunkColumn] return [@chunkLine, @chunkColumn]
@ -724,6 +742,9 @@ JS_FORBIDDEN = JS_KEYWORDS.concat(RESERVED).concat(STRICT_PROSCRIBED)
exports.RESERVED = RESERVED.concat(JS_KEYWORDS).concat(COFFEE_KEYWORDS).concat(STRICT_PROSCRIBED) exports.RESERVED = RESERVED.concat(JS_KEYWORDS).concat(COFFEE_KEYWORDS).concat(STRICT_PROSCRIBED)
exports.STRICT_PROSCRIBED = STRICT_PROSCRIBED exports.STRICT_PROSCRIBED = STRICT_PROSCRIBED
# The character code of the nasty Microsoft madness otherwise known as the BOM.
BOM = 65279
# Token matching regexes. # Token matching regexes.
IDENTIFIER = /// ^ IDENTIFIER = /// ^
( [$A-Za-z_\x7f-\uffff][$\w\x7f-\uffff]* ) ( [$A-Za-z_\x7f-\uffff][$\w\x7f-\uffff]* )
@ -751,7 +772,9 @@ OPERATOR = /// ^ (
WHITESPACE = /^[^\n\S]+/ WHITESPACE = /^[^\n\S]+/
COMMENT = /^###([^#][\s\S]*?)(?:###[^\n\S]*|(?:###)?$)|^(?:\s*#(?!##[^#]).*)+/ COMMENT = /^###([^#][\s\S]*?)(?:###[^\n\S]*|(?:###)$)|^(?:\s*#(?!##[^#]).*)+/
LITERATE = /^([ ]{4}|\t)/
CODE = /^[-=]>/ CODE = /^[-=]>/

View file

@ -1307,7 +1307,7 @@ exports.Param = class Param extends Base
for obj in name.objects for obj in name.objects
# * assignments within destructured parameters `{foo:bar}` # * assignments within destructured parameters `{foo:bar}`
if obj instanceof Assign if obj instanceof Assign
names.push obj.value.unwrap().value names.push @names(obj.value.unwrap())...
# * splats within destructured parameters `[xs...]` # * splats within destructured parameters `[xs...]`
else if obj instanceof Splat else if obj instanceof Splat
names.push obj.name.unwrap().value names.push obj.name.unwrap().value
@ -1952,6 +1952,8 @@ Closure =
func = new Code [], Block.wrap [expressions] func = new Code [], Block.wrap [expressions]
args = [] args = []
if (mentionsArgs = expressions.contains @literalArgs) or expressions.contains @literalThis if (mentionsArgs = expressions.contains @literalArgs) or expressions.contains @literalThis
if mentionsArgs and expressions.classBody
throw SyntaxError "Class bodies shouldn't reference arguments"
meth = new Literal if mentionsArgs then 'apply' else 'call' meth = new Literal if mentionsArgs then 'apply' else 'call'
args = [new Literal 'this'] args = [new Literal 'this']
args.push new Literal 'arguments' if mentionsArgs args.push new Literal 'arguments' if mentionsArgs

View file

@ -1,101 +0,0 @@
# The **Scope** class regulates lexical scoping within CoffeeScript. As you
# generate code, you create a tree of scopes in the same shape as the nested
# function bodies. Each scope knows about the variables declared within it,
# and has a reference to its parent enclosing scope. In this way, we know which
# variables are new and need to be declared with `var`, and which are shared
# with the outside.
# Import the helpers we plan to use.
{extend, last} = require './helpers'
exports.Scope = class Scope
# The top-level **Scope** object.
@root: null
# Initialize a scope with its parent, for lookups up the chain,
# as well as a reference to the **Block** node it belongs to, which is
# where it should declare its variables, and a reference to the function that
# it wraps.
constructor: (@parent, @expressions, @method) ->
@variables = [{name: 'arguments', type: 'arguments'}]
@positions = {}
Scope.root = this unless @parent
# Adds a new variable or overrides an existing one.
add: (name, type, immediate) ->
return @parent.add name, type, immediate if @shared and not immediate
if Object::hasOwnProperty.call @positions, name
@variables[@positions[name]].type = type
else
@positions[name] = @variables.push({name, type}) - 1
# When `super` is called, we need to find the name of the current method we're
# in, so that we know how to invoke the same method of the parent class. This
# can get complicated if super is being called from an inner function.
# `namedMethod` will walk up the scope tree until it either finds the first
# function object that has a name filled in, or bottoms out.
namedMethod: ->
return @method if @method.name or !@parent
@parent.namedMethod()
# Look up a variable name in lexical scope, and declare it if it does not
# already exist.
find: (name) ->
return yes if @check name
@add name, 'var'
no
# Reserve a variable name as originating from a function parameter for this
# scope. No `var` required for internal references.
parameter: (name) ->
return if @shared and @parent.check name, yes
@add name, 'param'
# Just check to see if a variable has already been declared, without reserving,
# walks up to the root scope.
check: (name) ->
!!(@type(name) or @parent?.check(name))
# Generate a temporary variable name at the given index.
temporary: (name, index) ->
if name.length > 1
'_' + name + if index > 1 then index - 1 else ''
else
'_' + (index + parseInt name, 36).toString(36).replace /\d/g, 'a'
# Gets the type of a variable.
type: (name) ->
return v.type for v in @variables when v.name is name
null
# If we need to store an intermediate result, find an available name for a
# compiler-generated variable. `_var`, `_var2`, and so on...
freeVariable: (name, reserve=true) ->
index = 0
index++ while @check((temp = @temporary name, index))
@add temp, 'var', yes if reserve
temp
# Ensure that an assignment is made at the top of this scope
# (or at the top-level scope, if requested).
assign: (name, value) ->
@add name, {value, assigned: yes}, yes
@hasAssignments = yes
# Does this scope have any declared variables?
hasDeclarations: ->
!!@declaredVariables().length
# Return the list of variables first declared in this scope.
declaredVariables: ->
realVars = []
tempVars = []
for v in @variables when v.type is 'var'
(if v.name.charAt(0) is '_' then tempVars else realVars).push v.name
realVars.sort().concat tempVars.sort()
# Return the list of assignments that are supposed to be made at the top
# of this scope.
assignedVariables: ->
"#{v.name} = #{v.type.value}" for v in @variables when v.type.assigned

117
src/scope.litcoffee Normal file
View file

@ -0,0 +1,117 @@
The **Scope** class regulates lexical scoping within CoffeeScript. As you
generate code, you create a tree of scopes in the same shape as the nested
function bodies. Each scope knows about the variables declared within it,
and has a reference to its parent enclosing scope. In this way, we know which
variables are new and need to be declared with `var`, and which are shared
with external scopes.
Import the helpers we plan to use.
{extend, last} = require './helpers'
exports.Scope = class Scope
The `root` is the top-level **Scope** object for a given file.
@root: null
Initialize a scope with its parent, for lookups up the chain,
as well as a reference to the **Block** node it belongs to, which is
where it should declare its variables, and a reference to the function that
it belongs to.
constructor: (@parent, @expressions, @method) ->
@variables = [{name: 'arguments', type: 'arguments'}]
@positions = {}
Scope.root = this unless @parent
Adds a new variable or overrides an existing one.
add: (name, type, immediate) ->
return @parent.add name, type, immediate if @shared and not immediate
if Object::hasOwnProperty.call @positions, name
@variables[@positions[name]].type = type
else
@positions[name] = @variables.push({name, type}) - 1
When `super` is called, we need to find the name of the current method we're
in, so that we know how to invoke the same method of the parent class. This
can get complicated if super is being called from an inner function.
`namedMethod` will walk up the scope tree until it either finds the first
function object that has a name filled in, or bottoms out.
namedMethod: ->
return @method if @method.name or !@parent
@parent.namedMethod()
Look up a variable name in lexical scope, and declare it if it does not
already exist.
find: (name) ->
return yes if @check name
@add name, 'var'
no
Reserve a variable name as originating from a function parameter for this
scope. No `var` required for internal references.
parameter: (name) ->
return if @shared and @parent.check name, yes
@add name, 'param'
Just check to see if a variable has already been declared, without reserving,
walks up to the root scope.
check: (name) ->
!!(@type(name) or @parent?.check(name))
Generate a temporary variable name at the given index.
temporary: (name, index) ->
if name.length > 1
'_' + name + if index > 1 then index - 1 else ''
else
'_' + (index + parseInt name, 36).toString(36).replace /\d/g, 'a'
Gets the type of a variable.
type: (name) ->
return v.type for v in @variables when v.name is name
null
If we need to store an intermediate result, find an available name for a
compiler-generated variable. `_var`, `_var2`, and so on...
freeVariable: (name, reserve=true) ->
index = 0
index++ while @check((temp = @temporary name, index))
@add temp, 'var', yes if reserve
temp
Ensure that an assignment is made at the top of this scope
(or at the top-level scope, if requested).
assign: (name, value) ->
@add name, {value, assigned: yes}, yes
@hasAssignments = yes
Does this scope have any declared variables?
hasDeclarations: ->
!!@declaredVariables().length
Return the list of variables first declared in this scope.
declaredVariables: ->
realVars = []
tempVars = []
for v in @variables when v.type is 'var'
(if v.name.charAt(0) is '_' then tempVars else realVars).push v.name
realVars.sort().concat tempVars.sort()
Return the list of assignments that are supposed to be made at the top
of this scope.
assignedVariables: ->
"#{v.name} = #{v.type.value}" for v in @variables when v.type.assigned

View file

@ -365,3 +365,13 @@ test '#2213: invocations within destructured parameters', ->
throws -> CoffeeScript.compile '({a()})->' throws -> CoffeeScript.compile '({a()})->'
throws -> CoffeeScript.compile '({a:b()})->' throws -> CoffeeScript.compile '({a:b()})->'
throws -> CoffeeScript.compile '({a:b.c()})->' throws -> CoffeeScript.compile '({a:b.c()})->'
test '#2532: compound assignment with terminator', ->
doesNotThrow -> CoffeeScript.compile """
a = "hello"
a +=
"
world
!
"
"""

View file

@ -676,3 +676,7 @@ test "#2052: classes should work in strict mode", ->
class A class A
catch e catch e
ok no ok no
test "#2630: class bodies can't reference arguments", ->
throws ->
CoffeeScript.compile('class Test then arguments')

View file

@ -206,3 +206,10 @@ test "#2258: allow whitespace-style parameter lists in function definitions", ->
c c
) -> b ) -> b
eq func(1, 2, 3), 2 eq func(1, 2, 3), 2
test "#2621: fancy destructuring in parameter lists", ->
func = ({ prop1: { key1 }, prop2: { key2, key3: [a, b, c] } }) ->
eq(key2, 'key2')
eq(a, 'a')
func({prop1: {key1: 'key1'}, prop2: {key2: 'key2', key3: ['a', 'b', 'c']}})

48
test/literate.litcoffee Normal file
View file

@ -0,0 +1,48 @@
Literate CoffeeScript Test
--------------------------
comment comment
test "basic literate CoffeeScript parsing", ->
ok yes
now with a...
test "broken up indentation", ->
... broken up ...
do ->
... nested block.
ok yes
Code in `backticks is not parsed` and...
test "comments in indented blocks work", ->
do ->
do ->
# Regular comment.
###
Block comment.
###
ok yes
Regular [Markdown](http://example.com/markdown) features, like links
and unordered lists, are fine:
* I
* Am
* A
* List
Tabs work too:
test "tabbed code", ->
ok yes

View file

@ -28,6 +28,12 @@ test "operators should respect new lines as spaced", ->
test "multiple operators should space themselves", -> test "multiple operators should space themselves", ->
eq (+ +1), (- -1) eq (+ +1), (- -1)
test "compound operators on successive lines", ->
a = 1
a +=
1
eq a, 2
test "bitwise operators", -> test "bitwise operators", ->
eq 2, (10 & 3) eq 2, (10 & 3)
eq 11, (10 | 3) eq 11, (10 | 3)