1
0
Fork 0
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:
Jeremy Ashkenas 2011-03-12 07:41:14 -06:00
commit 963adb5230
22 changed files with 491 additions and 220 deletions

View file

@ -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'

View file

@ -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) {

View file

@ -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);
}
});
};

View file

@ -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: [

View file

@ -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')) {
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;
}
if (!herecomment) {
} 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 = ['-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?=', '<<=', '>>=', '>>>=', '&=', '^=', '|='];

View file

@ -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();

File diff suppressed because one or more lines are too long

View file

@ -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() {

View file

@ -3,7 +3,7 @@
"description": "Unfancy JavaScript",
"keywords": ["javascript", "language", "coffeescript", "compiler"],
"author": "Jeremy Ashkenas",
"version": "1.0.2-pre",
"version": "1.1.0-pre",
"licenses": [{
"type": "MIT",
"url": "http://github.com/jashkenas/coffee-script/raw/master/LICENSE"
@ -18,5 +18,10 @@
"bin": {
"coffee": "./bin/coffee",
"cake": "./bin/cake"
},
"homepage": "http://coffeescript.org",
"repository": {
"type": "git",
"url": "git://github.com/jashkenas/coffee-script.git"
}
}

View file

@ -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

View file

@ -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

View file

@ -246,16 +246,20 @@ 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: [
o '{ AssignList OptComma }', -> new Obj $2, $1.generated
@ -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,

View file

@ -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+$/

View file

@ -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 ''

View file

@ -6,8 +6,10 @@
# 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()
@ -17,29 +19,69 @@ stdout = process.stdout
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()

View file

@ -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]

View file

@ -51,3 +51,6 @@ test "#1026", ->
else
d
'''
test "#1050", ->
cantCompile "### */ ###"

View file

@ -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", ->

View file

@ -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 -> ->

View file

@ -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

View file

@ -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

View file

@ -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]