mirror of
https://github.com/jashkenas/coffeescript.git
synced 2022-11-09 12:23:24 -05:00
merging jcoglan's splat declaration patch.
This commit is contained in:
commit
963adb5230
22 changed files with 491 additions and 220 deletions
7
Cakefile
7
Cakefile
|
@ -94,15 +94,16 @@ task 'build:browser', 'rebuild the merged script for inclusion in the browser',
|
|||
#{fs.readFileSync "lib/#{name}.js"}
|
||||
};
|
||||
"""
|
||||
{parser, uglify} = require 'uglify-js'
|
||||
ast = parser.parse """
|
||||
code = """
|
||||
this.CoffeeScript = function() {
|
||||
function require(path){ return require[path]; }
|
||||
#{code}
|
||||
return require['./coffee-script']
|
||||
}()
|
||||
"""
|
||||
code = uglify.gen_code uglify.ast_squeeze uglify.ast_mangle ast, extra: yes
|
||||
unless process.env.MINIFY is 'false'
|
||||
{parser, uglify} = require 'uglify-js'
|
||||
code = uglify.gen_code uglify.ast_squeeze uglify.ast_mangle parser.parse code
|
||||
fs.writeFileSync 'extras/coffee-script.js', header + '\n' + code
|
||||
console.log "built ... running browser tests:"
|
||||
invoke 'test:browser'
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
return compile(content);
|
||||
});
|
||||
}
|
||||
exports.VERSION = '1.0.2-pre';
|
||||
exports.VERSION = '1.1.0-pre';
|
||||
exports.RESERVED = RESERVED;
|
||||
exports.helpers = require('./helpers');
|
||||
exports.compile = compile = function(code, options) {
|
||||
|
|
|
@ -196,7 +196,7 @@
|
|||
if (err) {
|
||||
return printLine(err.message);
|
||||
} else if (opts.compile && opts.watch) {
|
||||
return console.log("" + ((new Date).toTimeString()) + " - compiled " + source);
|
||||
return console.log("" + ((new Date).toLocaleTimeString()) + " - compiled " + source);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -169,13 +169,11 @@
|
|||
return new Access($2, 'proto');
|
||||
}), o('::', function() {
|
||||
return new Access(new Literal('prototype'));
|
||||
}), o('Index'), o('Slice', function() {
|
||||
return new Slice($1);
|
||||
})
|
||||
}), o('Index')
|
||||
],
|
||||
Index: [
|
||||
o('INDEX_START Expression INDEX_END', function() {
|
||||
return new Index($2);
|
||||
o('INDEX_START IndexValue INDEX_END', function() {
|
||||
return $2;
|
||||
}), o('INDEX_SOAK Index', function() {
|
||||
return extend($2, {
|
||||
soak: true
|
||||
|
@ -186,6 +184,13 @@
|
|||
});
|
||||
})
|
||||
],
|
||||
IndexValue: [
|
||||
o('Expression', function() {
|
||||
return new Index($1);
|
||||
}), o('Slice', function() {
|
||||
return new Slice($1);
|
||||
})
|
||||
],
|
||||
Object: [
|
||||
o('{ AssignList OptComma }', function() {
|
||||
return new Obj($2, $1.generated);
|
||||
|
@ -280,12 +285,12 @@
|
|||
})
|
||||
],
|
||||
Slice: [
|
||||
o('INDEX_START Expression RangeDots Expression INDEX_END', function() {
|
||||
return new Range($2, $4, $3);
|
||||
}), o('INDEX_START Expression RangeDots INDEX_END', function() {
|
||||
return new Range($2, null, $3);
|
||||
}), o('INDEX_START RangeDots Expression INDEX_END', function() {
|
||||
return new Range(null, $3, $2);
|
||||
o('Expression RangeDots Expression', function() {
|
||||
return new Range($1, $3, $2);
|
||||
}), o('Expression RangeDots', function() {
|
||||
return new Range($1, null, $2);
|
||||
}), o('RangeDots Expression', function() {
|
||||
return new Range(null, $2, $1);
|
||||
})
|
||||
],
|
||||
ArgList: [
|
||||
|
|
23
lib/lexer.js
23
lib/lexer.js
|
@ -1,5 +1,5 @@
|
|||
(function() {
|
||||
var ASSIGNED, BOOL, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_KEYWORDS, COMMENT, COMPARE, COMPOUND_ASSIGN, HEREDOC, HEREDOC_INDENT, HEREGEX, HEREGEX_OMIT, IDENTIFIER, INDEXABLE, JSTOKEN, JS_FORBIDDEN, JS_KEYWORDS, LINE_BREAK, LINE_CONTINUER, LOGIC, Lexer, MATH, MULTILINER, MULTI_DENT, NOT_REGEX, NOT_SPACED_REGEX, NO_NEWLINE, NUMBER, OPERATOR, REGEX, RELATION, RESERVED, Rewriter, SHIFT, SIMPLESTR, TRAILING_SPACES, UNARY, WHITESPACE, compact, count, last, op, starts, _ref;
|
||||
var ASSIGNED, BOOL, CALLABLE, CODE, COFFEE_ALIASES, COFFEE_KEYWORDS, COMMENT, COMPARE, COMPOUND_ASSIGN, HEREDOC, HEREDOC_ILLEGAL, HEREDOC_INDENT, HEREGEX, HEREGEX_OMIT, IDENTIFIER, INDEXABLE, JSTOKEN, JS_FORBIDDEN, JS_KEYWORDS, LINE_BREAK, LINE_CONTINUER, LOGIC, Lexer, MATH, MULTILINER, MULTI_DENT, NOT_REGEX, NOT_SPACED_REGEX, NO_NEWLINE, NUMBER, OPERATOR, REGEX, RELATION, RESERVED, Rewriter, SHIFT, SIMPLESTR, TRAILING_SPACES, UNARY, WHITESPACE, compact, count, last, op, starts, _ref;
|
||||
var __indexOf = Array.prototype.indexOf || function(item) {
|
||||
for (var i = 0, l = this.length; i < l; i++) {
|
||||
if (this[i] === item) return i;
|
||||
|
@ -46,7 +46,7 @@
|
|||
this.token('OWN', id);
|
||||
return id.length;
|
||||
}
|
||||
forcedIdentifier = colon || (prev = last(this.tokens)) && !prev.spaced && ((_ref = prev[0]) === '.' || _ref === '?.' || _ref === '@' || _ref === '::');
|
||||
forcedIdentifier = colon || (prev = last(this.tokens)) && (((_ref = prev[0]) === '.' || _ref === '?.' || _ref === '::') || !prev.spaced && prev[0] === '@');
|
||||
tag = 'IDENTIFIER';
|
||||
if (__indexOf.call(JS_KEYWORDS, id) >= 0 || !forcedIdentifier && __indexOf.call(COFFEE_KEYWORDS, id) >= 0) {
|
||||
tag = id.toUpperCase();
|
||||
|
@ -175,7 +175,6 @@
|
|||
return 0;
|
||||
}
|
||||
comment = match[0], here = match[1];
|
||||
this.line += count(comment, '\n');
|
||||
if (here) {
|
||||
this.token('HERECOMMENT', this.sanitizeHeredoc(here, {
|
||||
herecomment: true,
|
||||
|
@ -183,6 +182,7 @@
|
|||
}));
|
||||
this.token('TERMINATOR', '\n');
|
||||
}
|
||||
this.line += count(comment, '\n');
|
||||
return comment.length;
|
||||
};
|
||||
Lexer.prototype.jsToken = function() {
|
||||
|
@ -398,10 +398,14 @@
|
|||
Lexer.prototype.sanitizeHeredoc = function(doc, options) {
|
||||
var attempt, herecomment, indent, match, _ref;
|
||||
indent = options.indent, herecomment = options.herecomment;
|
||||
if (herecomment && 0 > doc.indexOf('\n')) {
|
||||
return doc;
|
||||
}
|
||||
if (!herecomment) {
|
||||
if (herecomment) {
|
||||
if (HEREDOC_ILLEGAL.test(doc)) {
|
||||
throw new Error("block comment cannot contain \"*/\", starting on line " + (this.line + 1));
|
||||
}
|
||||
if (doc.indexOf('\n') <= 0) {
|
||||
return doc;
|
||||
}
|
||||
} else {
|
||||
while (match = HEREDOC_INDENT.exec(doc)) {
|
||||
attempt = match[1];
|
||||
if (indent === null || (0 < (_ref = attempt.length) && _ref < indent.length)) {
|
||||
|
@ -435,7 +439,7 @@
|
|||
case 'CALL_START':
|
||||
if (stack.length) {
|
||||
stack.pop();
|
||||
} else {
|
||||
} else if (tok[0] === '(') {
|
||||
tok[0] = 'PARAM_START';
|
||||
return this;
|
||||
}
|
||||
|
@ -618,8 +622,9 @@
|
|||
HEREGEX_OMIT = /\s+(?:#.*)?/g;
|
||||
MULTILINER = /\n/g;
|
||||
HEREDOC_INDENT = /\n+([^\n\S]*)/g;
|
||||
HEREDOC_ILLEGAL = /\*\//;
|
||||
ASSIGNED = /^\s*@?([$A-Za-z_][$\w\x7f-\uffff]*|['"].*['"])[^\n\S]*?[:=][^:=>]/;
|
||||
LINE_CONTINUER = /^\s*(?:,|\??\.(?!\.)|::)/;
|
||||
LINE_CONTINUER = /^\s*(?:,|\??\.(?![.\d])|::)/;
|
||||
TRAILING_SPACES = /\s+$/;
|
||||
NO_NEWLINE = /^(?:[-+*&|\/%=<>!.\\][<>=&|]*|and|or|is(?:nt)?|n(?:ot|ew)|delete|typeof|instanceof)$/;
|
||||
COMPOUND_ASSIGN = ['-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?=', '<<=', '>>=', '>>>=', '&=', '^=', '|='];
|
||||
|
|
78
lib/nodes.js
78
lib/nodes.js
|
@ -41,7 +41,7 @@
|
|||
}
|
||||
};
|
||||
Base.prototype.compileClosure = function(o) {
|
||||
if (this.jumps()) {
|
||||
if (this.jumps() || this instanceof Throw) {
|
||||
throw SyntaxError('cannot use a pure statement in an expression.');
|
||||
}
|
||||
o.sharedScope = true;
|
||||
|
@ -630,6 +630,32 @@
|
|||
}
|
||||
return ifn;
|
||||
};
|
||||
Call.prototype.filterImplicitObjects = function(list) {
|
||||
var node, nodes, obj, prop, properties, _i, _j, _len, _len2, _ref;
|
||||
nodes = [];
|
||||
for (_i = 0, _len = list.length; _i < _len; _i++) {
|
||||
node = list[_i];
|
||||
if (!((typeof node.isObject == "function" ? node.isObject() : void 0) && node.base.generated)) {
|
||||
nodes.push(node);
|
||||
continue;
|
||||
}
|
||||
obj = null;
|
||||
_ref = node.base.properties;
|
||||
for (_j = 0, _len2 = _ref.length; _j < _len2; _j++) {
|
||||
prop = _ref[_j];
|
||||
if (prop instanceof Assign) {
|
||||
if (!obj) {
|
||||
nodes.push(obj = new Obj(properties = [], true));
|
||||
}
|
||||
properties.push(prop);
|
||||
} else {
|
||||
nodes.push(prop);
|
||||
obj = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nodes;
|
||||
};
|
||||
Call.prototype.compileNode = function(o) {
|
||||
var arg, args, code, _ref;
|
||||
if ((_ref = this.variable) != null) {
|
||||
|
@ -638,16 +664,16 @@
|
|||
if (code = Splat.compileSplattedArray(o, this.args, true)) {
|
||||
return this.compileSplat(o, code);
|
||||
}
|
||||
args = this.filterImplicitObjects(this.args);
|
||||
args = ((function() {
|
||||
var _i, _len, _ref, _results;
|
||||
_ref = this.args;
|
||||
var _i, _len, _results;
|
||||
_results = [];
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
arg = _ref[_i];
|
||||
for (_i = 0, _len = args.length; _i < _len; _i++) {
|
||||
arg = args[_i];
|
||||
_results.push(arg.compile(o, LEVEL_LIST));
|
||||
}
|
||||
return _results;
|
||||
}).call(this)).join(', ');
|
||||
})()).join(', ');
|
||||
if (this.isSuper) {
|
||||
return this.superReference(o) + (".call(this" + (args && ', ' + args) + ")");
|
||||
} else {
|
||||
|
@ -842,7 +868,7 @@
|
|||
}
|
||||
Obj.prototype.children = ['properties'];
|
||||
Obj.prototype.compileNode = function(o) {
|
||||
var i, idt, indent, join, lastNoncom, obj, prop, props;
|
||||
var i, idt, indent, join, lastNoncom, node, obj, prop, props, _i, _len;
|
||||
props = this.properties;
|
||||
if (!props.length) {
|
||||
if (this.front) {
|
||||
|
@ -851,6 +877,14 @@
|
|||
return '{}';
|
||||
}
|
||||
}
|
||||
if (this.generated) {
|
||||
for (_i = 0, _len = props.length; _i < _len; _i++) {
|
||||
node = props[_i];
|
||||
if (node instanceof Value) {
|
||||
throw new Error('cannot have an implicit value in an implicit object');
|
||||
}
|
||||
}
|
||||
}
|
||||
idt = o.indent += TAB;
|
||||
lastNoncom = this.lastNonComment(this.properties);
|
||||
props = (function() {
|
||||
|
@ -900,25 +934,26 @@
|
|||
this.objects = objs || [];
|
||||
}
|
||||
Arr.prototype.children = ['objects'];
|
||||
Arr.prototype.filterImplicitObjects = Call.prototype.filterImplicitObjects;
|
||||
Arr.prototype.compileNode = function(o) {
|
||||
var code, obj;
|
||||
var code, obj, objs;
|
||||
if (!this.objects.length) {
|
||||
return '[]';
|
||||
}
|
||||
o.indent += TAB;
|
||||
if (code = Splat.compileSplattedArray(o, this.objects)) {
|
||||
objs = this.filterImplicitObjects(this.objects);
|
||||
if (code = Splat.compileSplattedArray(o, objs)) {
|
||||
return code;
|
||||
}
|
||||
code = ((function() {
|
||||
var _i, _len, _ref, _results;
|
||||
_ref = this.objects;
|
||||
var _i, _len, _results;
|
||||
_results = [];
|
||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
obj = _ref[_i];
|
||||
for (_i = 0, _len = objs.length; _i < _len; _i++) {
|
||||
obj = objs[_i];
|
||||
_results.push(obj.compile(o, LEVEL_LIST));
|
||||
}
|
||||
return _results;
|
||||
}).call(this)).join(', ');
|
||||
})()).join(', ');
|
||||
if (code.indexOf('\n') >= 0) {
|
||||
return "[\n" + o.indent + code + "\n" + this.tab + "]";
|
||||
} else {
|
||||
|
@ -1280,6 +1315,9 @@
|
|||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
||||
param = _ref[_i];
|
||||
if (param.splat) {
|
||||
if (param.name.value) {
|
||||
o.scope.add(param.name.value, 'var');
|
||||
}
|
||||
splats = new Assign(new Value(new Arr((function() {
|
||||
var _i, _len, _ref, _results;
|
||||
_ref = this.params;
|
||||
|
@ -1517,17 +1555,20 @@
|
|||
var CONVERSIONS, INVERSIONS;
|
||||
__extends(Op, Base);
|
||||
function Op(op, first, second, flip) {
|
||||
var call;
|
||||
if (op === 'in') {
|
||||
return new In(first, second);
|
||||
}
|
||||
if (op === 'do') {
|
||||
return new Call(first, first.params || []);
|
||||
call = new Call(first, first.params || []);
|
||||
call["do"] = true;
|
||||
return call;
|
||||
}
|
||||
if (op === 'new') {
|
||||
if (first instanceof Call) {
|
||||
if (first instanceof Call && !first["do"]) {
|
||||
return first.newInstance();
|
||||
}
|
||||
if (first instanceof Code && first.bound) {
|
||||
if (first instanceof Code && first.bound || first["do"]) {
|
||||
first = new Parens(first);
|
||||
}
|
||||
}
|
||||
|
@ -1635,6 +1676,9 @@
|
|||
if ((op === 'new' || op === 'typeof' || op === 'delete') || (op === '+' || op === '-') && this.first instanceof Op && this.first.operator === op) {
|
||||
parts.push(' ');
|
||||
}
|
||||
if (op === 'new' && this.first.isStatement(o)) {
|
||||
this.first = new Parens(this.first);
|
||||
}
|
||||
parts.push(this.first.compile(o, LEVEL_OP));
|
||||
if (this.flip) {
|
||||
parts.reverse();
|
||||
|
|
242
lib/parser.js
242
lib/parser.js
File diff suppressed because one or more lines are too long
70
lib/repl.js
70
lib/repl.js
|
@ -1,22 +1,24 @@
|
|||
(function() {
|
||||
var CoffeeScript, error, helpers, readline, repl, run, stdin, stdout;
|
||||
var ACCESSOR, CoffeeScript, SIMPLEVAR, Script, autocomplete, backlog, completeAttribute, completeVariable, error, getCompletions, getPropertyNames, readline, repl, run, stdin, stdout;
|
||||
var __hasProp = Object.prototype.hasOwnProperty;
|
||||
CoffeeScript = require('./coffee-script');
|
||||
helpers = require('./helpers');
|
||||
readline = require('readline');
|
||||
Script = process.binding('evals').Script;
|
||||
stdin = process.openStdin();
|
||||
stdout = process.stdout;
|
||||
error = function(err) {
|
||||
return stdout.write((err.stack || err.toString()) + '\n\n');
|
||||
};
|
||||
helpers.extend(global, {
|
||||
quit: function() {
|
||||
return process.exit(0);
|
||||
}
|
||||
});
|
||||
backlog = '';
|
||||
run = function(buffer) {
|
||||
var val;
|
||||
var code, val;
|
||||
code = backlog += '\n' + buffer.toString();
|
||||
if (code[code.length - 1] === '\\') {
|
||||
return backlog = backlog.slice(0, backlog.length - 1);
|
||||
}
|
||||
backlog = '';
|
||||
try {
|
||||
val = CoffeeScript.eval(buffer.toString(), {
|
||||
val = CoffeeScript.eval(code, {
|
||||
bare: true,
|
||||
globals: true,
|
||||
filename: 'repl'
|
||||
|
@ -29,14 +31,60 @@
|
|||
}
|
||||
return repl.prompt();
|
||||
};
|
||||
ACCESSOR = /\s*([\w\.]+)(?:\.(\w*))$/;
|
||||
SIMPLEVAR = /\s*(\w*)$/i;
|
||||
autocomplete = function(text) {
|
||||
return completeAttribute(text) || completeVariable(text) || [[], text];
|
||||
};
|
||||
completeAttribute = function(text) {
|
||||
var all, completions, match, obj, prefix, val;
|
||||
if (match = text.match(ACCESSOR)) {
|
||||
all = match[0], obj = match[1], prefix = match[2];
|
||||
try {
|
||||
val = Script.runInThisContext(obj);
|
||||
} catch (error) {
|
||||
return [[], text];
|
||||
}
|
||||
completions = getCompletions(prefix, getPropertyNames(val));
|
||||
return [completions, prefix];
|
||||
}
|
||||
};
|
||||
completeVariable = function(text) {
|
||||
var completions, free, scope, _ref;
|
||||
if (free = (_ref = text.match(SIMPLEVAR)) != null ? _ref[1] : void 0) {
|
||||
scope = Script.runInThisContext('this');
|
||||
completions = getCompletions(free, CoffeeScript.RESERVED.concat(getPropertyNames(scope)));
|
||||
return [completions, free];
|
||||
}
|
||||
};
|
||||
getCompletions = function(prefix, candidates) {
|
||||
var el, _i, _len, _results;
|
||||
_results = [];
|
||||
for (_i = 0, _len = candidates.length; _i < _len; _i++) {
|
||||
el = candidates[_i];
|
||||
if (el.indexOf(prefix) === 0) {
|
||||
_results.push(el);
|
||||
}
|
||||
}
|
||||
return _results;
|
||||
};
|
||||
getPropertyNames = function(obj) {
|
||||
var name, _results;
|
||||
_results = [];
|
||||
for (name in obj) {
|
||||
if (!__hasProp.call(obj, name)) continue;
|
||||
_results.push(name);
|
||||
}
|
||||
return _results;
|
||||
};
|
||||
process.on('uncaughtException', error);
|
||||
if (readline.createInterface.length < 3) {
|
||||
repl = readline.createInterface(stdin);
|
||||
repl = readline.createInterface(stdin, autocomplete);
|
||||
stdin.on('data', function(buffer) {
|
||||
return repl.write(buffer);
|
||||
});
|
||||
} else {
|
||||
repl = readline.createInterface(stdin, stdout);
|
||||
repl = readline.createInterface(stdin, stdout, autocomplete);
|
||||
}
|
||||
repl.setPrompt('coffee> ');
|
||||
repl.on('close', function() {
|
||||
|
|
45
package.json
45
package.json
|
@ -1,22 +1,27 @@
|
|||
{
|
||||
"name": "coffee-script",
|
||||
"description": "Unfancy JavaScript",
|
||||
"keywords": ["javascript", "language", "coffeescript", "compiler"],
|
||||
"author": "Jeremy Ashkenas",
|
||||
"version": "1.0.2-pre",
|
||||
"licenses": [{
|
||||
"type": "MIT",
|
||||
"url": "http://github.com/jashkenas/coffee-script/raw/master/LICENSE"
|
||||
}],
|
||||
"engines": {
|
||||
"node": ">=0.2.5"
|
||||
},
|
||||
"directories" : {
|
||||
"lib" : "./lib"
|
||||
},
|
||||
"main" : "./lib/coffee-script",
|
||||
"bin": {
|
||||
"coffee": "./bin/coffee",
|
||||
"cake": "./bin/cake"
|
||||
}
|
||||
"name": "coffee-script",
|
||||
"description": "Unfancy JavaScript",
|
||||
"keywords": ["javascript", "language", "coffeescript", "compiler"],
|
||||
"author": "Jeremy Ashkenas",
|
||||
"version": "1.1.0-pre",
|
||||
"licenses": [{
|
||||
"type": "MIT",
|
||||
"url": "http://github.com/jashkenas/coffee-script/raw/master/LICENSE"
|
||||
}],
|
||||
"engines": {
|
||||
"node": ">=0.2.5"
|
||||
},
|
||||
"directories" : {
|
||||
"lib" : "./lib"
|
||||
},
|
||||
"main" : "./lib/coffee-script",
|
||||
"bin": {
|
||||
"coffee": "./bin/coffee",
|
||||
"cake": "./bin/cake"
|
||||
},
|
||||
"homepage": "http://coffeescript.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/jashkenas/coffee-script.git"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ else if require.registerExtension
|
|||
require.registerExtension '.coffee', (content) -> compile content
|
||||
|
||||
# The current CoffeeScript version number.
|
||||
exports.VERSION = '1.0.2-pre'
|
||||
exports.VERSION = '1.1.0-pre'
|
||||
|
||||
# Words that cannot be used as identifiers in CoffeeScript code
|
||||
exports.RESERVED = RESERVED
|
||||
|
|
|
@ -165,7 +165,7 @@ writeJs = (source, js, base) ->
|
|||
if err
|
||||
printLine err.message
|
||||
else if opts.compile and opts.watch
|
||||
console.log "#{(new Date).toTimeString()} - compiled #{source}"
|
||||
console.log "#{(new Date).toLocaleTimeString()} - compiled #{source}"
|
||||
path.exists dir, (exists) ->
|
||||
if exists then compile() else exec "mkdir -p #{dir}", compile
|
||||
|
||||
|
|
|
@ -246,15 +246,19 @@ grammar =
|
|||
o ':: Identifier', -> new Access $2, 'proto'
|
||||
o '::', -> new Access new Literal 'prototype'
|
||||
o 'Index'
|
||||
o 'Slice', -> new Slice $1
|
||||
]
|
||||
|
||||
# Indexing into an object or array using bracket notation.
|
||||
Index: [
|
||||
o 'INDEX_START Expression INDEX_END', -> new Index $2
|
||||
o 'INDEX_START IndexValue INDEX_END', -> $2
|
||||
o 'INDEX_SOAK Index', -> extend $2, soak : yes
|
||||
o 'INDEX_PROTO Index', -> extend $2, proto: yes
|
||||
]
|
||||
|
||||
IndexValue: [
|
||||
o 'Expression', -> new Index $1
|
||||
o 'Slice', -> new Slice $1
|
||||
]
|
||||
|
||||
# In CoffeeScript, an object literal is simply a list of assignments.
|
||||
Object: [
|
||||
|
@ -334,9 +338,9 @@ grammar =
|
|||
|
||||
# Array slice literals.
|
||||
Slice: [
|
||||
o 'INDEX_START Expression RangeDots Expression INDEX_END', -> new Range $2, $4, $3
|
||||
o 'INDEX_START Expression RangeDots INDEX_END', -> new Range $2, null, $3
|
||||
o 'INDEX_START RangeDots Expression INDEX_END', -> new Range null, $3, $2
|
||||
o 'Expression RangeDots Expression', -> new Range $1, $3, $2
|
||||
o 'Expression RangeDots', -> new Range $1, null, $2
|
||||
o 'RangeDots Expression', -> new Range null, $2, $1
|
||||
]
|
||||
|
||||
# The **ArgList** is both the list of objects passed into a function call,
|
||||
|
|
|
@ -80,7 +80,8 @@ exports.Lexer = class Lexer
|
|||
@token 'OWN', id
|
||||
return id.length
|
||||
forcedIdentifier = colon or
|
||||
(prev = last @tokens) and not prev.spaced and prev[0] in ['.', '?.', '@', '::']
|
||||
(prev = last @tokens) and (prev[0] in ['.', '?.', '::'] or
|
||||
not prev.spaced and prev[0] is '@')
|
||||
tag = 'IDENTIFIER'
|
||||
|
||||
if id in JS_KEYWORDS or
|
||||
|
@ -170,11 +171,11 @@ exports.Lexer = class Lexer
|
|||
commentToken: ->
|
||||
return 0 unless match = @chunk.match COMMENT
|
||||
[comment, here] = match
|
||||
@line += count comment, '\n'
|
||||
if here
|
||||
@token 'HERECOMMENT', @sanitizeHeredoc here,
|
||||
herecomment: true, indent: Array(@indent + 1).join(' ')
|
||||
@token 'TERMINATOR', '\n'
|
||||
@line += count comment, '\n'
|
||||
comment.length
|
||||
|
||||
# Matches JavaScript interpolated directly into the source via backticks.
|
||||
|
@ -343,8 +344,11 @@ exports.Lexer = class Lexer
|
|||
# erasing all external indentation on the left-hand side.
|
||||
sanitizeHeredoc: (doc, options) ->
|
||||
{indent, herecomment} = options
|
||||
return doc if herecomment and 0 > doc.indexOf '\n'
|
||||
unless herecomment
|
||||
if herecomment
|
||||
if HEREDOC_ILLEGAL.test doc
|
||||
throw new Error "block comment cannot contain \"*/\", starting on line #{@line + 1}"
|
||||
return doc if doc.indexOf('\n') <= 0
|
||||
else
|
||||
while match = HEREDOC_INDENT.exec doc
|
||||
attempt = match[1]
|
||||
indent = attempt if indent is null or 0 < attempt.length < indent.length
|
||||
|
@ -367,7 +371,7 @@ exports.Lexer = class Lexer
|
|||
stack.push tok
|
||||
when '(', 'CALL_START'
|
||||
if stack.length then stack.pop()
|
||||
else
|
||||
else if tok[0] is '('
|
||||
tok[0] = 'PARAM_START'
|
||||
return this
|
||||
this
|
||||
|
@ -593,9 +597,11 @@ MULTILINER = /\n/g
|
|||
|
||||
HEREDOC_INDENT = /\n+([^\n\S]*)/g
|
||||
|
||||
HEREDOC_ILLEGAL = /\*\//
|
||||
|
||||
ASSIGNED = /^\s*@?([$A-Za-z_][$\w\x7f-\uffff]*|['"].*['"])[^\n\S]*?[:=][^:=>]/
|
||||
|
||||
LINE_CONTINUER = /// ^ \s* (?: , | \??\.(?!\.) | :: ) ///
|
||||
LINE_CONTINUER = /// ^ \s* (?: , | \??\.(?![.\d]) | :: ) ///
|
||||
|
||||
TRAILING_SPACES = /\s+$/
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ exports.Base = class Base
|
|||
# Statements converted into expressions via closure-wrapping share a scope
|
||||
# object with their parent closure, to preserve the expected lexical scope.
|
||||
compileClosure: (o) ->
|
||||
if @jumps()
|
||||
if @jumps() or this instanceof Throw
|
||||
throw SyntaxError 'cannot use a pure statement in an expression.'
|
||||
o.sharedScope = yes
|
||||
Closure.wrap(this).compileNode o
|
||||
|
@ -498,12 +498,31 @@ exports.Call = class Call extends Base
|
|||
ifn = unfoldSoak o, call, 'variable'
|
||||
ifn
|
||||
|
||||
# Walk through the objects in the arguments, moving over simple values.
|
||||
# This allows syntax like `call a: b, c` into `call({a: b}, c);`
|
||||
filterImplicitObjects: (list) ->
|
||||
nodes = []
|
||||
for node in list
|
||||
unless node.isObject?() and node.base.generated
|
||||
nodes.push node
|
||||
continue
|
||||
obj = null
|
||||
for prop in node.base.properties
|
||||
if prop instanceof Assign
|
||||
nodes.push obj = new Obj properties = [], true if not obj
|
||||
properties.push prop
|
||||
else
|
||||
nodes.push prop
|
||||
obj = null
|
||||
nodes
|
||||
|
||||
# Compile a vanilla function call.
|
||||
compileNode: (o) ->
|
||||
@variable?.front = @front
|
||||
if code = Splat.compileSplattedArray o, @args, true
|
||||
return @compileSplat o, code
|
||||
args = (arg.compile o, LEVEL_LIST for arg in @args).join ', '
|
||||
args = @filterImplicitObjects @args
|
||||
args = (arg.compile o, LEVEL_LIST for arg in args).join ', '
|
||||
if @isSuper
|
||||
@superReference(o) + ".call(this#{ args and ', ' + args })"
|
||||
else
|
||||
|
@ -700,6 +719,9 @@ exports.Obj = class Obj extends Base
|
|||
compileNode: (o) ->
|
||||
props = @properties
|
||||
return (if @front then '({})' else '{}') unless props.length
|
||||
if @generated
|
||||
for node in props when node instanceof Value
|
||||
throw new Error 'cannot have an implicit value in an implicit object'
|
||||
idt = o.indent += TAB
|
||||
lastNoncom = @lastNonComment @properties
|
||||
props = for prop, i in props
|
||||
|
@ -734,11 +756,14 @@ exports.Arr = class Arr extends Base
|
|||
|
||||
children: ['objects']
|
||||
|
||||
filterImplicitObjects: Call::filterImplicitObjects
|
||||
|
||||
compileNode: (o) ->
|
||||
return '[]' unless @objects.length
|
||||
o.indent += TAB
|
||||
return code if code = Splat.compileSplattedArray o, @objects
|
||||
code = (obj.compile o, LEVEL_LIST for obj in @objects).join ', '
|
||||
objs = @filterImplicitObjects @objects
|
||||
return code if code = Splat.compileSplattedArray o, objs
|
||||
code = (obj.compile o, LEVEL_LIST for obj in objs).join ', '
|
||||
if code.indexOf('\n') >= 0
|
||||
"[\n#{o.indent}#{code}\n#{@tab}]"
|
||||
else
|
||||
|
@ -1029,6 +1054,7 @@ exports.Code = class Code extends Base
|
|||
vars = []
|
||||
exprs = []
|
||||
for param in @params when param.splat
|
||||
o.scope.add param.name.value, 'var' if param.name.value
|
||||
splats = new Assign new Value(new Arr(p.asReference o for p in @params)),
|
||||
new Value new Literal 'arguments'
|
||||
break
|
||||
|
@ -1187,10 +1213,13 @@ exports.While = class While extends Base
|
|||
exports.Op = class Op extends Base
|
||||
constructor: (op, first, second, flip) ->
|
||||
return new In first, second if op is 'in'
|
||||
return new Call first, first.params or [] if op is 'do'
|
||||
if op is 'do'
|
||||
call = new Call first, first.params or []
|
||||
call.do = yes
|
||||
return call
|
||||
if op is 'new'
|
||||
return first.newInstance() if first instanceof Call
|
||||
first = new Parens first if first instanceof Code and first.bound
|
||||
return first.newInstance() if first instanceof Call and not first.do
|
||||
first = new Parens first if first instanceof Code and first.bound or first.do
|
||||
@operator = CONVERSIONS[op] or op
|
||||
@first = first
|
||||
@second = second
|
||||
|
@ -1284,6 +1313,7 @@ exports.Op = class Op extends Base
|
|||
parts = [op = @operator]
|
||||
parts.push ' ' if op in ['new', 'typeof', 'delete'] or
|
||||
op in ['+', '-'] and @first instanceof Op and @first.operator is op
|
||||
@first = new Parens @first if op is 'new' and @first.isStatement o
|
||||
parts.push @first.compile o, LEVEL_OP
|
||||
parts.reverse() if @flip
|
||||
parts.join ''
|
||||
|
|
|
@ -6,40 +6,82 @@
|
|||
|
||||
# Require the **coffee-script** module to get access to the compiler.
|
||||
CoffeeScript = require './coffee-script'
|
||||
helpers = require './helpers'
|
||||
readline = require 'readline'
|
||||
Script = process.binding('evals').Script
|
||||
|
||||
# REPL Setup
|
||||
|
||||
# Start by opening up `stdin` and `stdout`.
|
||||
stdin = process.openStdin()
|
||||
stdin = process.openStdin()
|
||||
stdout = process.stdout
|
||||
|
||||
# Log an error.
|
||||
error = (err) ->
|
||||
stdout.write (err.stack or err.toString()) + '\n\n'
|
||||
|
||||
# Quick alias for quitting the REPL.
|
||||
helpers.extend global, quit: -> process.exit(0)
|
||||
# The current backlog of multi-line code.
|
||||
backlog = ''
|
||||
|
||||
# The main REPL function. **run** is called every time a line of code is entered.
|
||||
# Attempt to evaluate the command. If there's an exception, print it out instead
|
||||
# of exiting.
|
||||
run = (buffer) ->
|
||||
code = backlog += '\n' + buffer.toString()
|
||||
if code[code.length - 1] is '\\'
|
||||
return backlog = backlog[0...backlog.length - 1]
|
||||
backlog = ''
|
||||
try
|
||||
val = CoffeeScript.eval buffer.toString(), bare: on, globals: on, filename: 'repl'
|
||||
val = CoffeeScript.eval code, bare: on, globals: on, filename: 'repl'
|
||||
process.stdout.write val + '\n' if val isnt undefined
|
||||
catch err
|
||||
error err
|
||||
repl.prompt()
|
||||
|
||||
## Autocompletion
|
||||
|
||||
# Regexes to match complete-able bits of text.
|
||||
ACCESSOR = /\s*([\w\.]+)(?:\.(\w*))$/
|
||||
SIMPLEVAR = /\s*(\w*)$/i
|
||||
|
||||
# Returns a list of completions, and the completed text.
|
||||
autocomplete = (text) ->
|
||||
completeAttribute(text) or completeVariable(text) or [[], text]
|
||||
|
||||
# Attempt to autocomplete a chained dotted attribute: `one.two.three`.
|
||||
completeAttribute = (text) ->
|
||||
if match = text.match ACCESSOR
|
||||
[all, obj, prefix] = match
|
||||
try
|
||||
val = Script.runInThisContext obj
|
||||
catch error
|
||||
return [[], text]
|
||||
completions = getCompletions prefix, getPropertyNames val
|
||||
[completions, prefix]
|
||||
|
||||
# Attempt to autocomplete an in-scope free variable: `one`.
|
||||
completeVariable = (text) ->
|
||||
if free = text.match(SIMPLEVAR)?[1]
|
||||
scope = Script.runInThisContext 'this'
|
||||
completions = getCompletions free, CoffeeScript.RESERVED.concat(getPropertyNames scope)
|
||||
[completions, free]
|
||||
|
||||
# Return elements of candidates for which `prefix` is a prefix.
|
||||
getCompletions = (prefix, candidates) ->
|
||||
(el for el in candidates when el.indexOf(prefix) is 0)
|
||||
|
||||
# Return all "own" properties of an object.
|
||||
getPropertyNames = (obj) ->
|
||||
(name for own name of obj)
|
||||
|
||||
# Make sure that uncaught exceptions don't kill the REPL.
|
||||
process.on 'uncaughtException', error
|
||||
|
||||
# Create the REPL by listening to **stdin**.
|
||||
if readline.createInterface.length < 3
|
||||
repl = readline.createInterface stdin
|
||||
repl = readline.createInterface stdin, autocomplete
|
||||
stdin.on 'data', (buffer) -> repl.write buffer
|
||||
else
|
||||
repl = readline.createInterface stdin, stdout
|
||||
repl = readline.createInterface stdin, stdout, autocomplete
|
||||
|
||||
repl.setPrompt 'coffee> '
|
||||
repl.on 'close', -> stdin.destroy()
|
||||
|
|
|
@ -36,6 +36,28 @@ test "array splat expansions with assignments", ->
|
|||
eq 4, b
|
||||
arrayEq [0,1,2,3,4], list
|
||||
|
||||
|
||||
test "mixed shorthand objects in array lists", ->
|
||||
|
||||
arr = [
|
||||
a:1
|
||||
'b'
|
||||
c:1
|
||||
]
|
||||
ok arr.length is 3
|
||||
ok arr[2].c is 1
|
||||
|
||||
arr = [b: 1, a: 2, 100]
|
||||
eq arr[1], 100
|
||||
|
||||
arr = [a:0, b:1, (1 + 1)]
|
||||
eq arr[1], 2
|
||||
|
||||
arr = [a:1, 'a', b:1, 'b']
|
||||
eq arr.length, 4
|
||||
eq arr[2].b, 1
|
||||
eq arr[3], 'b'
|
||||
|
||||
test "array splats with nested arrays", ->
|
||||
nonce = {}
|
||||
a = [nonce]
|
||||
|
|
|
@ -51,3 +51,6 @@ test "#1026", ->
|
|||
else
|
||||
d
|
||||
'''
|
||||
|
||||
test "#1050", ->
|
||||
cantCompile "### */ ###"
|
||||
|
|
|
@ -73,6 +73,18 @@ test "`?.` and `::` should continue lines", ->
|
|||
#::
|
||||
#?.foo
|
||||
|
||||
doesNotThrow -> CoffeeScript.compile """
|
||||
oh. yes
|
||||
oh?. true
|
||||
oh:: return
|
||||
"""
|
||||
|
||||
doesNotThrow -> CoffeeScript.compile """
|
||||
a?[b..]
|
||||
a?[...b]
|
||||
a?[b..c]
|
||||
"""
|
||||
|
||||
# Array Literals
|
||||
|
||||
test "indented array literals don't trigger whitespace rewriting", ->
|
||||
|
|
|
@ -339,6 +339,12 @@ test "passing splats to functions", ->
|
|||
arrayEq [2..6], others
|
||||
eq 7, last
|
||||
|
||||
test "splat variables are local to the function", ->
|
||||
outer = "x"
|
||||
clobber = (avar, outer...) -> outer
|
||||
clobber "foo", "bar"
|
||||
eq "x", outer
|
||||
|
||||
|
||||
test "Issue 894: Splatting against constructor-chained functions.", ->
|
||||
|
||||
|
@ -431,3 +437,9 @@ test "don't wrap 'pure' statements in a closure", ->
|
|||
for item in items
|
||||
return item if item is nonce
|
||||
eq nonce, fn items
|
||||
|
||||
#### Unusual `new` Usage
|
||||
|
||||
test "usage of `new` is careful about where the invocation parens end up", ->
|
||||
eq 'object', typeof new try Array
|
||||
eq 'object', typeof new do -> ->
|
||||
|
|
|
@ -147,3 +147,11 @@ test "default values with splatted arguments", ->
|
|||
eq 5, withSplats(1,1)
|
||||
eq 1, withSplats(1,1,1)
|
||||
eq 2, withSplats(1,1,1,1)
|
||||
|
||||
test "default values with function calls", ->
|
||||
doesNotThrow -> CoffeeScript.compile "(x = f()) ->"
|
||||
|
||||
test "arguments vs parameters", ->
|
||||
doesNotThrow -> CoffeeScript.compile "f(x) ->"
|
||||
f = (g) -> g()
|
||||
eq 5, f (x) -> 5
|
||||
|
|
|
@ -37,3 +37,8 @@ eq Number::toString, .42['toString']
|
|||
|
||||
eq Number::toString, 4.2.toString
|
||||
eq Number::toString, .42.toString
|
||||
|
||||
test '#1168: leading floating point suppresses newline', ->
|
||||
eq 1, do ->
|
||||
1
|
||||
.5 + 0.5
|
||||
|
|
|
@ -183,6 +183,22 @@ test "invoking functions with implicit object literals", ->
|
|||
ok result.length is 3
|
||||
ok result[2].c is 1
|
||||
|
||||
result = getA b: 13, a: 42, 2
|
||||
eq 42, result
|
||||
|
||||
result = getArgs a:1, (1 + 1)
|
||||
ok result[1] is 2
|
||||
|
||||
result = getArgs a:1, b
|
||||
ok result.length is 2
|
||||
ok result[1] is 30
|
||||
|
||||
result = getArgs a:1, b, b:1, a
|
||||
ok result.length is 4
|
||||
ok result[2].b is 1
|
||||
|
||||
throws -> CoffeeScript.compile "a = b:1, c"
|
||||
|
||||
test "some weird indentation in YAML-style object literals", ->
|
||||
two = (a, b) -> b
|
||||
obj = then two 1,
|
||||
|
@ -193,3 +209,4 @@ test "some weird indentation in YAML-style object literals", ->
|
|||
d: e
|
||||
f: 1
|
||||
eq 1, obj[1]
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue