Merge remote-tracking branch 'upstream/master'

Conflicts:
	lib/coffee-script/grammar.js
	lib/coffee-script/rewriter.js
	src/grammar.coffee
	src/rewriter.coffee
This commit is contained in:
Jason Walton
2013-02-06 10:29:19 -05:00
29 changed files with 660 additions and 743 deletions

View File

@@ -41,8 +41,8 @@ exports.compile = compile = (code, options = {}) ->
catch err
err.message = "In #{options.filename}, #{err.message}" if options.filename
throw err
header = "Generated by CoffeeScript #{@VERSION}"
"// #{header}\n#{js}"
footer = "Generated by CoffeeScript #{@VERSION}"
"#{js}\n// #{footer}\n"
# Tokenize a string of CoffeeScript code, and return the array of tokens.
exports.tokens = (code, options) ->

View File

@@ -43,7 +43,6 @@ SWITCHES = [
[ '--nodejs [ARGS]', 'pass options directly to the "node" binary']
['-o', '--output [DIR]', 'set the output directory for compiled JavaScript']
['-p', '--print', 'print out the compiled JavaScript']
['-r', '--require [FILE*]', 'require a library before executing your script']
['-s', '--stdio', 'listen for and compile scripts over stdio']
['-t', '--tokens', 'print out the tokens that the lexer/rewriter produce']
['-v', '--version', 'display the version number']
@@ -67,17 +66,15 @@ exports.run = ->
return forkNode() if opts.nodejs
return usage() if opts.help
return version() if opts.version
loadRequires() if opts.require
return require './repl' if opts.interactive
return require('./repl').start() if opts.interactive
if opts.watch and !fs.watch
return printWarn "The --watch feature depends on Node v0.6.0+. You are running #{process.version}."
return compileStdio() if opts.stdio
return compileScript null, sources[0] if opts.eval
return require './repl' unless sources.length
return require('./repl').start() unless sources.length
literals = if opts.run then sources.splice 1 else []
process.argv = process.argv[0..1].concat literals
process.argv[0] = 'coffee'
process.execPath = require.main.filename
for source in sources
compilePath source, yes, path.normalize source
@@ -88,14 +85,14 @@ compilePath = (source, topLevel, base) ->
fs.stat source, (err, stats) ->
throw err if err and err.code isnt 'ENOENT'
if err?.code is 'ENOENT'
if topLevel and path.extname(source) not in coffee_exts
if topLevel and source and path.extname(source) not in coffee_exts
source = sources[sources.indexOf(source)] = "#{source}.coffee"
return compilePath source, topLevel, base
if topLevel
console.error "File not found: #{source}"
process.exit 1
return
if stats.isDirectory()
if stats.isDirectory() and path.dirname(source) isnt 'node_modules'
watchDir source, base if opts.watch
fs.readdir source, (err, files) ->
throw err if err and err.code isnt 'ENOENT'
@@ -165,13 +162,6 @@ compileJoin = ->
joinTimeout = wait 100, ->
compileScript opts.join, sourceCode.join('\n'), opts.join
# Load files that are to-be-required before compilation occurs.
loadRequires = ->
realFilename = module.filename
module.filename = '.'
require req for req in opts.require
module.filename = realFilename
# Watch a source CoffeeScript file using `fs.watch`, recompiling it every
# time the file is updated. May be used in combination with other options,
# such as `--lint` or `--print`.

View File

@@ -51,6 +51,7 @@ o = (patternString, action, options) ->
action = action.replace /LOCDATA\(([0-9]*)\)/g, addLocationDataFn('$1')
action = action.replace /LOCDATA\(([0-9]*),\s*([0-9]*)\)/g, addLocationDataFn('$1', '$2')
action = action.replace /\b(Op|Value\.create)\b/g, 'yy.$&'
[patternString, "$$ = #{addLocationDataFn(1, patternCount)}(#{action});", options]
@@ -158,10 +159,10 @@ grammar =
# Assignment when it happens within an object literal. The difference from
# the ordinary **Assign** is that these allow numbers and strings as keys.
AssignObj: [
o 'ObjAssignable', -> new Value $1
o 'ObjAssignable : Expression', -> new Assign LOCDATA(1)(new Value $1), $3, 'object'
o 'ObjAssignable', -> Value.create $1
o 'ObjAssignable : Expression', -> new Assign LOCDATA(1)(Value.create($1)), $3, 'object'
o 'ObjAssignable :
INDENT Expression OUTDENT', -> new Assign LOCDATA(1)(new Value $1), $4, 'object'
INDENT Expression OUTDENT', -> new Assign LOCDATA(1)(Value.create($1)), $4, 'object'
o 'Comment'
]
@@ -235,26 +236,26 @@ grammar =
# Variables and properties that can be assigned to.
SimpleAssignable: [
o 'Identifier', -> new Value $1
o 'Identifier', -> Value.create $1
o 'Value Accessor', -> $1.add $2
o 'Invocation Accessor', -> new Value $1, [].concat $2
o 'Invocation Accessor', -> Value.create $1, [].concat $2
o 'ThisProperty'
]
# Everything that can be assigned to.
Assignable: [
o 'SimpleAssignable'
o 'Array', -> new Value $1
o 'Object', -> new Value $1
o 'Array', -> Value.create $1
o 'Object', -> Value.create $1
]
# The types of things that can be treated as values -- assigned to, invoked
# as functions, indexed into, named as a class, etc.
Value: [
o 'Assignable'
o 'Literal', -> new Value $1
o 'Parenthetical', -> new Value $1
o 'Range', -> new Value $1
o 'Literal', -> Value.create $1
o 'Parenthetical', -> Value.create $1
o 'Range', -> Value.create $1
o 'This'
]
@@ -329,13 +330,13 @@ grammar =
# A reference to the *this* current object.
This: [
o 'THIS', -> new Value new Literal 'this'
o '@', -> new Value new Literal 'this'
o 'THIS', -> Value.create new Literal 'this'
o '@', -> Value.create new Literal 'this'
]
# A reference to a property on *this*.
ThisProperty: [
o '@ Identifier', -> new Value LOCDATA(1)(new Literal 'this'), [LOCDATA(2)(new Access $2)], 'this'
o '@ Identifier', -> Value.create LOCDATA(1)(new Literal('this')), [LOCDATA(2)(new Access($2))], 'this'
]
# The array literal.
@@ -399,7 +400,7 @@ grammar =
# A catch clause names its error and runs a block of code.
Catch: [
o 'CATCH Identifier Block', -> [$2, $3]
o 'CATCH Object Block', -> [LOCDATA(2)(new Value $2), $3]
o 'CATCH Object Block', -> [LOCDATA(2)(Value.create($2)), $3]
]
# Throw an exception object.
@@ -448,7 +449,7 @@ grammar =
]
ForBody: [
o 'FOR Range', -> source: LOCDATA(2) new Value $2
o 'FOR Range', -> source: LOCDATA(2) Value.create($2)
o 'ForStart ForSource', -> $2.own = $1.own; $2.name = $1[0]; $2.index = $1[1]; $2
]
@@ -462,8 +463,8 @@ grammar =
ForValue: [
o 'Identifier'
o 'ThisProperty'
o 'Array', -> new Value $1
o 'Object', -> new Value $1
o 'Array', -> Value.create $1
o 'Object', -> Value.create $1
]
# An array or range comprehension has variables for the current element
@@ -529,30 +530,30 @@ grammar =
# -type rule, but in order to make the precedence binding possible, separate
# 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 Expression', -> Op.create $1 , $2
o '- Expression', (-> Op.create '-', $2), prec: 'UNARY'
o '+ Expression', (-> Op.create '+', $2), prec: 'UNARY'
o '-- SimpleAssignable', -> new Op '--', $2
o '++ SimpleAssignable', -> new Op '++', $2
o 'SimpleAssignable --', -> new Op '--', $1, null, true
o 'SimpleAssignable ++', -> new Op '++', $1, null, true
o '-- SimpleAssignable', -> Op.create '--', $2
o '++ SimpleAssignable', -> Op.create '++', $2
o 'SimpleAssignable --', -> Op.create '--', $1, null, true
o 'SimpleAssignable ++', -> Op.create '++', $1, null, true
# [The existential operator](http://jashkenas.github.com/coffee-script/#existence).
o 'Expression ?', -> new Existence $1
o 'Expression + Expression', -> new Op '+' , $1, $3
o 'Expression - Expression', -> new Op '-' , $1, $3
o 'Expression + Expression', -> Op.create '+' , $1, $3
o 'Expression - Expression', -> Op.create '-' , $1, $3
o 'Expression MATH 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
o 'Expression MATH Expression', -> Op.create $2, $1, $3
o 'Expression SHIFT Expression', -> Op.create $2, $1, $3
o 'Expression COMPARE Expression', -> Op.create $2, $1, $3
o 'Expression LOGIC Expression', -> Op.create $2, $1, $3
o 'Expression RELATION Expression', ->
if $2.charAt(0) is '!'
new Op($2[1..], $1, $3).invert()
Op.create($2[1..], $1, $3).invert()
else
new Op $2, $1, $3
Op.create $2, $1, $3
o 'SimpleAssignable COMPOUND_ASSIGN
Expression', -> new Assign $1, $3, $2

View File

@@ -134,7 +134,7 @@ exports.Base = class Base
child.traverseChildren crossScope, func
invert: ->
new Op '!', this
Op.create '!', this
unwrapAll: ->
node = this
@@ -395,12 +395,15 @@ exports.Return = class Return extends Base
# A value, variable or literal or parenthesized, indexed or dotted into,
# or vanilla.
exports.Value = class Value extends Base
constructor: (base, props, tag) ->
return base if not props and base instanceof Value
@base = base
@properties = props or []
@[tag] = true if tag
return this
@wrap: (base, props, tag) ->
if not props and base instanceof Value
base
else
new Value base, props, tag
constructor: (@base, @properties, tag) ->
@properties or= []
@[tag] = true if tag
children: ['base', 'properties']
@@ -446,16 +449,16 @@ exports.Value = class Value extends Base
name = last @properties
if @properties.length < 2 and not @base.isComplex() and not name?.isComplex()
return [this, this] # `a` `a.b`
base = new Value @base, @properties[...-1]
base = Value.wrap @base, @properties[...-1]
if base.isComplex() # `a().b`
bref = new Literal o.scope.freeVariable 'base'
base = new Value new Parens new Assign bref, base
base = Value.wrap new Parens new Assign bref, base
return [base, bref] unless name # `a()`
if name.isComplex() # `a[b()]`
nref = new Literal o.scope.freeVariable 'name'
name = new Index new Assign nref, name.index
nref = new Index nref
[base.add(name), new Value(bref or base.base, [nref or name])]
[base.add(name), Value.wrap(bref or base.base, [nref or name])]
# We compile a value to JavaScript by compiling and joining each property.
# Things get much more interesting if the chain of properties has *soak*
@@ -478,8 +481,8 @@ exports.Value = class Value extends Base
return ifn
for prop, i in @properties when prop.soak
prop.soak = off
fst = new Value @base, @properties[...i]
snd = new Value @base, @properties[i..]
fst = Value.wrap @base, @properties[...i]
snd = Value.wrap @base, @properties[i..]
if fst.isComplex()
ref = new Literal o.scope.freeVariable 'ref'
fst = new Parens new Assign ref, fst
@@ -535,7 +538,7 @@ exports.Call = class Call extends Base
accesses = [new Access(new Literal '__super__')]
accesses.push new Access new Literal 'constructor' if method.static
accesses.push new Access new Literal name
(new Value (new Literal method.klass), accesses).compile o
(Value.wrap (new Literal method.klass), accesses).compile o
else
"#{name}.__super__.constructor"
@@ -549,14 +552,14 @@ exports.Call = class Call extends Base
if @soak
if @variable
return ifn if ifn = unfoldSoak o, this, 'variable'
[left, rite] = new Value(@variable).cacheReference o
[left, rite] = Value.wrap(@variable).cacheReference o
else
left = new Literal @superReference o
rite = new Value left
rite = Value.wrap left
rite = new Call rite, @args
rite.isNew = @isNew
left = new Literal "typeof #{ left.compile o } === \"function\""
return new If left, new Value(rite), soak: yes
return new If left, Value.wrap(rite), soak: yes
call = this
list = []
loop
@@ -627,7 +630,7 @@ exports.Call = class Call extends Base
#{idt}return Object(result) === result ? result : child;
#{@tab}})(#{ @variable.compile o, LEVEL_LIST }, #{splatArgs}, function(){})
"""
base = new Value @variable
base = Value.wrap @variable
if (name = base.properties.pop()) and base.isComplex()
ref = o.scope.freeVariable 'ref'
fun = "(#{ref} = #{ base.compile o, LEVEL_LIST })#{ name.compile o }"
@@ -653,7 +656,7 @@ exports.Extends = class Extends extends Base
# Hooks one constructor into another's prototype chain.
compile: (o) ->
new Call(new Value(new Literal utility 'extends'), [@child, @parent]).compile o
new Call(Value.wrap(new Literal utility 'extends'), [@child, @parent]).compile o
#### Access
@@ -732,7 +735,7 @@ exports.Range = class Range extends Base
[from, to] = [+@fromNum, +@toNum]
if from <= to then "#{lt} #{to}" else "#{gt} #{to}"
else
cond = "#{@fromVar} <= #{@toVar}"
cond = if @stepVar then "#{@stepVar} > 0" else "#{@fromVar} <= #{@toVar}"
"#{cond} ? #{lt} #{@toVar} : #{gt} #{@toVar}"
# Generate the step.
@@ -912,7 +915,7 @@ exports.Class = class Class extends Base
addBoundFunctions: (o) ->
if @boundFuncs.length
for bvar in @boundFuncs
lhs = (new Value (new Literal "this"), [new Access bvar]).compile o
lhs = (Value.wrap (new Literal "this"), [new Access bvar]).compile o
@ctor.body.unshift new Literal "#{lhs} = #{utility 'bind'}(#{lhs}, this)"
# Merge the properties from a top-level object as prototypal properties
@@ -940,7 +943,7 @@ exports.Class = class Class extends Base
if func.bound
func.context = name
else
assign.variable = new Value(new Literal(name), [(new Access new Literal 'prototype'), new Access base ])
assign.variable = Value.wrap(new Literal(name), [(new Access new Literal 'prototype'), new Access base ])
if func instanceof Code and func.bound
@boundFuncs.push base
func.bound = no
@@ -969,17 +972,23 @@ exports.Class = class Class extends Base
# Make sure that a constructor is defined for the class, and properly
# configured.
ensureConstructor: (name) ->
ensureConstructor: (name, o) ->
if not @ctor
@ctor = new Code
@ctor.body.push new Literal "#{name}.__super__.constructor.apply(this, arguments)" if @parent
@ctor.body.push new Literal "#{@externalCtor}.apply(this, arguments)" if @externalCtor
@ctor.body.makeReturn()
@body.expressions.unshift @ctor
@ctor.ctor = @ctor.name = name
@ctor.klass = null
@ctor.noReturn = yes
# Prevent constructor from returning a value.
returnExpr = null
@ctor.body.traverseChildren no, (node) ->
return no if node instanceof Return and (returnExpr = node.expression)
if returnExpr
throw SyntaxError "cannot return a value from a constructor: \"#{returnExpr.compileNode o}\" in class #{name}"
# Instead of generating the JavaScript string directly, we build up the
# equivalent syntax tree and compile that, in pieces. You can see the
# constructor, property assignments, and inheritance getting built out below.
@@ -992,7 +1001,7 @@ exports.Class = class Class extends Base
@hoistDirectivePrologue()
@setContext name
@walkBody name, o
@ensureConstructor name
@ensureConstructor name, o
@body.spaced = yes
@body.expressions.unshift @ctor unless @ctor instanceof Code
@body.expressions.push lname
@@ -1078,15 +1087,12 @@ exports.Assign = class Assign extends Base
if obj instanceof Assign
{variable: {base: idx}, value: obj} = obj
else
if obj.base instanceof Parens
[obj, idx] = new Value(obj.unwrapAll()).cacheReference o
idx = if isObject
if obj.this then obj.properties[0].name else obj
else
idx = if isObject
if obj.this then obj.properties[0].name else obj
else
new Literal 0
new Literal 0
acc = IDENTIFIER.test idx.unwrap().value or 0
value = new Value value
value = Value.wrap value
value.properties.push new (if acc then Access else Index) idx
if obj.unwrap().value in RESERVED
throw new SyntaxError "assignment to a reserved word: #{obj.compile o} = #{value.compile o}"
@@ -1107,7 +1113,7 @@ exports.Assign = class Assign extends Base
else
# A shorthand `{a, b, @c} = val` pattern-match.
if obj.base instanceof Parens
[obj, idx] = new Value(obj.unwrapAll()).cacheReference o
[obj, idx] = Value.wrap(obj.unwrapAll()).cacheReference o
else
idx = if obj.this then obj.properties[0].name else obj
if not splat and obj instanceof Splat
@@ -1132,7 +1138,7 @@ exports.Assign = class Assign extends Base
acc = no
else
acc = isObject and IDENTIFIER.test idx.unwrap().value or 0
val = new Value new Literal(vvar), [new (if acc then Access else Index) idx]
val = Value.wrap new Literal(vvar), [new (if acc then Access else Index) idx]
if name? and name in RESERVED
throw new SyntaxError "assignment to a reserved word: #{obj.compile o} = #{val.compile o}"
assigns.push new Assign(obj, val, null, param: @param, subpattern: yes).compile o, LEVEL_LIST
@@ -1150,7 +1156,7 @@ exports.Assign = class Assign extends Base
left.base.value != "this" and not o.scope.check left.base.value
throw new Error "the variable \"#{left.base.value}\" can't be assigned with #{@context} because it has not been defined."
if "?" in @context then o.isExistentialEquals = true
new Op(@context[...-1], left, new Assign(right, @value, '=') ).compile o
Op.create(@context[...-1], left, new Assign(right, @value, '=') ).compile o
# Compile the assignment from an array splice literal, using JavaScript's
# `Array#splice` method.
@@ -1208,19 +1214,19 @@ exports.Code = class Code extends Base
for {name: p} in @params
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)),
new Value new Literal 'arguments'
splats = new Assign Value.wrap(new Arr(p.asReference o for p in @params)),
Value.wrap new Literal 'arguments'
break
for param in @params
if param.isComplex()
val = ref = param.asReference o
val = new Op '?', ref, param.value if param.value
exprs.push new Assign new Value(param.name), val, '=', param: yes
val = Op.create '?', ref, param.value if param.value
exprs.push new Assign Value.wrap(param.name), val, '=', param: yes
else
ref = param
if param.value
lit = new Literal ref.name.value + ' == null'
val = new Assign new Value(param.name), param.value, '='
val = new Assign Value.wrap(param.name), param.value, '='
exprs.push new If lit, val
params.push ref unless splats
wasEmpty = @body.isEmpty()
@@ -1281,7 +1287,7 @@ exports.Param = class Param extends Base
node = new Literal o.scope.freeVariable node.value
else if node.isComplex()
node = new Literal o.scope.freeVariable 'arg'
node = new Value node
node = Value.wrap node
node = new Splat node if @splat
@reference = node
@@ -1425,18 +1431,19 @@ exports.While = class While extends Base
# Simple Arithmetic and logical operations. Performs some conversion from
# CoffeeScript operations into their JavaScript equivalents.
exports.Op = class Op extends Base
constructor: (op, first, second, flip ) ->
return new In first, second if op is 'in'
@create: (op, first, second, flip) ->
if op is 'in'
return new In first, second
if op is 'do'
return @generateDo first
if op is 'new'
return first.newInstance() if first instanceof Call and not first.do and not first.isNew
first = new Parens first if first instanceof Code and first.bound or first.do
return new Op op, first, second, flip
constructor: (op, @first, @second, flip ) ->
@operator = CONVERSIONS[op] or op
@first = first
@second = second
@flip = !!flip
return this
# The map of conversions from CoffeeScript to JavaScript symbols.
CONVERSIONS =
@@ -1489,12 +1496,12 @@ exports.Op = class Op extends Base
fst.operator in ['!', 'in', 'instanceof']
fst
else
new Op '!', this
Op.create '!', this
unfoldSoak: (o) ->
@operator in ['++', '--', 'delete'] and unfoldSoak o, this, 'first'
generateDo: (exp) ->
@generateDo: (exp) ->
passedParams = []
func = if exp instanceof Assign and (ref = exp.value.unwrap()) instanceof Code
ref
@@ -1752,8 +1759,9 @@ exports.For = class For extends While
ivar = (@object and index) or scope.freeVariable 'i'
kvar = (@range and name) or index or ivar
kvarAssign = if kvar isnt ivar then "#{kvar} = " else ""
# the `_by` variable is created twice in `Range`s if we don't prevent it from being declared here
stepvar = scope.freeVariable "step" if @step and not @range
if @step and not @range
[step, stepVar] = @step.cache o, LEVEL_LIST
stepNum = stepVar.match SIMPLENUM
name = ivar if @pattern
varPart = ''
guardPart = ''
@@ -1764,16 +1772,29 @@ exports.For = class For extends While
else
svar = @source.compile o, LEVEL_LIST
if (name or @own) and not IDENTIFIER.test svar
defPart = "#{@tab}#{ref = scope.freeVariable 'ref'} = #{svar};\n"
defPart += "#{@tab}#{ref = scope.freeVariable 'ref'} = #{svar};\n"
svar = ref
if name and not @pattern
namePart = "#{name} = #{svar}[#{kvar}]"
unless @object
lvar = scope.freeVariable 'len'
forVarPart = "#{kvarAssign}#{ivar} = 0, #{lvar} = #{svar}.length"
forVarPart += ", #{stepvar} = #{@step.compile o, LEVEL_OP}" if @step
stepPart = "#{kvarAssign}#{if @step then "#{ivar} += #{stepvar}" else (if kvar isnt ivar then "++#{ivar}" else "#{ivar}++")}"
forPart = "#{forVarPart}; #{ivar} < #{lvar}; #{stepPart}"
if not @object
defPart += "#{@tab}#{step};\n" if step isnt stepVar
lvar = scope.freeVariable 'len' unless @step and stepNum and down = (+stepNum < 0)
declare = "#{kvarAssign}#{ivar} = 0, #{lvar} = #{svar}.length"
declareDown = "#{kvarAssign}#{ivar} = #{svar}.length - 1"
compare = "#{ivar} < #{lvar}"
compareDown = "#{ivar} >= 0"
if @step
if stepNum
if down
compare = compareDown
declare = declareDown
else
compare = "#{stepVar} > 0 ? #{compare} : #{compareDown}"
declare = "(#{stepVar} > 0 ? (#{declare}) : #{declareDown})"
increment = "#{ivar} += #{stepVar}"
else
increment = "#{if kvar isnt ivar then "++#{ivar}" else "#{ivar}++"}"
forPart = "#{declare}; #{compare}; #{kvarAssign}#{increment}"
if @returns
resultPart = "#{@tab}#{rvar} = [];\n"
returnResult = "\n#{@tab}return #{rvar};"
@@ -1809,7 +1830,7 @@ exports.For = class For extends While
val.properties[0].name?.value in ['call', 'apply'])
fn = val.base?.unwrapAll() or val
ref = new Literal o.scope.freeVariable 'fn'
base = new Value ref
base = Value.wrap ref
if val.base
[val.base, base] = [base, val]
body.expressions[idx] = new Call base, expr.args
@@ -1957,7 +1978,7 @@ Closure =
meth = new Literal if mentionsArgs then 'apply' else 'call'
args = [new Literal 'this']
args.push new Literal 'arguments' if mentionsArgs
func = new Value func, [new Access meth]
func = Value.wrap func, [new Access meth]
func.noReturn = noReturn
call = new Call func, args
if statement then Block.wrap [call] else call
@@ -1974,7 +1995,7 @@ Closure =
unfoldSoak = (o, parent, name) ->
return unless ifn = parent[name].unfoldSoak o
parent[name] = ifn.body
ifn.body = new Value parent
ifn.body = Value.wrap parent
ifn
# Constants

View File

@@ -1,197 +1,77 @@
# A very simple Read-Eval-Print-Loop. Compiles one line at a time to JavaScript
# and evaluates it. Good for simple tests, or poking around the **Node.js** API.
# Using it looks like this:
#
# coffee> console.log "#{num} bottles of beer" for num in [99..1]
# Start by opening up `stdin` and `stdout`.
stdin = process.openStdin()
stdout = process.stdout
# Require the **coffee-script** module to get access to the compiler.
vm = require 'vm'
nodeREPL = require 'repl'
CoffeeScript = require './coffee-script'
readline = require 'readline'
{inspect} = require 'util'
{Script} = require 'vm'
Module = require 'module'
{merge} = require './helpers'
# REPL Setup
replDefaults =
prompt: 'coffee> ',
eval: (input, context, filename, cb) ->
# XXX: multiline hack
input = input.replace /\uFF00/g, '\n'
# strip single-line comments
input = input.replace /(^|[\r\n]+)(\s*)##?(?:[^#\r\n][^\r\n]*|)($|[\r\n])/, '$1$2$3'
# empty command
return cb null if /^\s*$/.test input
# TODO: fix #1829: pass in-scope vars and avoid accidentally shadowing them by omitting those declarations
try
js = CoffeeScript.compile "_=(#{input}\n)", {filename, bare: yes}
catch err
cb err
cb null, vm.runInContext(js, context, filename)
# Config
REPL_PROMPT = 'coffee> '
REPL_PROMPT_MULTILINE = '------> '
REPL_PROMPT_CONTINUATION = '......> '
enableColours = no
unless process.platform is 'win32'
enableColours = not process.env.NODE_DISABLE_COLORS
addMultilineHandler = (repl) ->
{rli, inputStream, outputStream} = repl
# Log an error.
error = (err) ->
stdout.write (err.stack or err.toString()) + '\n'
multiline =
enabled: off
initialPrompt: repl.prompt.replace(/^[^> ]*/, (x) -> x.replace /./g, '-')
prompt: repl.prompt.replace(/^[^> ]*>?/, (x) -> x.replace /./g, '.')
buffer: ''
## Autocompletion
# Regexes to match complete-able bits of text.
ACCESSOR = /\s*([\w\.]+)(?:\.(\w*))$/
SIMPLEVAR = /(\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 obj = Script.runInThisContext obj
catch e
return
return unless obj?
obj = Object obj
candidates = Object.getOwnPropertyNames obj
while obj = Object.getPrototypeOf obj
for key in Object.getOwnPropertyNames obj when key not in candidates
candidates.push key
completions = getCompletions prefix, candidates
[completions, prefix]
# Attempt to autocomplete an in-scope free variable: `one`.
completeVariable = (text) ->
free = text.match(SIMPLEVAR)?[1]
free = "" if text is ""
if free?
vars = Script.runInThisContext 'Object.getOwnPropertyNames(Object(this))'
keywords = (r for r in CoffeeScript.RESERVED when r[..1] isnt '__')
candidates = vars
for key in keywords when key not in candidates
candidates.push key
completions = getCompletions free, candidates
[completions, free]
# Return elements of candidates for which `prefix` is a prefix.
getCompletions = (prefix, candidates) ->
el for el in candidates when 0 is el.indexOf prefix
# Make sure that uncaught exceptions don't kill the REPL.
process.on 'uncaughtException', error
# 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) ->
# remove single-line comments
buffer = buffer.replace /(^|[\r\n]+)(\s*)##?(?:[^#\r\n][^\r\n]*|)($|[\r\n])/, "$1$2$3"
# remove trailing newlines
buffer = buffer.replace /[\r\n]+$/, ""
if multilineMode
backlog += "#{buffer}\n"
repl.setPrompt REPL_PROMPT_CONTINUATION
repl.prompt()
# Proxy node's line listener
nodeLineListener = rli.listeners('line')[0]
rli.removeListener 'line', nodeLineListener
rli.on 'line', (cmd) ->
if multiline.enabled
multiline.buffer += "#{cmd}\n"
rli.setPrompt multiline.prompt
rli.prompt true
else
nodeLineListener cmd
return
if !buffer.toString().trim() and !backlog
repl.prompt()
# Handle Ctrl-v
inputStream.on 'keypress', (char, key) ->
return unless key and key.ctrl and not key.meta and not key.shift and key.name is 'v'
if multiline.enabled
# allow arbitrarily switching between modes any time before multiple lines are entered
unless multiline.buffer.match /\n/
multiline.enabled = not multiline.enabled
rli.setPrompt repl.prompt
rli.prompt true
return
# no-op unless the current line is empty
return if rli.line? and not rli.line.match /^\s*$/
# eval, print, loop
multiline.enabled = not multiline.enabled
rli.line = ''
rli.cursor = 0
rli.output.cursorTo 0
rli.output.clearLine 1
# XXX: multiline hack
multiline.buffer = multiline.buffer.replace /\n/g, '\uFF00'
rli.emit 'line', multiline.buffer
multiline.buffer = ''
else
multiline.enabled = not multiline.enabled
rli.setPrompt multiline.initialPrompt
rli.prompt true
return
code = backlog += buffer
if code[code.length - 1] is '\\'
backlog = "#{backlog[...-1]}\n"
repl.setPrompt REPL_PROMPT_CONTINUATION
repl.prompt()
return
repl.setPrompt REPL_PROMPT
backlog = ''
try
_ = global._
returnValue = CoffeeScript.eval "_=(#{code}\n)", {
filename: 'repl'
modulename: 'repl'
}
if returnValue is undefined
global._ = _
repl.output.write "#{inspect returnValue, no, 2, enableColours}\n"
catch err
error err
repl.prompt()
if stdin.readable and stdin.isRaw
# handle piped input
pipedInput = ''
repl =
prompt: -> stdout.write @_prompt
setPrompt: (p) -> @_prompt = p
input: stdin
output: stdout
on: ->
stdin.on 'data', (chunk) ->
pipedInput += chunk
return unless /\n/.test pipedInput
lines = pipedInput.split "\n"
pipedInput = lines[lines.length - 1]
for line in lines[...-1] when line
stdout.write "#{line}\n"
run line
return
stdin.on 'end', ->
for line in pipedInput.trim().split "\n" when line
stdout.write "#{line}\n"
run line
stdout.write '\n'
process.exit 0
else
# Create the REPL by listening to **stdin**.
if readline.createInterface.length < 3
repl = readline.createInterface stdin, autocomplete
stdin.on 'data', (buffer) -> repl.write buffer
else
repl = readline.createInterface stdin, stdout, autocomplete
multilineMode = off
# Handle multi-line mode switch
repl.input.on 'keypress', (char, key) ->
# test for Ctrl-v
return unless key and key.ctrl and not key.meta and not key.shift and key.name is 'v'
cursorPos = repl.cursor
repl.output.cursorTo 0
repl.output.clearLine 1
multilineMode = not multilineMode
repl._line() if not multilineMode and backlog
backlog = ''
repl.setPrompt (newPrompt = if multilineMode then REPL_PROMPT_MULTILINE else REPL_PROMPT)
repl.prompt()
repl.output.cursorTo newPrompt.length + (repl.cursor = cursorPos)
# Handle Ctrl-d press at end of last line in multiline mode
repl.input.on 'keypress', (char, key) ->
return unless multilineMode and repl.line
# test for Ctrl-d
return unless key and key.ctrl and not key.meta and not key.shift and key.name is 'd'
multilineMode = off
repl._line()
repl.on 'attemptClose', ->
if multilineMode
multilineMode = off
repl.output.cursorTo 0
repl.output.clearLine 1
repl._onLine repl.line
return
if backlog or repl.line
backlog = ''
repl.historyIndex = -1
repl.setPrompt REPL_PROMPT
repl.output.write '\n(^C again to quit)'
repl._line (repl.line = '')
else
repl.close()
repl.on 'close', ->
repl.output.write '\n'
repl.input.destroy()
repl.on 'line', run
repl.setPrompt REPL_PROMPT
repl.prompt()
module.exports =
start: (opts = {}) ->
opts = merge replDefaults, opts
repl = nodeREPL.start opts
repl.on 'exit', -> repl.outputStream.write '\n'
addMultilineHandler repl
repl

View File

@@ -155,6 +155,7 @@ class exports.Rewriter
addImplicitParentheses: ->
noCall = seenSingle = seenControl = no
callIndex = null
condition = (token, i) ->
[tag] = token
@@ -166,7 +167,7 @@ class exports.Rewriter
(tag is 'INDENT' and not seenControl)) and
(tag isnt 'INDENT' or
(@tag(i - 2) not in ['CLASS', 'EXTENDS'] and @tag(i - 1) not in IMPLICIT_BLOCK and
not ((post = @tokens[i + 1]) and post.generated and post[0] is '{')))
not (callIndex is i - 1 and (post = @tokens[i + 1]) and post.generated and post[0] is '{')))
action = (token, i) ->
@tokens.splice i, 0, @generate 'CALL_END', ')'
@@ -186,7 +187,8 @@ class exports.Rewriter
return 1 unless callObject or
prev?.spaced and (prev.call or prev[0] in IMPLICIT_FUNC) and
(tag in IMPLICIT_CALL or not (token.spaced or token.newLine) and tag in IMPLICIT_UNSPACED_CALL)
tokens.splice i, 0, @generate 'CALL_START', '('
callIndex = i
tokens.splice i, 0, @generate 'CALL_START', '(', token[2]
@detectEnd i + 1, condition, action
prev[0] = 'FUNC_EXIST' if prev[0] is '?'
2