1
0
Fork 0
mirror of https://github.com/jashkenas/coffeescript.git synced 2022-11-09 12:23:24 -05:00
Conflicts:
	lib/coffee-script/lexer.js
	lib/coffee-script/parser.js
	lib/coffee-script/rewriter.js
	src/lexer.coffee
	src/rewriter.coffee
This commit is contained in:
Andreas Lubbe 2014-01-25 19:37:35 -08:00
commit f375394381
42 changed files with 1011 additions and 508 deletions

View file

@ -161,7 +161,7 @@ task 'bench', 'quick benchmark of compilation time', ->
# Run the CoffeeScript test suite.
runTests = (CoffeeScript) ->
require './lib/coffee-script/extensions'
CoffeeScript.register()
startTime = Date.now()
currentFile = null
passedTests = 0

2
README
View file

@ -46,5 +46,5 @@
The source repository:
git://github.com/jashkenas/coffee-script.git
All contributors are listed here:
Top 100 contributors are listed here:
http://github.com/jashkenas/coffee-script/contributors

View file

@ -1064,6 +1064,11 @@ Expressions
is a new book from Packt Publishing that introduces CoffeeScript while
walking through the process of building a demonstration web application.
</li>
<li>
<a href="http://www.manning.com/lee/">CoffeeScript in Action</a>
is a new book from Manning Publications that covers CoffeeScript syntax, composition techniques
and application development.
</li>
</ul>
<h2>

View file

@ -31,7 +31,7 @@ File.open = (path, mode, block) ->
# Write.
write = (location, data) ->
path = new Pathname location
throw new Error "Location does not exist" unless fs.existsSync(location)
throw new Error "Location does not exist" unless fs.existsSync location
File.open path, 'w', (file) ->
return false if Digest.MD5.hexdigest(file.read()) is data.hash()

View file

@ -13,7 +13,7 @@ run_loop = ->
wait()
# Objects:
dense_object_literal = {one: 1, two: 2, three: 3}
dense_object_literal = one: 1, two: 2, three: 3
spaced_out_multiline_object =
pi: 3.14159
@ -56,7 +56,7 @@ race = ->
run()
walk()
crawl()
if tired then return sleep()
return sleep() if tired
race()
# Conditional assignment:
@ -64,7 +64,7 @@ good or= evil
wine and= cheese
# Nested property access and calls.
((moon.turn(360))).shapes[3].move({x: 45, y: 30}).position['top'].offset('x')
(moon.turn 360).shapes[3].move(x: 45, y: 30).position['top'].offset('x')
a = b = c = 5
@ -79,7 +79,7 @@ try
dogs_and_cats_living_together()
throw "up"
catch error
print(error)
print error
finally
clean_up()

View file

@ -22,4 +22,4 @@ binary_search = (items, value) ->
console.log 2 is binary_search [10, 20, 30, 40, 50], 30
console.log 4 is binary_search [-97, 35, 67, 88, 1200], 1200
console.log 0 is binary_search [0, 45, 70], 0
console.log(-1 is binary_search [0, 45, 70], 10)
console.log -1 is binary_search [0, 45, 70], 10

View file

@ -1,8 +1,8 @@
# A bubble sort implementation, sorting the given array in-place.
bubble_sort = (list) ->
for i in [0...list.length]
for j in [0...list.length - i]
[list[j], list[j+1]] = [list[j+1], list[j]] if list[j] > list[j+1]
for j in [0...list.length - i] when list[j] > list[j + 1]
[list[j], list[j+1]] = [list[j + 1], list[j]]
list

View file

@ -1,8 +1,8 @@
# "Classic" linked list implementation that doesn't keep track of its size.
class LinkedList
->
this._head = null # Pointer to the first item in the list.
constructor: ->
@_head = null # Pointer to the first item in the list.
# Appends some data to the end of the list. This method traverses the existing
@ -12,10 +12,10 @@ class LinkedList
# Create a new node object to wrap the data.
node = data: data, next: null
current = this._head or= node
current = @_head or= node
if this._head isnt node
(current = current.next) while current.next
if @_head isnt node
current = current.next while current.next
current.next = node
this
@ -27,11 +27,11 @@ class LinkedList
# Check for out-of-bounds values.
return null if index < 0
current = this._head or null
current = @_head or null
i = -1
# Advance through the list.
(current = current.next) while current and index > (i += 1)
current = current.next while current and index > ++i
# Return null if we've reached the end.
current and current.data
@ -43,16 +43,16 @@ class LinkedList
# Check for out-of-bounds values.
return null if index < 0
current = this._head or null
current = @_head or null
i = -1
# Special case: removing the first item.
if index is 0
this._head = current.next
@_head = current.next
else
# Find the right location.
([previous, current] = [current, current.next]) while index > (i += 1)
[previous, current] = [current, current.next] while index > ++i
# Skip over the item to remove.
previous.next = current.next
@ -63,7 +63,7 @@ class LinkedList
# Calculate the number of items in the list.
size: ->
current = this._head
current = @_head
count = 0
while current
@ -76,7 +76,7 @@ class LinkedList
# Convert the list into an array.
toArray: ->
result = []
current = this._head
current = @_head
while current
result.push current.data
@ -86,7 +86,7 @@ class LinkedList
# The string representation of the linked list.
toString: -> this.toArray().toString()
toString: -> @toArray().toString()
# Tests.

View file

@ -7,13 +7,13 @@ is_valid_identifier = (identifier) ->
sum = 0
alt = false
for i in [identifier.length - 1..0] by -1
for c in identifier by -1
# Get the next digit.
num = parseInt identifier.charAt(i), 10
num = parseInt c, 10
# If it's not a valid number, abort.
return false if isNaN(num)
return false if isNaN num
# If it's an alternate number...
if alt

View file

@ -3,13 +3,12 @@ merge_sort = (list) ->
return list if list.length is 1
result = []
pivot = Math.floor list.length / 2
left = merge_sort list.slice 0, pivot
right = merge_sort list.slice pivot
while left.length and right.length
result.push(if left[0] < right[0] then left.shift() else right.shift())
result = while left.length and right.length
if left[0] < right[0] then left.shift() else right.shift()
result.concat(left).concat(right)

View file

@ -1,8 +1,11 @@
# Examples from the Poignant Guide.
# These are examples of syntax differences between CoffeeScript and Ruby,
# they won't run.
# ['toast', 'cheese', 'wine'].each { |food| print food.capitalize }
['toast', 'wine', 'cheese'].each (food) -> print food.capitalize()
print food.capitalize() for food in ['toast', 'wine', 'cheese']
@ -42,13 +45,10 @@ LotteryDraw =
play: ->
result = LotteryTicket.new_random()
winners = {}
this.tickets.each (buyer, ticket_list) ->
ticket_list.each (ticket) ->
score = ticket.score result
return if score is 0
winners[buyer] or= []
winners[buyer].push [ticket, score]
this.tickets = {}
for buyer, ticketList of @tickets
for ticket in ticketList when (score = ticket.score result) isnt 0
(winners[buyer] or= []).push [ticket, score]
@tickets = {}
winners
@ -64,7 +64,7 @@ LotteryDraw =
WishScanner =
scan_for_a_wish: ->
wish = this.read().detect (thought) -> thought.index('wish: ') is 0
wish = @read().detect (thought) -> thought.indexOf('wish: ') is 0
wish.replace 'wish: ', ''
@ -109,28 +109,28 @@ Creature =
# This method applies a hit taken during a fight.
hit: (damage) ->
p_up = Math.rand this.charisma
p_up = Math.rand @charisma
if p_up % 9 is 7
this.life += p_up / 4
console.log "[" + this.name + " magick powers up " + p_up + "!]"
this.life -= damage
if this.life <= 0 then console.log "[" + this.name + " has died.]"
@life += p_up / 4
console.log "[#{@name} magick powers up #{p_up}!]"
@life -= damage
if @life <= 0 then console.log "[#{@name} has died.]"
# This method takes one turn in a fight.
fight: (enemy, weapon) ->
if this.life <= 0 then return console.log "[" + this.name + "is too dead to fight!]"
return console.log "[#{@name} is too dead to fight!]" if @life <= 0
# Attack the opponent.
your_hit = Math.rand this.strength + weapon
console.log "[You hit with " + your_hit + "points of damage!]"
your_hit = Math.rand @strength + weapon
console.log "[You hit with #{your_hit}points of damage!]"
enemy.hit your_hit
# Retaliation.
console.log enemy
if enemy.life > 0
enemy_hit = Math.rand enemy.strength + enemy.weapon
console.log "[Your enemy hit with " + enemy_hit + "points of damage!]"
this.hit enemy_hit
console.log "[Your enemy hit with #{enemy_hit}points of damage!]"
@hit enemy_hit
@ -156,7 +156,7 @@ code_words.each (real, code) -> idea.replace(real, code)
# Save the jibberish to a new file
print "File encoded. Please enter a name for this idea: "
idea_name = gets().strip()
File.open "idea-" + idea_name + '.txt', 'w', (file) -> file.write idea
File.open "idea-#{idea_name}.txt", 'w', (file) -> file.write idea
@ -174,8 +174,8 @@ File.open "idea-" + idea_name + '.txt', 'w', (file) -> file.write idea
wipe_mutterings_from = (sentence) ->
throw new Error "cannot wipe mutterings" unless sentence.indexOf
while sentence.indexOf('(') >= 0
open = sentence.indexOf('(') - 1
close = sentence.indexOf(')') + 1
sentence = sentence.slice(0, open) + sentence.slice(close, sentence.length)
while '(' in sentence
open = sentence.indexOf('(')
close = sentence.indexOf(')')
sentence = "#{sentence[0...open]}#{sentence[close + 1..]}"
sentence

View file

@ -45,7 +45,7 @@ foods[2]
# (key, ' is a ', val) join print.
for key, val of {dog: 'canine', cat: 'feline', fox: 'vulpine'}
print key + ' is a ' + val
print "#{key} is a #{val}"
# Person = class: /name, /age, /sex.
@ -54,7 +54,7 @@ for key, val of {dog: 'canine', cat: 'feline', fox: 'vulpine'}
class Person
print: ->
print 'My name is ' + @name + '.'
print "My name is #{@name}."
# p = Person ()
@ -74,7 +74,7 @@ class Policeman extends Person
(@rank) ->
print: ->
print 'My name is ' + @name + " and I'm a " + @rank + '.'
print "My name is #{@name} and I'm a #{@rank}."
print new Policeman 'Constable'
@ -180,7 +180,7 @@ if 3.gender?
# session = url query ? at ('session').
HomePage::get = (url) ->
session = url.query.session if url.query?
session = url.query?.session
# BTree = class: /left, /right.

View file

@ -7,6 +7,6 @@ server = http.createServer (req, res) ->
res.write 'Hello, World!'
res.end()
server.listen 3000
server.listen PORT = 3000
console.log "Server running at http://localhost:3000/"
console.log "Server running at http://localhost:#{PORT}/"

View file

@ -182,6 +182,10 @@
}
};
exports.register = function() {
return require('./register');
};
exports._compileFile = function(filename, sourceMap) {
var answer, err, raw, stripped;
if (sourceMap == null) {
@ -210,6 +214,7 @@
token = this.tokens[this.pos++];
if (token) {
tag = token[0], this.yytext = token[1], this.yylloc = token[2];
this.errorToken = token.origin || token;
this.yylineno = this.yylloc.first_line;
} else {
tag = '';
@ -228,10 +233,12 @@
parser.yy = require('./nodes');
parser.yy.parseError = function(message, _arg) {
var token;
var errorLoc, errorText, errorToken, ignored, token, tokens, _ref;
token = _arg.token;
message = "unexpected " + (token === 1 ? 'end of input' : token);
return helpers.throwSyntaxError(message, parser.lexer.yylloc);
_ref = parser.lexer, errorToken = _ref.errorToken, tokens = _ref.tokens;
ignored = errorToken[0], errorText = errorToken[1], errorLoc = errorToken[2];
errorText = errorToken === tokens[tokens.length - 1] ? 'end of input' : helpers.nameWhitespaceCharacter(errorText);
return helpers.throwSyntaxError("unexpected " + errorText, errorLoc);
};
formatSourcePosition = function(frame, getSourceMapping) {

View file

@ -1,6 +1,6 @@
// Generated by CoffeeScript 1.6.3
(function() {
var BANNER, CoffeeScript, EventEmitter, SWITCHES, compileJoin, compileOptions, compilePath, compileScript, compileStdio, exec, forkNode, fs, helpers, hidden, joinTimeout, mkdirp, notSources, optionParser, optparse, opts, outputPath, parseOptions, path, printLine, printTokens, printWarn, removeSource, removeSourceDir, sourceCode, sources, spawn, timeLog, usage, useWinPathSep, version, wait, watch, watchDir, watchedDirs, writeJs, _ref,
var BANNER, CoffeeScript, EventEmitter, SWITCHES, compileJoin, compileOptions, compilePath, compileScript, compileStdio, exec, findDirectoryIndex, forkNode, fs, helpers, hidden, joinTimeout, mkdirp, notSources, optionParser, optparse, opts, outputPath, parseOptions, path, printLine, printTokens, printWarn, removeSource, removeSourceDir, silentUnlink, sourceCode, sources, spawn, timeLog, usage, useWinPathSep, version, wait, watch, watchDir, watchedDirs, 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');
@ -117,6 +117,10 @@
notSources[source] = true;
return;
}
if (opts.run) {
compilePath(findDirectoryIndex(source), topLevel, base);
return;
}
if (opts.watch) {
watchDir(source, base);
}
@ -159,6 +163,27 @@
}
};
findDirectoryIndex = function(source) {
var err, ext, index, _i, _len, _ref1;
_ref1 = CoffeeScript.FILE_EXTENSIONS;
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
ext = _ref1[_i];
index = path.join(source, "index" + ext);
try {
if ((fs.statSync(index)).isFile()) {
return index;
}
} catch (_error) {
err = _error;
if (err.code !== 'ENOENT') {
throw err;
}
}
}
console.error("Missing index.coffee or index.litcoffee in " + source);
return process.exit(1);
};
compileScript = function(file, input, base) {
var compiled, err, message, o, options, t, task;
if (base == null) {
@ -178,6 +203,7 @@
} else if (o.nodes) {
return printLine(CoffeeScript.nodes(t.input, t.options).toString().trim());
} else if (o.run) {
CoffeeScript.register();
return CoffeeScript.run(t.input, t.options);
} else if (o.join && t.file !== o.join) {
if (helpers.isLiterate(file)) {
@ -246,25 +272,24 @@
};
watch = function(source, base) {
var compile, compileTimeout, e, prevStats, rewatch, watchErr, watcher;
var compile, compileTimeout, err, prevStats, rewatch, startWatcher, watchErr, watcher;
watcher = null;
prevStats = null;
compileTimeout = null;
watchErr = function(e) {
if (e.code === 'ENOENT') {
if (sources.indexOf(source) === -1) {
watchErr = function(err) {
if (err.code !== 'ENOENT') {
throw err;
}
if (__indexOf.call(sources, source) < 0) {
return;
}
try {
rewatch();
return compile();
} catch (_error) {
e = _error;
removeSource(source, base, true);
removeSource(source, base);
return compileJoin();
}
} else {
throw e;
}
};
compile = function() {
clearTimeout(compileTimeout);
@ -287,26 +312,39 @@
});
});
};
try {
watcher = fs.watch(source, compile);
} catch (_error) {
e = _error;
watchErr(e);
startWatcher = function() {
return watcher = fs.watch(source).on('change', compile).on('error', function(err) {
if (err.code !== 'EPERM') {
throw err;
}
return rewatch = function() {
return removeSource(source, base);
});
};
rewatch = function() {
if (watcher != null) {
watcher.close();
}
return watcher = fs.watch(source, compile);
return startWatcher();
};
try {
return startWatcher();
} catch (_error) {
err = _error;
return watchErr(err);
}
};
watchDir = function(source, base) {
var e, readdirTimeout, watcher;
var err, readdirTimeout, startWatcher, stopWatcher, watcher;
watcher = null;
readdirTimeout = null;
try {
watchedDirs[source] = true;
return watcher = fs.watch(source, function() {
startWatcher = function() {
return watcher = fs.watch(source).on('error', function(err) {
if (err.code !== 'EPERM') {
throw err;
}
return stopWatcher();
}).on('change', function() {
clearTimeout(readdirTimeout);
return readdirTimeout = wait(25, function() {
var err, file, files, _i, _len, _results;
@ -317,8 +355,7 @@
if (err.code !== 'ENOENT') {
throw err;
}
watcher.close();
return removeSourceDir(source, base);
return stopWatcher();
}
_results = [];
for (_i = 0, _len = files.length; _i < _len; _i++) {
@ -328,10 +365,18 @@
return _results;
});
});
};
stopWatcher = function() {
watcher.close();
return removeSourceDir(source, base);
};
watchedDirs[source] = true;
try {
return startWatcher();
} catch (_error) {
e = _error;
if (e.code !== 'ENOENT') {
throw e;
err = _error;
if (err.code !== 'ENOENT') {
throw err;
}
}
};
@ -345,7 +390,7 @@
if (!(source === path.dirname(file))) {
continue;
}
removeSource(file, base, true);
removeSource(file, base);
sourcesChanged = true;
}
if (sourcesChanged) {
@ -353,23 +398,28 @@
}
};
removeSource = function(source, base, removeJs) {
var err, index, jsPath;
removeSource = function(source, base) {
var index;
index = sources.indexOf(source);
sources.splice(index, 1);
sourceCode.splice(index, 1);
if (removeJs && !opts.join) {
jsPath = outputPath(source, base);
if (!opts.join) {
silentUnlink(outputPath(source, base));
silentUnlink(outputPath(source, base, '.map'));
return timeLog("removed " + source);
}
};
silentUnlink = function(path) {
var err, _ref1;
try {
fs.unlinkSync(jsPath);
return fs.unlinkSync(path);
} catch (_error) {
err = _error;
if (err.code !== 'ENOENT') {
if ((_ref1 = err.code) !== 'ENOENT' && _ref1 !== 'EPERM') {
throw err;
}
}
return timeLog("removed " + source);
}
};
outputPath = function(source, base, extension) {

View file

@ -152,6 +152,8 @@
return new Param($1, null, true);
}), o('ParamVar = Expression', function() {
return new Param($1, $3);
}), o('...', function() {
return new Expansion;
})
],
ParamVar: [o('Identifier'), o('ThisProperty'), o('Array'), o('Object')],
@ -331,7 +333,11 @@
return $1.concat($4);
})
],
Arg: [o('Expression'), o('Splat')],
Arg: [
o('Expression'), o('Splat'), o('...', function() {
return new Expansion;
})
],
SimpleArgs: [
o('Expression'), o('SimpleArgs , Expression', function() {
return [].concat($1, $3);
@ -540,14 +546,16 @@
Operation: [
o('UNARY Expression', function() {
return new Op($1, $2);
}), o('UNARY_MATH Expression', function() {
return new Op($1, $2);
}), o('- Expression', (function() {
return new Op('-', $2);
}), {
prec: 'UNARY'
prec: 'UNARY_MATH'
}), o('+ Expression', (function() {
return new Op('+', $2);
}), {
prec: 'UNARY'
prec: 'UNARY_MATH'
}), o('-- SimpleAssignable', function() {
return new Op('--', $2);
}), o('++ SimpleAssignable', function() {
@ -564,6 +572,8 @@
return new Op('-', $1, $3);
}), o('Expression MATH Expression', function() {
return new Op($2, $1, $3);
}), o('Expression ** Expression', function() {
return new Op($2, $1, $3);
}), o('Expression SHIFT Expression', function() {
return new Op($2, $1, $3);
}), o('Expression COMPARE Expression', function() {
@ -588,7 +598,7 @@
]
};
operators = [['left', '.', '?.', '::', '?::'], ['left', 'CALL_START', 'CALL_END'], ['nonassoc', '++', '--'], ['left', '?'], ['right', 'UNARY'], ['left', 'MATH'], ['left', '+', '-'], ['left', 'SHIFT'], ['left', 'RELATION'], ['left', 'COMPARE'], ['left', 'LOGIC'], ['nonassoc', 'INDENT', 'OUTDENT'], ['right', '=', ':', 'COMPOUND_ASSIGN', 'RETURN', 'THROW', 'EXTENDS'], ['right', 'FORIN', 'FOROF', 'BY', 'WHEN'], ['right', 'IF', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS'], ['right', 'POST_IF']];
operators = [['left', '.', '?.', '::', '?::'], ['left', 'CALL_START', 'CALL_END'], ['nonassoc', '++', '--'], ['left', '?'], ['right', 'UNARY'], ['right', '**'], ['right', 'UNARY_MATH'], ['left', 'MATH'], ['left', '+', '-'], ['left', 'SHIFT'], ['left', 'RELATION'], ['left', 'COMPARE'], ['left', 'LOGIC'], ['nonassoc', 'INDENT', 'OUTDENT'], ['right', '=', ':', 'COMPOUND_ASSIGN', 'RETURN', 'THROW', 'EXTENDS'], ['right', 'FORIN', 'FOROF', 'BY', 'WHEN'], ['right', 'IF', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS'], ['left', 'POST_IF']];
tokens = [];

View file

@ -234,4 +234,19 @@
return "" + filename + ":" + (first_line + 1) + ":" + (first_column + 1) + ": error: " + this.message + "\n" + codeLine + "\n" + marker;
};
exports.nameWhitespaceCharacter = function(string) {
switch (string) {
case ' ':
return 'space';
case '\n':
return 'newline';
case '\r':
return 'carriage return';
case '\t':
return 'tab';
default:
return string;
}
};
}).call(this);

View file

@ -1,6 +1,6 @@
// Generated by CoffeeScript 1.6.3
(function() {
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, 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, invertLiterate, key, last, locationDataToString, repeat, starts, throwSyntaxError, _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, INDENTABLE_CLOSERS, 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, UNARY_MATH, WHITESPACE, compact, count, invertLiterate, key, last, locationDataToString, repeat, starts, throwSyntaxError, _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; };
_ref = require('./rewriter'), Rewriter = _ref.Rewriter, INVERSES = _ref.INVERSES;
@ -247,8 +247,7 @@
if (this.chunk.charAt(0) !== '/') {
return 0;
}
if (match = HEREGEX.exec(this.chunk)) {
length = this.heregexToken(match);
if (length = this.heregexToken()) {
return length;
}
prev = last(this.tokens);
@ -259,18 +258,21 @@
return 0;
}
_ref3 = match, match = _ref3[0], regex = _ref3[1], flags = _ref3[2];
if (regex === '//') {
return 0;
}
if (regex.slice(0, 2) === '/*') {
this.error('regular expressions cannot begin with `*`');
}
if (regex === '//') {
regex = '/(?:)/';
}
this.token('REGEX', "" + regex + flags, 0, match.length);
return match.length;
};
Lexer.prototype.heregexToken = function(match) {
var body, flags, flagsOffset, heregex, plusToken, prev, re, tag, token, tokens, value, _i, _len, _ref2, _ref3, _ref4;
Lexer.prototype.heregexToken = function() {
var body, flags, flagsOffset, heregex, match, plusToken, prev, re, tag, token, tokens, value, _i, _len, _ref2, _ref3, _ref4;
if (!(match = HEREGEX.exec(this.chunk))) {
return 0;
}
heregex = match[0], body = match[1], flags = match[2];
if (0 > body.indexOf('#{')) {
re = this.escapeLines(body.replace(HEREGEX_OMIT, '$1$2').replace(/\//g, '\\/'), true);
@ -354,34 +356,39 @@
this.indents.push(diff);
this.ends.push('OUTDENT');
this.outdebt = this.indebt = 0;
this.indent = size;
} else if (size < this.baseIndent) {
this.error('missing indentation', indent.length);
} else {
this.indebt = 0;
this.outdentToken(this.indent - size, noNewlines, indent.length);
}
this.indent = size;
return indent.length;
};
Lexer.prototype.outdentToken = function(moveOut, noNewlines, outdentLength) {
var dent, len;
var decreasedIndent, dent, lastIndent, _ref2;
decreasedIndent = this.indent - moveOut;
while (moveOut > 0) {
len = this.indents.length - 1;
if (this.indents[len] === void 0) {
lastIndent = this.indents[this.indents.length - 1];
if (!lastIndent) {
moveOut = 0;
} else if (this.indents[len] === this.outdebt) {
} else if (lastIndent === this.outdebt) {
moveOut -= this.outdebt;
this.outdebt = 0;
} else if (this.indents[len] < this.outdebt) {
this.outdebt -= this.indents[len];
moveOut -= this.indents[len];
} else if (lastIndent < this.outdebt) {
this.outdebt -= lastIndent;
moveOut -= lastIndent;
} else {
dent = this.indents.pop() + this.outdebt;
moveOut -= dent;
if (outdentLength && (_ref2 = this.chunk[outdentLength], __indexOf.call(INDENTABLE_CLOSERS, _ref2) >= 0)) {
decreasedIndent -= dent - moveOut;
moveOut = dent;
}
this.outdebt = 0;
this.pair('OUTDENT');
this.token('OUTDENT', dent, 0, outdentLength);
this.token('OUTDENT', moveOut, 0, outdentLength);
moveOut -= dent;
}
}
if (dent) {
@ -393,6 +400,7 @@
if (!(this.tag() === 'TERMINATOR' || noNewlines)) {
this.token('TERMINATOR', '\n', outdentLength, 0);
}
this.indent = decreasedIndent;
return this;
};
@ -462,6 +470,8 @@
tag = 'COMPOUND_ASSIGN';
} else if (__indexOf.call(UNARY, value) >= 0) {
tag = 'UNARY';
} else if (__indexOf.call(UNARY_MATH, value) >= 0) {
tag = 'UNARY_MATH';
} else if (__indexOf.call(SHIFT, value) >= 0) {
tag = 'SHIFT';
} else if (__indexOf.call(LOGIC, value) >= 0 || value === '?' && (prev != null ? prev.spaced : void 0)) {
@ -591,14 +601,14 @@
};
Lexer.prototype.interpolateString = function(str, options) {
var column, expr, heredoc, i, inner, interpolated, len, letter, lexedLength, line, locationToken, nested, offsetInChunk, pi, plusToken, popped, regex, rparen, strOffset, tag, token, tokens, value, _i, _len, _ref2, _ref3, _ref4;
var column, errorToken, expr, heredoc, i, inner, interpolated, len, letter, lexedLength, line, locationToken, nested, offsetInChunk, pi, plusToken, popped, regex, rparen, strOffset, tag, token, tokens, value, _i, _len, _ref2, _ref3, _ref4;
if (options == null) {
options = {};
}
heredoc = options.heredoc, regex = options.regex, offsetInChunk = options.offsetInChunk, strOffset = options.strOffset, lexedLength = options.lexedLength;
offsetInChunk = offsetInChunk || 0;
strOffset = strOffset || 0;
lexedLength = lexedLength || str.length;
offsetInChunk || (offsetInChunk = 0);
strOffset || (strOffset = 0);
lexedLength || (lexedLength = str.length);
tokens = [];
pi = 0;
i = -1;
@ -613,6 +623,9 @@
if (pi < i) {
tokens.push(this.makeToken('NEOSTRING', str.slice(pi, i), strOffset + pi));
}
if (!errorToken) {
errorToken = this.makeToken('', 'string interpolation', offsetInChunk + i + 1, 2);
}
inner = expr.slice(1, -1);
if (inner.length) {
_ref2 = this.getLineAndColumnFromChunk(strOffset + i + 1), line = _ref2[0], column = _ref2[1];
@ -649,7 +662,7 @@
tokens.unshift(this.makeToken('NEOSTRING', '', offsetInChunk));
}
if (interpolated = tokens.length > 1) {
this.token('(', '(', offsetInChunk, 0);
this.token('(', '(', offsetInChunk, 0, errorToken);
}
for (i = _i = 0, _len = tokens.length; _i < _len; i = ++_i) {
token = tokens[i];
@ -685,13 +698,12 @@
};
Lexer.prototype.pair = function(tag) {
var size, wanted;
var wanted;
if (tag !== (wanted = last(this.ends))) {
if ('OUTDENT' !== wanted) {
this.error("unmatched " + tag);
}
this.indent -= size = last(this.indents);
this.outdentToken(size, true);
this.outdentToken(last(this.indents), true);
return this.pair(tag);
}
return this.ends.pop();
@ -734,9 +746,12 @@
return token;
};
Lexer.prototype.token = function(tag, value, offsetInChunk, length) {
Lexer.prototype.token = function(tag, value, offsetInChunk, length, origin) {
var token;
token = this.makeToken(tag, value, offsetInChunk, length);
if (origin) {
token.origin = origin;
}
this.tokens.push(token);
return token;
};
@ -753,7 +768,7 @@
Lexer.prototype.unfinished = function() {
var _ref2;
return LINE_CONTINUER.test(this.chunk) || ((_ref2 = this.tag()) === '\\' || _ref2 === '.' || _ref2 === '?.' || _ref2 === '?::' || _ref2 === 'UNARY' || _ref2 === 'MATH' || _ref2 === '+' || _ref2 === '-' || _ref2 === 'SHIFT' || _ref2 === 'RELATION' || _ref2 === 'COMPARE' || _ref2 === 'LOGIC' || _ref2 === 'THROW' || _ref2 === 'EXTENDS');
return LINE_CONTINUER.test(this.chunk) || ((_ref2 = this.tag()) === '\\' || _ref2 === '.' || _ref2 === '?.' || _ref2 === '?::' || _ref2 === 'UNARY' || _ref2 === 'MATH' || _ref2 === 'UNARY_MATH' || _ref2 === '+' || _ref2 === '-' || _ref2 === '**' || _ref2 === 'SHIFT' || _ref2 === 'RELATION' || _ref2 === 'COMPARE' || _ref2 === 'LOGIC' || _ref2 === 'THROW' || _ref2 === 'EXTENDS');
};
Lexer.prototype.removeNewlines = function(str) {
@ -851,7 +866,7 @@
HEREDOC = /^("""|''')((?:\\[\s\S]|[^\\])*?)(?:\n[^\n\S]*)?\1/;
OPERATOR = /^(?:[-=]>\*?|[-+*\/%<>&|^!?=]=|>>>=?|([-+:])\1|([&|<>])\2=?|\?(\.|::)|\.{2,3})/;
OPERATOR = /^(?:[-=]>\*?|[-+*\/%<>&|^!?=]=|>>>=?|([-+:])\1|([&|<>*\/%])\2=?|\?(\.|::)|\.{2,3})/;
WHITESPACE = /^[^\n\S]+/;
@ -881,9 +896,11 @@
TRAILING_SPACES = /\s+$/;
COMPOUND_ASSIGN = ['-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?=', '<<=', '>>=', '>>>=', '&=', '^=', '|='];
COMPOUND_ASSIGN = ['-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?=', '<<=', '>>=', '>>>=', '&=', '^=', '|=', '**=', '//=', '%%='];
UNARY = ['!', '~', 'NEW', 'TYPEOF', 'DELETE', 'DO', 'YIELD', 'YIELD*'];
UNARY = ['NEW', 'TYPEOF', 'DELETE', 'DO', 'YIELD', 'YIELD*'];
UNARY_MATH = ['!', '~'];
LOGIC = ['&&', '||', '&', '|', '^'];
@ -891,7 +908,7 @@
COMPARE = ['==', '!=', '<', '>', '<=', '>='];
MATH = ['*', '/', '%'];
MATH = ['*', '/', '%', '//', '%%'];
RELATION = ['IN', 'OF', 'INSTANCEOF'];
@ -907,4 +924,6 @@
LINE_BREAK = ['INDENT', 'OUTDENT', 'TERMINATOR'];
INDENTABLE_CLOSERS = [')', '}', ']'];
}).call(this);

View file

@ -1,6 +1,6 @@
// Generated by CoffeeScript 1.6.3
(function() {
var Access, Arr, Assign, Base, Block, Call, Class, Code, CodeFragment, Comment, Existence, Extends, For, HEXNUM, IDENTIFIER, IDENTIFIER_STR, IS_REGEX, IS_STRING, If, In, Index, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, METHOD_DEF, NEGATE, NO, NUMBER, 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, fragmentsToText, isLiteralArguments, isLiteralThis, last, locationDataToString, merge, multident, parseNum, some, starts, throwSyntaxError, unfoldSoak, utility, _ref, _ref1,
var Access, Arr, Assign, Base, Block, Call, Class, Code, CodeFragment, Comment, Existence, Expansion, Extends, For, HEXNUM, IDENTIFIER, IDENTIFIER_STR, IS_REGEX, IS_STRING, If, In, Index, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, METHOD_DEF, NEGATE, NO, NUMBER, 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, fragmentsToText, isLiteralArguments, isLiteralThis, last, locationDataToString, merge, multident, parseNum, some, starts, throwSyntaxError, unfoldSoak, utility, _ref, _ref1,
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
__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; },
@ -1591,7 +1591,7 @@
};
Assign.prototype.compileNode = function(o) {
var answer, compiledName, isValue, match, name, val, varBase, _ref2, _ref3, _ref4;
var answer, compiledName, isValue, match, name, val, varBase, _ref2, _ref3, _ref4, _ref5;
if (isValue = this.variable instanceof Value) {
if (this.variable.isArray() || this.variable.isObject()) {
return this.compilePatternMatch(o);
@ -1602,6 +1602,9 @@
if ((_ref2 = this.context) === '||=' || _ref2 === '&&=' || _ref2 === '?=') {
return this.compileConditional(o);
}
if ((_ref3 = this.context) === '**=' || _ref3 === '//=' || _ref3 === '%%=') {
return this.compileSpecialMath(o);
}
}
compiledName = this.variable.compileToFragments(o, LEVEL_LIST);
name = fragmentsToText(compiledName);
@ -1622,7 +1625,7 @@
if (match[2]) {
this.value.klass = match[1];
}
this.value.name = (_ref3 = (_ref4 = match[3]) != null ? _ref4 : match[4]) != null ? _ref3 : match[5];
this.value.name = (_ref4 = (_ref5 = match[3]) != null ? _ref5 : match[4]) != null ? _ref4 : match[5];
}
val = this.value.compileToFragments(o, LEVEL_LIST);
if (this.context === 'object') {
@ -1637,7 +1640,7 @@
};
Assign.prototype.compilePatternMatch = function(o) {
var acc, assigns, code, fragments, i, idx, isObject, ivar, name, obj, objects, olen, ref, rest, splat, top, val, value, vvar, vvarText, _i, _len, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7;
var acc, assigns, code, expandedIdx, fragments, i, idx, isObject, ivar, name, obj, objects, olen, ref, rest, top, val, value, vvar, vvarText, _i, _len, _ref2, _ref3, _ref4, _ref5, _ref6, _ref7;
top = o.level === LEVEL_TOP;
value = this.value;
objects = this.variable.base.objects;
@ -1669,7 +1672,7 @@
vvar = value.compileToFragments(o, LEVEL_LIST);
vvarText = fragmentsToText(vvar);
assigns = [];
splat = false;
expandedIdx = false;
if (!IDENTIFIER.test(vvarText) || this.variable.assigns(vvarText)) {
assigns.push([this.makeCode("" + (ref = o.scope.freeVariable('ref')) + " = ")].concat(__slice.call(vvar)));
vvar = [this.makeCode(ref)];
@ -1689,7 +1692,7 @@
}
}
}
if (!splat && obj instanceof Splat) {
if (!expandedIdx && obj instanceof Splat) {
name = obj.name.unwrap().value;
obj = obj.unwrap();
val = "" + olen + " <= " + vvarText + ".length ? " + (utility('slice')) + ".call(" + vvarText + ", " + i;
@ -1700,14 +1703,26 @@
val += ") : []";
}
val = new Literal(val);
splat = "" + ivar + "++";
expandedIdx = "" + ivar + "++";
} else if (!expandedIdx && obj instanceof Expansion) {
if (rest = olen - i - 1) {
if (rest === 1) {
expandedIdx = "" + vvarText + ".length - 1";
} else {
ivar = o.scope.freeVariable('i');
val = new Literal("" + ivar + " = " + vvarText + ".length - " + rest);
expandedIdx = "" + ivar + "++";
assigns.push(val.compileToFragments(o, LEVEL_LIST));
}
}
continue;
} else {
name = obj.unwrap().value;
if (obj instanceof Splat) {
obj.error("multiple splats are disallowed in an assignment");
if (obj instanceof Splat || obj instanceof Expansion) {
obj.error("multiple splats/expansions are disallowed in an assignment");
}
if (typeof idx === 'number') {
idx = new Literal(splat || idx);
idx = new Literal(expandedIdx || idx);
acc = false;
} else {
acc = isObject && IDENTIFIER.test(idx.unwrap().value || 0);
@ -1754,6 +1769,12 @@
}
};
Assign.prototype.compileSpecialMath = function(o) {
var left, right, _ref2;
_ref2 = this.variable.cacheReference(o), left = _ref2[0], right = _ref2[1];
return new Assign(left, new Op(this.context.slice(0, -1), right, this.value)).compileToFragments(o);
};
Assign.prototype.compileSplice = function(o) {
var answer, exclusive, from, fromDecl, fromRef, name, to, valDef, valRef, _ref2, _ref3, _ref4;
_ref2 = this.variable.properties.pop().range, from = _ref2.from, to = _ref2.to, exclusive = _ref2.exclusive;
@ -1835,17 +1856,22 @@
_ref3 = this.params;
for (_i = 0, _len = _ref3.length; _i < _len; _i++) {
param = _ref3[_i];
if (!(param instanceof Expansion)) {
o.scope.parameter(param.asReference(o));
}
}
_ref4 = this.params;
for (_j = 0, _len1 = _ref4.length; _j < _len1; _j++) {
param = _ref4[_j];
if (!param.splat) {
if (!(param.splat || param instanceof Expansion)) {
continue;
}
_ref5 = this.params;
for (_k = 0, _len2 = _ref5.length; _k < _len2; _k++) {
p = _ref5[_k].name;
if (!(!(param instanceof Expansion))) {
continue;
}
if (p["this"]) {
p = p.properties[0].name;
}
@ -2041,7 +2067,7 @@
} else {
iterator(obj.base.value, obj.base);
}
} else {
} else if (!(obj instanceof Expansion)) {
obj.error("illegal parameter " + (obj.compile()));
}
}
@ -2121,6 +2147,29 @@
})(Base);
exports.Expansion = Expansion = (function(_super) {
__extends(Expansion, _super);
function Expansion() {
return Expansion.__super__.constructor.apply(this, arguments);
}
Expansion.prototype.isComplex = NO;
Expansion.prototype.compileNode = function(o) {
return this.error('Expansion must be used inside a destructuring assignment or parameter list');
};
Expansion.prototype.asReference = function(o) {
return this;
};
Expansion.prototype.eachName = function(iterator) {};
return Expansion;
})(Base);
exports.While = While = (function(_super) {
__extends(While, _super);
@ -2315,7 +2364,7 @@
};
Op.prototype.compileNode = function(o) {
var answer, isChain, _ref2, _ref3;
var answer, isChain, lhs, rhs, _ref2, _ref3;
isChain = this.isChainable() && this.first.isChainable();
if (!isChain) {
this.first.front = this.front;
@ -2332,15 +2381,25 @@
if (isChain) {
return this.compileChain(o);
}
if (this.operator === '?') {
switch (this.operator) {
case '?':
return this.compileExistence(o);
}
answer = [].concat(this.first.compileToFragments(o, LEVEL_OP), this.makeCode(' ' + this.operator + ' '), this.second.compileToFragments(o, LEVEL_OP));
case '**':
return this.compilePower(o);
case '//':
return this.compileFloorDivision(o);
case '%%':
return this.compileModulo(o);
default:
lhs = this.first.compileToFragments(o, LEVEL_OP);
rhs = this.second.compileToFragments(o, LEVEL_OP);
answer = [].concat(lhs, this.makeCode(" " + this.operator + " "), rhs);
if (o.level <= LEVEL_OP) {
return answer;
} else {
return this.wrapInBraces(answer);
}
}
};
Op.prototype.compileChain = function(o) {
@ -2391,6 +2450,25 @@
return this.joinFragmentArrays(parts, '');
};
Op.prototype.compilePower = function(o) {
var pow;
pow = new Value(new Literal('Math'), [new Access(new Literal('pow'))]);
return new Call(pow, [this.first, this.second]).compileToFragments(o);
};
Op.prototype.compileFloorDivision = function(o) {
var div, floor;
floor = new Value(new Literal('Math'), [new Access(new Literal('floor'))]);
div = new Op('/', this.first, this.second);
return new Call(floor, [div]).compileToFragments(o);
};
Op.prototype.compileModulo = function(o) {
var mod;
mod = new Value(new Literal(utility('modulo')));
return new Call(mod, [this.first, this.second]).compileToFragments(o);
};
Op.prototype.toString = function(idt) {
return Op.__super__.toString.call(this, idt, this.constructor.name + ' ' + this.operator);
};
@ -2413,7 +2491,7 @@
In.prototype.compileNode = function(o) {
var hasSplat, obj, _i, _len, _ref2;
if (this.array instanceof Value && this.array.isArray()) {
if (this.array instanceof Value && this.array.isArray() && this.array.base.objects.length) {
_ref2 = this.array.base.objects;
for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
obj = _ref2[_i];
@ -2432,9 +2510,6 @@
In.prototype.compileOrTest = function(o) {
var cmp, cnj, i, item, ref, sub, tests, _i, _len, _ref2, _ref3, _ref4;
if (this.array.base.objects.length === 0) {
return [this.makeCode("" + (!!this.negated))];
}
_ref2 = this.object.cache(o, LEVEL_OP), sub = _ref2[0], ref = _ref2[1];
_ref3 = this.negated ? [' !== ', ' && '] : [' === ', ' || '], cmp = _ref3[0], cnj = _ref3[1];
tests = [];
@ -2646,7 +2721,9 @@
}
source = this.range ? this.source.base : this.source;
scope = o.scope;
if (!this.pattern) {
name = this.name && (this.name.compile(o, LEVEL_LIST));
}
index = this.index && (this.index.compile(o, LEVEL_LIST));
if (name && !this.pattern) {
scope.find(name);
@ -3001,6 +3078,9 @@
indexOf: function() {
return "[].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }";
},
modulo: function() {
return "function(a, b) { return (a % b + +b) % b; }";
},
hasProp: function() {
return '{}.hasOwnProperty';
},

File diff suppressed because one or more lines are too long

View file

@ -51,19 +51,14 @@
fork = child_process.fork;
binary = require.resolve('../../bin/coffee');
child_process.fork = function(path, args, options) {
var execPath;
if (args == null) {
args = [];
}
if (options == null) {
options = {};
}
execPath = helpers.isCoffee(path) ? binary : null;
if (helpers.isCoffee(path)) {
if (!Array.isArray(args)) {
args = [];
options = args || {};
args = [];
}
args = [path].concat(args);
path = binary;
}
options.execPath || (options.execPath = execPath);
return fork(path, args, options);
};
}

View file

@ -145,7 +145,7 @@
console.warn("Node 0.8.0+ required for CoffeeScript REPL");
process.exit(1);
}
require('./extensions');
CoffeeScript.register();
process.argv = ['coffee'].concat(process.argv.slice(2));
opts = merge(replDefaults, opts);
repl = nodeREPL.start(opts);

View file

@ -4,10 +4,13 @@
__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; },
__slice = [].slice;
generate = function(tag, value) {
generate = function(tag, value, origin) {
var tok;
tok = [tag, value];
tok.generated = true;
if (origin) {
tok.origin = origin;
}
return tok;
};
@ -195,12 +198,9 @@
return i += 1;
};
endAllImplicitCalls = function() {
var _results;
_results = [];
while (inImplicitCall()) {
_results.push(endImplicitCall());
endImplicitCall();
}
return _results;
};
startImplicitObject = function(j, startsLine) {
var idx;
@ -215,7 +215,7 @@
ours: true
}
]);
tokens.splice(idx, 0, generate('{', generate(new String('{'))));
tokens.splice(idx, 0, generate('{', generate(new String('{')), token));
if (j == null) {
return i += 1;
}
@ -223,7 +223,7 @@
endImplicitObject = function(j) {
j = j != null ? j : i;
stack.pop();
tokens.splice(j, 0, generate('}', '}'));
tokens.splice(j, 0, generate('}', '}', token));
return i += 1;
};
if (inImplicitCall() && (tag === 'IF' || tag === 'TRY' || tag === 'FINALLY' || tag === 'CATCH' || tag === 'CLASS' || tag === 'SWITCH')) {
@ -293,16 +293,10 @@
startImplicitObject(s, !!startsLine);
return forward(2);
}
if (inImplicitCall() && __indexOf.call(CALL_CLOSERS, tag) >= 0) {
if (prevTag === 'OUTDENT') {
endImplicitCall();
return forward(1);
}
if (prevToken.newLine) {
if (inImplicitCall() && __indexOf.call(CALL_CLOSERS, tag) >= 0 && (prevTag === 'OUTDENT' || prevToken.newLine)) {
endAllImplicitCalls();
return forward(1);
}
}
if (inImplicitObject() && __indexOf.call(LINEBREAKS, tag) >= 0) {
stackTop()[2].sameLine = false;
}
@ -311,7 +305,7 @@
_ref4 = stackTop(), stackTag = _ref4[0], stackIdx = _ref4[1], (_ref5 = _ref4[2], sameLine = _ref5.sameLine, startsLine = _ref5.startsLine);
if (inImplicitCall() && prevTag !== ',') {
endImplicitCall();
} else if (inImplicitObject() && sameLine && !startsLine) {
} else if (inImplicitObject() && sameLine && tag !== 'TERMINATOR' && prevTag !== ':') {
endImplicitObject();
} else if (inImplicitObject() && tag === 'TERMINATOR' && prevTag !== ',' && !(startsLine && this.looksObjectish(i + 1))) {
endImplicitObject();
@ -474,7 +468,7 @@
IMPLICIT_FUNC = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@', 'THIS'];
IMPLICIT_CALL = ['IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'CLASS', 'IF', 'TRY', 'SWITCH', 'THIS', 'BOOL', 'NULL', 'UNDEFINED', 'UNARY', 'SUPER', 'THROW', '@', '->', '=>', '->*', '=>*', '[', '(', '{', '--', '++'];
IMPLICIT_CALL = ['IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'CLASS', 'IF', 'TRY', 'SWITCH', 'THIS', 'BOOL', 'NULL', 'UNDEFINED', 'UNARY', 'UNARY_MATH', 'SUPER', 'THROW', '@', '->', '=>', '[', '(', '{', '--', '++'];
IMPLICIT_UNSPACED_CALL = ['+', '-'];

1
register.js Normal file
View file

@ -0,0 +1 @@
require('./lib/coffee-script/register');

View file

@ -156,6 +156,8 @@ exports.eval = (code, options = {}) ->
else
vm.runInContext js, sandbox
exports.register = -> require './register'
exports._compileFile = (filename, sourceMap = no) ->
raw = fs.readFileSync filename, 'utf8'
stripped = if raw.charCodeAt(0) is 0xFEFF then raw.substring 1 else raw
@ -181,6 +183,7 @@ parser.lexer =
token = @tokens[@pos++]
if token
[tag, @yytext, @yylloc] = token
@errorToken = token.origin or token
@yylineno = @yylloc.first_line
else
tag = ''
@ -196,12 +199,21 @@ parser.yy = require './nodes'
# Override Jison's default error handling function.
parser.yy.parseError = (message, {token}) ->
# Disregard Jison's message, it contains redundant line numer information.
message = "unexpected #{if token is 1 then 'end of input' else token}"
# Disregard the token, we take its value directly from the lexer in case
# the error is caused by a generated token which might refer to its origin.
{errorToken, tokens} = parser.lexer
[ignored, errorText, errorLoc] = errorToken
errorText = if errorToken is tokens[tokens.length - 1]
'end of input'
else
helpers.nameWhitespaceCharacter errorText
# The second argument has a `loc` property, which should have the location
# data for this token. Unfortunately, Jison seems to send an outdated `loc`
# (from the previous token), so we take the location information directly
# from the lexer.
helpers.throwSyntaxError message, parser.lexer.yylloc
helpers.throwSyntaxError "unexpected #{errorText}", errorLoc
# Based on http://v8.googlecode.com/svn/branches/bleeding_edge/src/messages.js
# Modified to handle sourceMap

View file

@ -104,6 +104,9 @@ compilePath = (source, topLevel, base) ->
if path.basename(source) is 'node_modules'
notSources[source] = yes
return
if opts.run
compilePath findDirectoryIndex(source), topLevel, base
return
watchDir source, base if opts.watch
try
files = fs.readdirSync source
@ -124,6 +127,16 @@ compilePath = (source, topLevel, base) ->
else
notSources[source] = yes
findDirectoryIndex = (source) ->
for ext in CoffeeScript.FILE_EXTENSIONS
index = path.join source, "index#{ext}"
try
return index if (fs.statSync index).isFile()
catch err
throw err unless err.code is 'ENOENT'
console.error "Missing index.coffee or index.litcoffee in #{source}"
process.exit 1
# Compile a single source script, containing the given code, according to the
# requested options. If evaluating the script directly sets `__filename`,
# `__dirname` and `module.filename` to be correct relative to the script's path.
@ -133,9 +146,13 @@ compileScript = (file, input, base = null) ->
try
t = task = {file, input, options}
CoffeeScript.emit 'compile', task
if o.tokens then printTokens CoffeeScript.tokens t.input, t.options
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
if o.tokens
printTokens CoffeeScript.tokens t.input, t.options
else if o.nodes
printLine CoffeeScript.nodes(t.input, t.options).toString().trim()
else if o.run
CoffeeScript.register()
CoffeeScript.run t.input, t.options
else if o.join and t.file isnt o.join
t.input = helpers.invertLiterate t.input if helpers.isLiterate file
sourceCode[sources.indexOf(t.file)] = t.input
@ -186,27 +203,27 @@ compileJoin = ->
# time the file is updated. May be used in combination with other options,
# such as `--print`.
watch = (source, base) ->
watcher = null
prevStats = null
compileTimeout = null
watchErr = (e) ->
if e.code is 'ENOENT'
return if sources.indexOf(source) is -1
watchErr = (err) ->
throw err unless err.code is 'ENOENT'
return unless source in sources
try
rewatch()
compile()
catch e
removeSource source, base, yes
catch
removeSource source, base
compileJoin()
else throw e
compile = ->
clearTimeout compileTimeout
compileTimeout = wait 25, ->
fs.stat source, (err, stats) ->
return watchErr err if err
return rewatch() if prevStats and stats.size is prevStats.size and
return rewatch() if prevStats and
stats.size is prevStats.size and
stats.mtime.getTime() is prevStats.mtime.getTime()
prevStats = stats
fs.readFile source, (err, code) ->
@ -214,57 +231,78 @@ watch = (source, base) ->
compileScript(source, code.toString(), base)
rewatch()
try
watcher = fs.watch source, compile
catch e
watchErr e
startWatcher = ->
watcher = fs.watch source
.on 'change', compile
.on 'error', (err) ->
throw err unless err.code is 'EPERM'
removeSource source, base
rewatch = ->
watcher?.close()
watcher = fs.watch source, compile
startWatcher()
try
startWatcher()
catch err
watchErr err
# Watch a directory of files for new additions.
watchDir = (source, base) ->
watcher = null
readdirTimeout = null
try
watchedDirs[source] = yes
watcher = fs.watch source, ->
startWatcher = ->
watcher = fs.watch source
.on 'error', (err) ->
throw err unless err.code is 'EPERM'
stopWatcher()
.on 'change', ->
clearTimeout readdirTimeout
readdirTimeout = wait 25, ->
try
files = fs.readdirSync source
catch err
throw err unless err.code is 'ENOENT'
watcher.close()
return removeSourceDir source, base
return stopWatcher()
for file in files
compilePath (path.join source, file), no, base
catch e
throw e unless e.code is 'ENOENT'
stopWatcher = ->
watcher.close()
removeSourceDir source, base
watchedDirs[source] = yes
try
startWatcher()
catch err
throw err unless err.code is 'ENOENT'
removeSourceDir = (source, base) ->
delete watchedDirs[source]
sourcesChanged = no
for file in sources when source is path.dirname file
removeSource file, base, yes
removeSource file, base
sourcesChanged = yes
compileJoin() if sourcesChanged
# Remove a file from our source list, and source code cache. Optionally remove
# the compiled JS version as well.
removeSource = (source, base, removeJs) ->
removeSource = (source, base) ->
index = sources.indexOf source
sources.splice index, 1
sourceCode.splice index, 1
if removeJs and not opts.join
jsPath = outputPath source, base
try
fs.unlinkSync jsPath
catch err
throw err unless err.code is 'ENOENT'
unless opts.join
silentUnlink outputPath source, base
silentUnlink outputPath source, base, '.map'
timeLog "removed #{source}"
silentUnlink = (path) ->
try
fs.unlinkSync path
catch err
throw err unless err.code in ['ENOENT', 'EPERM']
# Get the corresponding output JavaScript path for a source file.
outputPath = (source, base, extension=".js") ->
basename = helpers.baseFileName source, yes, useWinPathSep

View file

@ -221,6 +221,7 @@ grammar =
o 'ParamVar', -> new Param $1
o 'ParamVar ...', -> new Param $1, null, on
o 'ParamVar = Expression', -> new Param $1, $3
o '...', -> new Expansion
]
# Function Parameters
@ -382,6 +383,7 @@ grammar =
Arg: [
o 'Expression'
o 'Splat'
o '...', -> new Expansion
]
# Just simple, comma-separated, required arguments (no fancy syntax). We need
@ -535,8 +537,9 @@ grammar =
# rules are necessary.
Operation: [
o 'UNARY Expression', -> new Op $1 , $2
o '- Expression', (-> new Op '-', $2), prec: 'UNARY'
o '+ Expression', (-> new Op '+', $2), prec: 'UNARY'
o 'UNARY_MATH Expression', -> new Op $1 , $2
o '- Expression', (-> new Op '-', $2), prec: 'UNARY_MATH'
o '+ Expression', (-> new Op '+', $2), prec: 'UNARY_MATH'
o '-- SimpleAssignable', -> new Op '--', $2
o '++ SimpleAssignable', -> new Op '++', $2
@ -550,6 +553,7 @@ grammar =
o 'Expression - Expression', -> new Op '-' , $1, $3
o 'Expression MATH Expression', -> new Op $2, $1, $3
o 'Expression ** Expression', -> new Op $2, $1, $3
o 'Expression SHIFT Expression', -> new Op $2, $1, $3
o 'Expression COMPARE Expression', -> new Op $2, $1, $3
o 'Expression LOGIC Expression', -> new Op $2, $1, $3
@ -586,6 +590,8 @@ operators = [
['nonassoc', '++', '--']
['left', '?']
['right', 'UNARY']
['right', '**']
['right', 'UNARY_MATH']
['left', 'MATH']
['left', '+', '-']
['left', 'SHIFT']
@ -596,7 +602,7 @@ operators = [
['right', '=', ':', 'COMPOUND_ASSIGN', 'RETURN', 'THROW', 'EXTENDS']
['right', 'FORIN', 'FOROF', 'BY', 'WHEN']
['right', 'IF', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS']
['right', 'POST_IF']
['left', 'POST_IF']
]
# Wrapping Up

View file

@ -188,3 +188,11 @@ syntaxErrorToString = ->
#{codeLine}
#{marker}
"""
exports.nameWhitespaceCharacter = (string) ->
switch string
when ' ' then 'space'
when '\n' then 'newline'
when '\r' then 'carriage return'
when '\t' then 'tab'
else string

View file

@ -235,21 +235,21 @@ exports.Lexer = class Lexer
# JavaScript and Ruby.
regexToken: ->
return 0 if @chunk.charAt(0) isnt '/'
if match = HEREGEX.exec @chunk
length = @heregexToken match
return length
return length if length = @heregexToken()
prev = last @tokens
return 0 if prev and (prev[0] in (if prev.spaced then NOT_REGEX else NOT_SPACED_REGEX))
return 0 unless match = REGEX.exec @chunk
[match, regex, flags] = match
# Avoid conflicts with floor division operator.
return 0 if regex is '//'
if regex[..1] is '/*' then @error 'regular expressions cannot begin with `*`'
if regex is '//' then regex = '/(?:)/'
@token 'REGEX', "#{regex}#{flags}", 0, match.length
match.length
# Matches multiline extended regular expressions.
heregexToken: (match) ->
heregexToken: ->
return 0 unless match = HEREGEX.exec @chunk
[heregex, body, flags] = match
if 0 > body.indexOf '#{'
re = @escapeLines body.replace(HEREGEX_OMIT, '$1$2').replace(/\//g, '\\/'), yes
@ -328,37 +328,43 @@ exports.Lexer = class Lexer
@indents.push diff
@ends.push 'OUTDENT'
@outdebt = @indebt = 0
@indent = size
else if size < @baseIndent
@error 'missing indentation', indent.length
else
@indebt = 0
@outdentToken @indent - size, noNewlines, indent.length
@indent = size
indent.length
# Record an outdent token or multiple tokens, if we happen to be moving back
# inwards past several recorded indents.
# inwards past several recorded indents. Sets new @indent value.
outdentToken: (moveOut, noNewlines, outdentLength) ->
decreasedIndent = @indent - moveOut
while moveOut > 0
len = @indents.length - 1
if @indents[len] is undefined
lastIndent = @indents[@indents.length - 1]
if not lastIndent
moveOut = 0
else if @indents[len] is @outdebt
else if lastIndent is @outdebt
moveOut -= @outdebt
@outdebt = 0
else if @indents[len] < @outdebt
@outdebt -= @indents[len]
moveOut -= @indents[len]
else if lastIndent < @outdebt
@outdebt -= lastIndent
moveOut -= lastIndent
else
dent = @indents.pop() + @outdebt
moveOut -= dent
if outdentLength and @chunk[outdentLength] in INDENTABLE_CLOSERS
decreasedIndent -= dent - moveOut
moveOut = dent
@outdebt = 0
# pair might call outdentToken, so preserve decreasedIndent
@pair 'OUTDENT'
@token 'OUTDENT', dent, 0, outdentLength
@token 'OUTDENT', moveOut, 0, outdentLength
moveOut -= dent
@outdebt -= moveOut if dent
@tokens.pop() while @value() is ';'
@token 'TERMINATOR', '\n', outdentLength, 0 unless @tag() is 'TERMINATOR' or noNewlines
@indent = decreasedIndent
this
# Matches and consumes non-meaningful whitespace. Tag the previous token
@ -409,6 +415,7 @@ exports.Lexer = class Lexer
else if value in COMPARE then tag = 'COMPARE'
else if value in COMPOUND_ASSIGN then tag = 'COMPOUND_ASSIGN'
else if value in UNARY then tag = 'UNARY'
else if value in UNARY_MATH then tag = 'UNARY_MATH'
else if value in SHIFT then tag = 'SHIFT'
else if value in LOGIC or value is '?' and prev?.spaced then tag = 'LOGIC'
else if prev and not prev.spaced
@ -521,9 +528,9 @@ exports.Lexer = class Lexer
# current chunk.
interpolateString: (str, options = {}) ->
{heredoc, regex, offsetInChunk, strOffset, lexedLength} = options
offsetInChunk = offsetInChunk || 0
strOffset = strOffset || 0
lexedLength = lexedLength || str.length
offsetInChunk ||= 0
strOffset ||= 0
lexedLength ||= str.length
# Parse the string.
tokens = []
@ -538,6 +545,8 @@ exports.Lexer = class Lexer
continue
# NEOSTRING is a fake token. This will be converted to a string below.
tokens.push @makeToken('NEOSTRING', str[pi...i], strOffset + pi) if pi < i
unless errorToken
errorToken = @makeToken '', 'string interpolation', offsetInChunk + i + 1, 2
inner = expr[1...-1]
if inner.length
[line, column] = @getLineAndColumnFromChunk(strOffset + i + 1)
@ -563,7 +572,9 @@ exports.Lexer = class Lexer
# If the first token is not a string, add a fake empty string to the beginning.
tokens.unshift @makeToken('NEOSTRING', '', offsetInChunk) unless tokens[0][0] is 'NEOSTRING'
@token '(', '(', offsetInChunk, 0 if interpolated = tokens.length > 1
if interpolated = tokens.length > 1
@token '(', '(', offsetInChunk, 0, errorToken
# Push all the tokens
for token, i in tokens
[tag, value] = token
@ -603,8 +614,7 @@ exports.Lexer = class Lexer
# el.click((event) ->
# el.hide())
#
@indent -= size = last @indents
@outdentToken size, true
@outdentToken last(@indents), true
return @pair tag
@ends.pop()
@ -657,8 +667,9 @@ exports.Lexer = class Lexer
# not specified, the length of `value` will be used.
#
# Returns the new token.
token: (tag, value, offsetInChunk, length) ->
token: (tag, value, offsetInChunk, length, origin) ->
token = @makeToken tag, value, offsetInChunk, length
token.origin = origin if origin
@tokens.push token
token
@ -673,8 +684,8 @@ exports.Lexer = class Lexer
# Are we in the midst of an unfinished expression?
unfinished: ->
LINE_CONTINUER.test(@chunk) or
@tag() in ['\\', '.', '?.', '?::', 'UNARY', 'MATH', '+', '-', 'SHIFT', 'RELATION'
'COMPARE', 'LOGIC', 'THROW', 'EXTENDS']
@tag() in ['\\', '.', '?.', '?::', 'UNARY', 'MATH', 'UNARY_MATH', '+', '-',
'**', 'SHIFT', 'RELATION', 'COMPARE', 'LOGIC', 'THROW', 'EXTENDS']
# Remove newlines from beginning and (non escaped) from end of string literals.
removeNewlines: (str) ->
@ -778,7 +789,7 @@ OPERATOR = /// ^ (
| [-+*/%<>&|^!?=]= # compound assign / compare
| >>>=? # zero-fill right shift
| ([-+:])\1 # doubles
| ([&|<>])\2=? # logic / shift
| ([&|<>*/%])\2=? # logic / shift / power / floor division / modulo
| \?(\.|::) # soak access
| \.{2,3} # range or splat
) ///
@ -831,11 +842,14 @@ TRAILING_SPACES = /\s+$/
# Compound assignment tokens.
COMPOUND_ASSIGN = [
'-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?=', '<<=', '>>=', '>>>=', '&=', '^=', '|='
'-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?=', '<<=', '>>=', '>>>='
'&=', '^=', '|=', '**=', '//=', '%%='
]
# Unary tokens.
UNARY = ['!', '~', 'NEW', 'TYPEOF', 'DELETE', 'DO', 'YIELD', 'YIELD*']
UNARY = ['NEW', 'TYPEOF', 'DELETE', 'DO', 'YIELD', 'YIELD*']
UNARY_MATH = ['!', '~']
# Logical tokens.
LOGIC = ['&&', '||', '&', '|', '^']
@ -847,7 +861,7 @@ SHIFT = ['<<', '>>', '>>>']
COMPARE = ['==', '!=', '<', '>', '<=', '>=']
# Mathematical tokens.
MATH = ['*', '/', '%']
MATH = ['*', '/', '%', '//', '%%']
# Relational tokens that are negatable with `not` prefix.
RELATION = ['IN', 'OF', 'INSTANCEOF']
@ -877,3 +891,6 @@ INDEXABLE = CALLABLE.concat 'NUMBER', 'BOOL', 'NULL', 'UNDEFINED'
# occurs at the start of a line. We disambiguate these from trailing whens to
# avoid an ambiguity in the grammar.
LINE_BREAK = ['INDENT', 'OUTDENT', 'TERMINATOR']
# Additional indent in front of these is ignored.
INDENTABLE_CLOSERS = [')', '}', ']']

View file

@ -1156,6 +1156,7 @@ exports.Assign = class Assign extends Base
return @compilePatternMatch o if @variable.isArray() or @variable.isObject()
return @compileSplice o if @variable.isSplice()
return @compileConditional o if @context in ['||=', '&&=', '?=']
return @compileSpecialMath o if @context in ['**=', '//=', '%%=']
compiledName = @variable.compileToFragments o, LEVEL_LIST
name = fragmentsToText compiledName
unless @context
@ -1205,7 +1206,7 @@ exports.Assign = class Assign extends Base
vvar = value.compileToFragments o, LEVEL_LIST
vvarText = fragmentsToText vvar
assigns = []
splat = false
expandedIdx = false
# Make vvar into a simple variable if it isn't already.
if not IDENTIFIER.test(vvarText) or @variable.assigns(vvarText)
assigns.push [@makeCode("#{ ref = o.scope.freeVariable 'ref' } = "), vvar...]
@ -1224,7 +1225,7 @@ exports.Assign = class Assign extends Base
[obj, idx] = new Value(obj.unwrapAll()).cacheReference o
else
idx = if obj.this then obj.properties[0].name else obj
if not splat and obj instanceof Splat
if not expandedIdx and obj instanceof Splat
name = obj.name.unwrap().value
obj = obj.unwrap()
val = "#{olen} <= #{vvarText}.length ? #{ utility 'slice' }.call(#{vvarText}, #{i}"
@ -1234,13 +1235,23 @@ exports.Assign = class Assign extends Base
else
val += ") : []"
val = new Literal val
splat = "#{ivar}++"
expandedIdx = "#{ivar}++"
else if not expandedIdx and obj instanceof Expansion
if rest = olen - i - 1
if rest is 1
expandedIdx = "#{vvarText}.length - 1"
else
ivar = o.scope.freeVariable 'i'
val = new Literal "#{ivar} = #{vvarText}.length - #{rest}"
expandedIdx = "#{ivar}++"
assigns.push val.compileToFragments o, LEVEL_LIST
continue
else
name = obj.unwrap().value
if obj instanceof Splat
obj.error "multiple splats are disallowed in an assignment"
if obj instanceof Splat or obj instanceof Expansion
obj.error "multiple splats/expansions are disallowed in an assignment"
if typeof idx is 'number'
idx = new Literal splat or idx
idx = new Literal expandedIdx or idx
acc = no
else
acc = isObject and IDENTIFIER.test idx.unwrap().value or 0
@ -1268,6 +1279,12 @@ exports.Assign = class Assign extends Base
fragments = new Op(@context[...-1], left, new Assign(right, @value, '=')).compileToFragments o
if o.level <= LEVEL_LIST then fragments else @wrapInBraces fragments
# Convert special math assignment operators like `a **= b` to the equivalent
# extended form `a = a ** b` and then compiles that.
compileSpecialMath: (o) ->
[left, right] = @variable.cacheReference o
new Assign(left, new Op(@context[...-1], right, @value)).compileToFragments o
# Compile the assignment from an array splice literal, using JavaScript's
# `Array#splice` method.
compileSplice: (o) ->
@ -1336,10 +1353,10 @@ exports.Code = class Code extends Base
delete o.isExistentialEquals
params = []
exprs = []
for param in @params
for param in @params when param not instanceof Expansion
o.scope.parameter param.asReference o
for param in @params when param.splat
for {name: p} in @params
for param in @params when param.splat or param instanceof Expansion
for {name: p} in @params when param not instanceof Expansion
if p.this then p = p.properties[0].name
if p.value then o.scope.add p.value, 'var', yes
splats = new Assign new Value(new Arr(p.asReference o for p in @params)),
@ -1454,7 +1471,7 @@ exports.Param = class Param extends Base
atParam obj
# * simple destructured parameters {foo}
else iterator obj.base.value, obj.base
else
else if obj not instanceof Expansion
obj.error "illegal parameter #{obj.compile()}"
return
@ -1505,6 +1522,22 @@ exports.Splat = class Splat extends Base
concatPart = list[index].joinFragmentArrays args, ', '
[].concat list[0].makeCode("["), base, list[index].makeCode("].concat("), concatPart, (last list).makeCode(")")
#### Expansion
# Used to skip values inside an array destructuring (pattern matching) or
# parameter list.
exports.Expansion = class Expansion extends Base
isComplex: NO
compileNode: (o) ->
@error 'Expansion must be used inside a destructuring assignment or parameter list'
asReference: (o) ->
this
eachName: (iterator) ->
#### While
# A while loop, the only sort of low-level loop exposed by CoffeeScript. From
@ -1662,9 +1695,15 @@ exports.Op = class Op extends Base
@error "cannot increment/decrement \"#{@first.unwrapAll().value}\""
return @compileUnary o if @isUnary()
return @compileChain o if isChain
return @compileExistence o if @operator is '?'
answer = [].concat @first.compileToFragments(o, LEVEL_OP), @makeCode(' ' + @operator + ' '),
@second.compileToFragments(o, LEVEL_OP)
switch @operator
when '?' then @compileExistence o
when '**' then @compilePower o
when '//' then @compileFloorDivision o
when '%%' then @compileModulo o
else
lhs = @first.compileToFragments o, LEVEL_OP
rhs = @second.compileToFragments o, LEVEL_OP
answer = [].concat lhs, @makeCode(" #{@operator} "), rhs
if o.level <= LEVEL_OP then answer else @wrapInBraces answer
# Mimic Python's chained comparisons when multiple comparison operators are
@ -1708,6 +1747,20 @@ exports.Op = class Op extends Base
parts.reverse() if @flip
@joinFragmentArrays parts, ''
compilePower: (o) ->
# Make a Math.pow call
pow = new Value new Literal('Math'), [new Access new Literal 'pow']
new Call(pow, [@first, @second]).compileToFragments o
compileFloorDivision: (o) ->
floor = new Value new Literal('Math'), [new Access new Literal 'floor']
div = new Op '/', @first, @second
new Call(floor, [div]).compileToFragments o
compileModulo: (o) ->
mod = new Value new Literal utility 'modulo'
new Call(mod, [@first, @second]).compileToFragments o
toString: (idt) ->
super idt, @constructor.name + ' ' + @operator
@ -1720,7 +1773,7 @@ exports.In = class In extends Base
invert: NEGATE
compileNode: (o) ->
if @array instanceof Value and @array.isArray()
if @array instanceof Value and @array.isArray() and @array.base.objects.length
for obj in @array.base.objects when obj instanceof Splat
hasSplat = yes
break
@ -1729,7 +1782,6 @@ exports.In = class In extends Base
@compileLoopTest o
compileOrTest: (o) ->
return [@makeCode("#{!!@negated}")] if @array.base.objects.length is 0
[sub, ref] = @object.cache o, LEVEL_OP
[cmp, cnj] = if @negated then [' !== ', ' && '] else [' === ', ' || ']
tests = []
@ -1890,7 +1942,7 @@ exports.For = class For extends While
@returns = no if lastJumps and lastJumps instanceof Return
source = if @range then @source.base else @source
scope = o.scope
name = @name and (@name.compile o, LEVEL_LIST)
name = @name and (@name.compile o, LEVEL_LIST) if not @pattern
index = @index and (@index.compile o, LEVEL_LIST)
scope.find(name) if name and not @pattern
scope.find(index) if index
@ -2108,18 +2160,42 @@ UTILITIES =
# Correctly set up a prototype chain for inheritance, including a reference
# to the superclass for `super()` calls, and copies of any static properties.
extends: -> """
function(child, parent) { for (var key in parent) { if (#{utility 'hasProp'}.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }
"""
extends: -> "
function(child, parent) {
for (var key in parent) {
if (#{utility 'hasProp'}.call(parent, key)) child[key] = parent[key];
}
function ctor() {
this.constructor = child;
}
ctor.prototype = parent.prototype;
child.prototype = new ctor();
child.__super__ = parent.prototype;
return child;
}
"
# Create a function bound to the current value of "this".
bind: -> '''
function(fn, me){ return function(){ return fn.apply(me, arguments); }; }
'''
bind: -> '
function(fn, me){
return function(){
return fn.apply(me, arguments);
};
}
'
# Discover if an item is in an array.
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;
}
"
modulo: -> """
function(a, b) { return (a % b + +b) % b; }
"""
# Shortcuts to speed up the lookup time for native functions.

View file

@ -42,10 +42,11 @@ if require.extensions
if child_process
{fork} = child_process
binary = require.resolve '../../bin/coffee'
child_process.fork = (path, args = [], options = {}) ->
execPath = if helpers.isCoffee(path) then binary else null
if not Array.isArray args
args = []
child_process.fork = (path, args, options) ->
if helpers.isCoffee path
unless Array.isArray args
options = args or {}
options.execPath or= execPath
args = []
args = [path].concat args
path = binary
fork path, args, options

View file

@ -131,7 +131,7 @@ module.exports =
console.warn "Node 0.8.0+ required for CoffeeScript REPL"
process.exit 1
require './extensions'
CoffeeScript.register()
process.argv = ['coffee'].concat process.argv[2..]
opts = merge replDefaults, opts
repl = nodeREPL.start opts

View file

@ -6,9 +6,10 @@
# parentheses, and generally clean things up.
# Create a generated token: one that exists due to a use of implicit syntax.
generate = (tag, value) ->
generate = (tag, value, origin) ->
tok = [tag, value]
tok.generated = yes
tok.origin = origin if origin
tok
# The **Rewriter** class is used by the [Lexer](lexer.html), directly against
@ -162,17 +163,18 @@ class exports.Rewriter
endAllImplicitCalls = ->
while inImplicitCall()
endImplicitCall()
return
startImplicitObject = (j, startsLine = yes) ->
idx = j ? i
stack.push ['{', idx, sameLine: yes, startsLine: startsLine, ours: yes]
tokens.splice idx, 0, generate '{', generate(new String('{'))
tokens.splice idx, 0, generate '{', generate(new String('{')), token
i += 1 if not j?
endImplicitObject = (j) ->
j = j ? i
stack.pop()
tokens.splice j, 0, generate '}', '}'
tokens.splice j, 0, generate '}', '}', token
i += 1
# Don't end an implicit call on next indent if any of these are in an argument
@ -286,11 +288,8 @@ class exports.Rewriter
# .g b
# .h a
#
if inImplicitCall() and tag in CALL_CLOSERS
if prevTag is 'OUTDENT'
endImplicitCall()
return forward(1)
if prevToken.newLine
if inImplicitCall() and tag in CALL_CLOSERS and
(prevTag is 'OUTDENT' or prevToken.newLine)
endAllImplicitCalls()
return forward(1)
@ -304,7 +303,8 @@ class exports.Rewriter
endImplicitCall()
# Close implicit objects such as:
# return a: 1, b: 2 unless true
else if inImplicitObject() and sameLine and not startsLine
else if inImplicitObject() and sameLine and
tag isnt 'TERMINATOR' and prevTag isnt ':'
endImplicitObject()
# Close implicit objects when at end of line, line didn't end with a comma
# and the implicit object didn't start the line or the next line doesn't look like
@ -470,8 +470,8 @@ IMPLICIT_FUNC = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@
# If preceded by an `IMPLICIT_FUNC`, indicates a function invocation.
IMPLICIT_CALL = [
'IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'CLASS'
'IF', 'TRY', 'SWITCH', 'THIS', 'BOOL', 'NULL', 'UNDEFINED', 'UNARY', 'SUPER'
'THROW', '@', '->', '=>', '->*', '=>*', '[', '(', '{', '--', '++'
'IF', 'TRY', 'SWITCH', 'THIS', 'BOOL', 'NULL', 'UNDEFINED', 'UNARY',
'UNARY_MATH', 'SUPER', 'THROW', '@', '->', '=>', '[', '(', '{', '--', '++'
]
IMPLICIT_UNSPACED_CALL = ['+', '-']

View file

@ -268,6 +268,22 @@ test "#2055: destructuring assignment with `new`", ->
{length} = new Array
eq 0, length
test "#156: destructuring with expansion", ->
array = [1..5]
[first, ..., last] = array
eq 1, first
eq 5, last
[..., lastButOne, last] = array
eq 4, lastButOne
eq 5, last
[first, second, ..., last] = array
eq 2, second
[..., last] = 'strings as well -> x'
eq 'x', last
throws (-> CoffeeScript.compile "[1, ..., 3]"), null, "prohibit expansion outside of assignment"
throws (-> CoffeeScript.compile "[..., a, b...] = c"), null, "prohibit expansion and a splat"
throws (-> CoffeeScript.compile "[...] = c"), null, "prohibit lone expansion"
# Existential Assignment

View file

@ -537,7 +537,10 @@ test "#2525, #1187, #1208, #1758, looping over an array backwards", ->
arrayEq (index for i, index in list by ident(-1) * 2), [4, 2, 0]
test "splats in destructuring in comprehensions", ->
list = [[0, 1, 2], [2, 3, 4], [4, 5, 6]]
arrayEq (seq for [rep, seq...] in list), [[1, 2], [3, 4], [5, 6]]
test "#156: expansion in destructuring in comprehensions", ->
list = [[0, 1, 2], [2, 3, 4], [4, 5, 6]]
arrayEq (last for [..., last] in list), [2, 4, 6]

View file

@ -198,6 +198,14 @@ test "#748: trailing reserved identifiers", ->
nonce
eq nonce, result
# Postfix
test "#3056: multiple postfix conditionals", ->
temp = 'initial'
temp = 'ignored' unless true if false
eq temp, 'initial'
# Loops
test "basic `while` loops", ->
@ -296,6 +304,7 @@ test "break *not* at the top level", ->
result
eq 2, someFunc()
# Switch
test "basic `switch`", ->
@ -420,6 +429,7 @@ test "Issue #997. Switch doesn't fallthrough.", ->
eq val, 1
# Throw
test "Throw should be usable as an expression.", ->
try

View file

@ -26,7 +26,7 @@ test "parser error formating", ->
foo in bar or in baz
''',
'''
[stdin]:1:15: error: unexpected RELATION
[stdin]:1:15: error: unexpected in
foo in bar or in baz
^^
'''
@ -58,9 +58,44 @@ test "#2849: compilation error in a require()d file", ->
require './test/syntax-error'
''',
"""
#{path.join __dirname, 'syntax-error.coffee'}:1:15: error: unexpected RELATION
#{path.join __dirname, 'syntax-error.coffee'}:1:15: error: unexpected in
foo in bar or in baz
^^
"""
finally
fs.unlink 'test/syntax-error.coffee'
test "#1096: unexpected generated tokens", ->
# Unexpected interpolation
assertErrorFormat '{"#{key}": val}', '''
[stdin]:1:3: error: unexpected string interpolation
{"#{key}": val}
^^
'''
# Implicit ends
assertErrorFormat 'a:, b', '''
[stdin]:1:3: error: unexpected ,
a:, b
^
'''
# Explicit ends
assertErrorFormat '(a:)', '''
[stdin]:1:4: error: unexpected )
(a:)
^
'''
# Unexpected end of file
assertErrorFormat 'a:', '''
[stdin]:1:3: error: unexpected end of input
a:
^
'''
# Unexpected implicit object
assertErrorFormat '''
for i in [1]:
1
''', '''
[stdin]:1:13: error: unexpected :
for i in [1]:
^
'''

View file

@ -98,6 +98,13 @@ test "#1495, method call chaining", ->
).join ', '
eq 'a, b, c', result
test "chaining after outdent", ->
str = 'abc'
zero = parseInt str.replace /\w/, (letter) ->
0
.toString()
eq '0', zero
# Operators
test "newline suppression for operators", ->
@ -192,7 +199,7 @@ test "#1299: Disallow token misnesting", ->
test "#2981: Enforce initial indentation", ->
try
CoffeeScript.compile ' a\nb'
CoffeeScript.compile ' a\nb-'
ok no
catch e
eq 'missing indentation', e.message
@ -205,3 +212,16 @@ test "'single-line' expression containing multiple lines", ->
then -b
else null
"""
test "#1275: allow indentation before closing brackets", ->
array = [
1
2
3
]
eq array, array
do ->
(
a = 1
)
eq 1, a

View file

@ -178,6 +178,21 @@ test "default values with splatted arguments", ->
eq 1, withSplats(1,1,1)
eq 2, withSplats(1,1,1,1)
test "#156: parameter lists with expansion", ->
expandArguments = (first, ..., lastButOne, last) ->
eq 1, first
eq 4, lastButOne
last
eq 5, expandArguments 1, 2, 3, 4, 5
throws (-> CoffeeScript.compile "(..., a, b...) ->"), null, "prohibit expansion and a splat"
throws (-> CoffeeScript.compile "(...) ->"), null, "prohibit lone expansion"
test "#156: parameter lists with expansion in array destructuring", ->
expandArray = (..., [..., last]) ->
last
eq 3, expandArray 1, 2, 3, [1, 2, 3]
test "default values with function calls", ->
doesNotThrow -> CoffeeScript.compile "(x = f()) ->"

View file

@ -387,6 +387,23 @@ test "#1871: Special case for IMPLICIT_END in the middle of an implicit object",
eq result.two.join(' '), '2 2 2'
test "#1871: implicit object closed by IMPLICIT_END in implicit returns", ->
ob = do ->
a: 1 if no
eq ob, undefined
# instead these return an object
func = ->
key:
i for i in [1, 2, 3]
eq func().key.join(' '), '1 2 3'
func = ->
key: (i for i in [1, 2, 3])
eq func().key.join(' '), '1 2 3'
test "#1961, #1974, regression with compound assigning to an implicit object", ->
obj = null
@ -410,3 +427,10 @@ test "#2207: Immediate implicit closes don't close implicit objects", ->
key: for i in [1, 2, 3] then i
eq func().key.join(' '), '1 2 3'
test 'inline implicit object literals within multiline implicit object literals', ->
x =
a: aa: 0
b: 0
eq 0, x.b
eq 0, x.a.aa

View file

@ -218,6 +218,10 @@ test "#1714: lexer bug with raw range `for` followed by `in`", ->
test "#1099: statically determined `not in []` reporting incorrect result", ->
ok 0 not in []
test "#1099: make sure expression tested gets evaluted when array is empty", ->
a = 0
(do -> a = 1) in []
eq a, 1
# Chained Comparison
@ -296,3 +300,60 @@ test "#2567: Optimization of negated existential produces correct result", ->
test "#2508: Existential access of the prototype", ->
eq NonExistent?::nothing, undefined
ok Object?::toString
test "power operator", ->
eq 27, 3 ** 3
test "power operator has higher precedence than other maths operators", ->
eq 55, 1 + 3 ** 3 * 2
eq -4, -2 ** 2
eq false, !2 ** 2
eq 0, (!2) ** 2
eq -2, ~1 ** 5
test "power operator is right associative", ->
eq 2, 2 ** 1 ** 3
test "power operator compound assignment", ->
a = 2
a **= 3
eq 8, a
test "floor division operator", ->
eq 2, 7 // 3
eq -3, -7 // 3
eq NaN, 0 // 0
test "floor division operator compound assignment", ->
a = 7
a //= 2
eq 3, a
test "modulo operator", ->
check = (a, b, expected) ->
eq expected, a %% b, "expected #{a} %%%% #{b} to be #{expected}"
check 0, 1, 0
check 0, -1, -0
check 1, 0, NaN
check 1, 2, 1
check 1, -2, -1
check 1, 3, 1
check 2, 3, 2
check 3, 3, 0
check 4, 3, 1
check -1, 3, 2
check -2, 3, 1
check -3, 3, 0
check -4, 3, 2
check 5.5, 2.5, 0.5
check -5.5, 2.5, 2.0
test "modulo operator compound assignment", ->
a = -2
a %%= 5
eq 3, a
test "modulo operator converts arguments to numbers", ->
eq 1, 1 %% '42'
eq 1, '1' %% 42
eq 1, '1' %% '42'

View file

@ -55,25 +55,3 @@ test "an empty heregex will compile to an empty, non-capturing group", ->
test "#1724: regular expressions beginning with `*`", ->
throws -> CoffeeScript.compile '/// * ///'
test "empty regular expressions with flags", ->
fn = (x) -> x
a = "" + //i
fn ""
eq '/(?:)/i', a
test "#3059: don't remove escaped whitespace", ->
eq /// One\ cannot [\ ] escape \ \destiny. ///.source,
/One cannot[ ]escape \destiny./.source
test "#2238: don't escape already escaped slashes", ->
eq /// \\\/ \/ ///.source, /\\\/\//.source
test "escaped slashes don't close heregex", ->
eq /// \/// ///.source, /\/\/\//.source
eq /// \\\////.source, /\\\//.source
test "escaped linebreaks", ->
eq /// \n\
\
///.source, /\n\n\n/.source