From 0e40feb9af53a8350624f466df453b2f10194f48 Mon Sep 17 00:00:00 2001
From: Jeremy Ashkenas Override exported methods for non-Node.js engines. Use standard JavaScript Running code does not provide access to this scope. Use standard JavaScript Running code does not provide access to this scope. If we're not in a browser environment, we're finished with the public API. Load a remote script from the current domain via XHR. If we're not in a browser environment, we're finished with the public API. Load a remote script from the current domain via XHR. Mixin the top-level Cake functions for Cakefiles to use directly. Define a Cake task with a short name, an optional sentence description,
-and the function to run as the action itself. Define an option that the Cakefile accepts. The parsed options hash,
containing all of the command-line options passed, will be made available
-as the first argument to the action. Invoke another task in the current Cakefile. Invoke another task in the current Cakefile. Run Display the list of Cake tasks in a format similar to Print an error and exit when attempting to call an undefined task. When Print an error and exit when attempting to use an invalid task/option. When TODO: Remove registerExtension when fully deprecated. The current CoffeeScript version number. Words that cannot be used as identifiers in CoffeeScript code Expose helpers for testing. Compile a string of CoffeeScript code to JavaScript, using the Coffee/Jison
-compiler. The current CoffeeScript version number. Words that cannot be used as identifiers in CoffeeScript code Expose helpers for testing. Compile a string of CoffeeScript code to JavaScript, using the Coffee/Jison
+compiler. Tokenize a string of CoffeeScript code, and return the array of tokens. Tokenize a string of CoffeeScript code, and return the array of tokens. Parse a string of CoffeeScript code or an array of lexed tokens, and
return the AST. You can then compile it by calling Compile and execute a string of CoffeeScript (on the server), correctly
-setting Set the filename. Clear the module cache. Assign paths for node_modules loading Compile. Compile and evaluate a string of CoffeeScript (in a Node.js-like environment).
-The CoffeeScript REPL uses this to run the input. define module/require only if they chose not to specify their own use the same hack node currently uses for their own REPL Instantiate a Lexer for our use here. The real Lexer produces a generic stream of tokens. This object provides a
thin wrapper around it, compatible with the Jison API. We can then pass it
directly as a "Jison lexer". Allow CoffeeScript to emit Node.js events. The help banner that is printed when The help banner that is printed when Top-level objects shared by all the functions. Run Asynchronously read in each CoffeeScript in a list of source files and
-compile them. If a directory is passed, recursively compile all
-'.coffee' extension source files in it and all subdirectories. Compile a path, which could be a script or a directory. If a directory
+is passed, recursively compile all '.coffee' extension source files in it
+and all subdirectories. Compile a single source script, containing the given code, according to the
+ files = files.map (file) -> path.join source, file
+ index = sources.indexOf source
+ sources[index..index] = files
+ sourceCode[index..index] = files.map -> null
+ compilePath file, no, base for file in files
+ else if topLevel or path.extname(source) is '.coffee'
+ watch source, base if opts.watch
+ fs.readFile source, (err, code) ->
+ throw err if err
+ compileScript(source, code.toString(), base)
+ else
+ notSources[source] = yes
+ removeSource source, base
+ Compile a single source script, containing the given code, according to the
requested options. If evaluating the script directly sets After all of the source files are done being read, concatenate and compile
+ compileScript null, code If all of the source files are done being read, concatenate and compile
them together. Load files that are to-be-required before compilation occurs. Load files that are to-be-required before compilation occurs. Watch a source CoffeeScript file using Watch a directory of files for new additions. Write out a JavaScript source file with the compiled code. By default, files
-are written out in Remove a file from our source list, and source code cache. Optionally remove
+the compiled JS version as well. Get the corresponding output JavaScript path for a source file. Write out a JavaScript source file with the compiled code. By default, files
+are written out in Pipe compiled JS through JSLint (requires a working When watching scripts, it's useful to log changes with the timestamp. Pipe compiled JS through JSLint (requires a working Pretty-print a stream of tokens. Pretty-print a stream of tokens. Use the OptionParser module to extract all options from
+ printLine strings.join(' ') Use the OptionParser module to extract all options from
The compile-time options to pass to the CoffeeScript compiler. Start up a new Node.js instance with the arguments in The compile-time options to pass to the CoffeeScript compiler. Start up a new Node.js instance with the arguments in Print the Print the Print the Print the
browser.coffee
CoffeeScript = require './coffee-script'
-CoffeeScript.require = require
eval
to eval code. CoffeeScript.eval = (code, options) ->
- eval CoffeeScript.compile code, options
CoffeeScript.run = (code, options = {}) ->
+CoffeeScript.require = require
eval
to eval code. global ?= this
+CoffeeScript.eval = (code, options) ->
+ global.eval CoffeeScript.compile code, options
CoffeeScript.run = (code, options = {}) ->
options.bare = on
- Function(CoffeeScript.compile code, options)()
return unless window?
CoffeeScript.load = (url, callback) ->
+ Function(CoffeeScript.compile code, options)()
return unless window?
CoffeeScript.load = (url, callback) ->
xhr = new (window.ActiveXObject or XMLHttpRequest)('Microsoft.XMLHTTP')
xhr.open 'GET', url, true
xhr.overrideMimeType 'text/plain' if 'overrideMimeType' of xhr
diff --git a/documentation/docs/cake.html b/documentation/docs/cake.html
index 9490119f..69049f64 100644
--- a/documentation/docs/cake.html
+++ b/documentation/docs/cake.html
@@ -12,35 +12,42 @@ current directory's Cakefile.
options = {}
switches = []
oparse = null helpers.extend global,
task: (name, description, action) ->
+and the function to run as the action itself.
task: (name, description, action) ->
[action, description] = [description, action] unless action
tasks[name] = {name, description, action}
option: (letter, flag, description) ->
- switches.push [letter, flag, description]
invoke: (name) ->
+as the first argument to the action.
option: (letter, flag, description) ->
+ switches.push [letter, flag, description]
invoke: (name) ->
missingTask name unless tasks[name]
tasks[name].action options
cake
. Executes all of the tasks you pass, in order. Note that Node's
asynchrony may cause tasks to execute in a different order than you'd expect.
-If no tasks are passed, print the help screen. Keep a reference to the
-original directory name, when running Cake tasks from subdirectories. exports.run = ->
+If no tasks are passed, print the help screen. Keep a reference to the
+original directory name, when running Cake tasks from subdirectories.
exports.run = ->
global.__originalDirname = fs.realpathSync '.'
process.chdir cakefileDirectory __originalDirname
args = process.argv.slice 2
- CoffeeScript.run fs.readFileSync('Cakefile').toString(), filename: 'Cakefile'
+ CoffeeScript.run fs.readFileSync('Cakefile').toString(), filename: 'Cakefile'
oparse = new optparse.OptionParser switches
return printTasks() unless args.length
- options = oparse.parse(args)
+ try
+ options = oparse.parse(args)
+ catch e
+ return fatalError "#{e}"
invoke arg for arg in options.arguments
rake -T
printTasks = ->
- console.log ''
+ cakefilePath = path.join path.relative(__originalDirname, process.cwd()), 'Cakefile'
+ console.log "#{cakefilePath} defines the following tasks:\n"
for name, task of tasks
spaces = 20 - name.length
spaces = if spaces > 0 then Array(spaces + 1).join(' ') else ''
desc = if task.description then "# #{task.description}" else ''
console.log "cake #{name}#{spaces} #{desc}"
- console.log oparse.help() if switches.length
missingTask = (task) ->
- console.log "No such task: \"#{task}\""
- process.exit 1
cake
is invoked, search in the current and all parent directories
-to find the relevant Cakefile. cakefileDirectory = (dir) ->
+ console.log oparse.help() if switches.length
fatalError = (message) ->
+ console.error message + '\n'
+ console.log 'To see a list of all tasks/options, run "cake"'
+ process.exit 1
+
+missingTask = (task) -> fatalError "No such task: #{task}"
cake
is invoked, search in the current and all parent directories
+to find the relevant Cakefile. cakefileDirectory = (dir) ->
return dir if path.existsSync path.join dir, 'Cakefile'
parent = path.normalize path.join dir, '..'
return cakefileDirectory parent unless parent is dir
diff --git a/documentation/docs/coffee-script.html b/documentation/docs/coffee-script.html
index 343be7e8..bdf5cb31 100644
--- a/documentation/docs/coffee-script.html
+++ b/documentation/docs/coffee-script.html
@@ -9,31 +9,32 @@ execute all scripts present in
text/coffeescript
tags.
{Lexer,RESERVED} = require './lexer'
{parser} = require './parser'
vm = require 'vm' if require.extensions
- require.extensions['.coffee'] = (module, filename) ->
+ require.extensions['.coffee'] = (module, filename) ->
content = compile fs.readFileSync(filename, 'utf8'), {filename}
module._compile content, filename
else if require.registerExtension
- require.registerExtension '.coffee', (content) -> compile content
exports.VERSION = '1.1.3'
exports.RESERVED = RESERVED
exports.helpers = require './helpers'
exports.compile = compile = (code, options = {}) ->
+ require.registerExtension '.coffee', (content) -> compile content
exports.VERSION = '1.2.0'
exports.RESERVED = RESERVED
exports.helpers = require './helpers'
exports.compile = compile = (code, options = {}) ->
+ {merge} = exports.helpers
try
- (parser.parse lexer.tokenize code).compile options
+ (parser.parse lexer.tokenize code).compile merge {}, options
catch err
err.message = "In #{options.filename}, #{err.message}" if options.filename
- throw err
exports.tokens = (code, options) ->
+ throw err
exports.tokens = (code, options) ->
lexer.tokenize code, options
.compile()
on the root,
-or traverse it by using .traverse()
with a callback. exports.nodes = (source, options) ->
+or traverse it by using
.traverseChildren()
with a callback. exports.nodes = (source, options) ->
if typeof source is 'string'
parser.parse lexer.tokenize source, options
else
parser.parse source
__filename
, __dirname
, and relative require()
. exports.run = (code, options) ->
+setting
__filename
, __dirname
, and relative require()
. exports.run = (code, options) ->
mainModule = require.main
mainModule.filename = process.argv[1] =
if options.filename then fs.realpathSync(options.filename) else '.'
mainModule.moduleCache and= {}
mainModule.paths = require('module')._nodeModulePaths path.dirname options.filename
if path.extname(mainModule.filename) isnt '.coffee' or require.extensions
mainModule._compile compile(code, options), mainModule.filename
else
mainModule._compile code, mainModule.filename
exports.eval = (code, options = {}) ->
+The CoffeeScript REPL uses this to run the input.
sandbox.__dirname = path.dirname sandbox.__filename exports.eval = (code, options = {}) ->
return unless code = code.trim()
Script = vm.Script
if Script
@@ -50,10 +51,10 @@ The CoffeeScript REPL uses this to run the input.
unless sandbox isnt global or sandbox.module or sandbox.require
Module = require 'module'
sandbox.module = _module = new Module(options.modulename || 'eval')
- sandbox.require = _require = (path) -> Module._load path, _module, true
+ sandbox.require = _require = (path) -> Module._load path, _module, true
_module.filename = sandbox.__filename
_require[r] = require[r] for r in Object.getOwnPropertyNames require when r isnt 'paths'
vm.runInContext js, sandbox _require.paths = _module.paths = Module._nodeModulePaths process.cwd()
- _require.resolve = (request) -> Module._resolveFilename request, _module
+ _require.resolve = (request) -> Module._resolveFilename request, _module
o = {}
o[k] = v for own k, v of options
o.bare = on # ensure return value
@@ -64,12 +65,12 @@ The CoffeeScript REPL uses this to run the input.
lexer = new Lexer
parser.lexer =
- lex: ->
+ lex: ->
[tag, @yytext, @yylineno] = @tokens[@pos++] or ['']
tag
- setInput: (@tokens) ->
+ setInput: (@tokens) ->
@pos = 0
- upcomingInput: ->
+ upcomingInput: ->
""
parser.yy = require './nodes'
diff --git a/documentation/docs/command.html b/documentation/docs/command.html
index 480e051d..b830c63e 100644
--- a/documentation/docs/command.html
+++ b/documentation/docs/command.html
@@ -10,8 +10,8 @@ interactive REPL.
{spawn, exec} = require 'child_process'
{EventEmitter} = require 'events' helpers.extend CoffeeScript, new EventEmitter
-printLine = (line) -> process.stdout.write line + '\n'
-printWarn = (line) -> process.stderr.write line + '\n'
coffee
is called without arguments. BANNER = '''
+printLine = (line) -> process.stdout.write line + '\n'
+printWarn = (line) -> process.stderr.write line + '\n'
coffee
is called without arguments. BANNER = '''
Usage: coffee [options] path/to/script.coffee
If called without options, `coffee` will run your script.
@@ -34,7 +34,9 @@ interactive REPL.
['-w', '--watch', 'watch scripts for changes and rerun commands']
] opts = {}
sources = []
-contents = []
+sourceCode = []
+notSources = {}
+watchers = {}
optionParser = null
coffee
by parsing passed options and determining what action to take.
Many flags cause us to divert before compiling anything. Flags passed after
--
will be passed verbatim to your script as arguments in process.argv
exports.run = ->
@@ -44,6 +46,8 @@ Many flags cause us to divert before compiling anything. Flags passed after
return version() if opts.version
loadRequires() if opts.require
return require './repl' if opts.interactive
+ if opts.watch and !fs.watch
+ 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
@@ -52,54 +56,40 @@ Many flags cause us to divert before compiling anything. Flags passed after
process.argv = process.argv.slice(0, 2).concat opts.literals
process.argv[0] = 'coffee'
process.execPath = require.main.filename
- compileScripts()
compileScripts = ->
- unprocessed = []
- remaining_files = ->
- total = 0
- total += x for x in unprocessed
- total
- trackUnprocessedFiles = (sourceIndex, fileCount) ->
- unprocessed[sourceIndex] ?= 0
- unprocessed[sourceIndex] += fileCount
- trackCompleteFiles = (sourceIndex, fileCount) ->
- unprocessed[sourceIndex] -= fileCount
- if opts.join
- if helpers.compact(contents).length > 0 and remaining_files() == 0
- compileJoin()
for source in sources
- trackUnprocessedFiles sources.indexOf(source), 1
- for source in sources
- base = path.join(source)
- compile = (source, sourceIndex, topLevel) ->
- path.exists source, (exists) ->
- if topLevel and not exists and source[-7..] isnt '.coffee'
- return compile "#{source}.coffee", sourceIndex, topLevel
- throw new Error "File not found: #{source}" if topLevel and not exists
- fs.stat source, (err, stats) ->
+ compilePath source, yes, path.normalize source
+
compilePath = (source, topLevel, base) ->
+ path.exists source, (exists) ->
+ if topLevel and not exists and source[-7..] isnt '.coffee'
+ source = sources[sources.indexOf(source)] = "#{source}.coffee"
+ return compilePath source, topLevel, base
+ if topLevel and not exists
+ console.error "File not found: #{source}"
+ process.exit 1
+ fs.stat source, (err, stats) ->
+ throw err if err
+ if stats.isDirectory()
+ watchDir source, base if opts.watch
+ fs.readdir source, (err, files) ->
throw err if err
- if stats.isDirectory()
- fs.readdir source, (err, files) ->
- throw err if err
- trackUnprocessedFiles sourceIndex, files.length
- for file in files
- compile path.join(source, file), sourceIndex
- trackCompleteFiles sourceIndex, 1
- else if topLevel or path.extname(source) is '.coffee'
- fs.readFile source, (err, code) ->
- throw err if err
- if opts.join
- contents[sourceIndex] = helpers.compact([contents[sourceIndex], code.toString()]).join('\n')
- else
- compileScript(source, code.toString(), base)
- trackCompleteFiles sourceIndex, 1
- watch source, base if opts.watch and not opts.join
- else
- trackCompleteFiles sourceIndex, 1
- compile source, sources.indexOf(source), true
__filename
,
-__dirname
and module.filename
to be correct relative to the script's path. compileScript = (file, input, base) ->
+
__dirname
and module.filename
to be correct relative to the script's path. compileScript = (file, input, base) ->
o = opts
options = compileOptions file
try
@@ -108,6 +98,9 @@ requested options. If evaluating the script directly sets
__filename
if o.tokens then printTokens CoffeeScript.tokens t.input
else if o.nodes then printLine CoffeeScript.nodes(t.input).toString().trim()
else if o.run then CoffeeScript.run t.input, t.options
+ else if o.join and t.file isnt o.join
+ sourceCode[sources.indexOf(t.file)] = t.input
+ compileJoin()
else
t.output = CoffeeScript.compile t.input, t.options
CoffeeScript.emit 'success', task
@@ -123,76 +116,128 @@ requested options. If evaluating the script directly sets __filename
stdout. compileStdio = ->
code = ''
stdin = process.openStdin()
- stdin.on 'data', (buffer) ->
+ stdin.on 'data', (buffer) ->
code += buffer.toString() if buffer
stdin.on 'end', ->
- compileScript null, code
compileJoin = ->
- code = contents.join '\n'
- compileScript opts.join, code, opts.join
loadRequires = ->
+ return unless opts.join
+ unless sourceCode.some((code) -> code is null)
+ compileScript opts.join, sourceCode.join('\n'), opts.join
loadRequires = ->
realFilename = module.filename
module.filename = '.'
require req for req in opts.require
module.filename = realFilename
fs.watch
, recompiling it every
time the file is updated. May be used in combination with other options,
-such as --lint
or --print
. watch = (source, base) ->
- fs.stat source, (err, prevStats)->
- throw err if err
- fs.watch source, (event) ->
- if event is 'change'
- fs.stat source, (err, stats) ->
+such as
--lint
or --print
. watch = (source, base) ->
+
+ prevStats = null
+
+ compile = ->
+ fs.stat source, (err, stats) ->
+ throw err if err
+ return if prevStats and (stats.size is prevStats.size and
+ stats.mtime.getTime() is prevStats.mtime.getTime())
+ prevStats = stats
+ fs.readFile source, (err, code) ->
+ throw err if err
+ compileScript(source, code.toString(), base)
+
+ watcher = fs.watch source, callback = (event) ->
+ if event is 'change'
+ compile()
+ else if event is 'rename'
+ watcher.close()
+ setTimeout ->
+ path.exists source, (exists) ->
+ if exists
+ compile()
+ watcher = fs.watch source, callback
+ else
+ removeSource source, base, yes
+ compileJoin()
+ , 250
+
watchDir = (source, base) ->
+ watcher = fs.watch source, ->
+ path.exists source, (exists) ->
+ if exists
+ fs.readdir source, (err, files) ->
throw err if err
- return if stats.size is prevStats.size and
- stats.mtime.getTime() is prevStats.mtime.getTime()
- prevStats = stats
- fs.readFile source, (err, code) ->
- throw err if err
- compileScript(source, code.toString(), base)
cwd
as .js
files with the same name, but the output
-directory can be customized with --output
. writeJs = (source, js, base) ->
+ files = files.map (file) -> path.join source, file
+ for file in files when not notSources[file]
+ continue if sources.some (s) -> s.indexOf(file) >= 0
+ sources.push file
+ sourceCode.push null
+ compilePath file, no, base
+ else
+ watcher.close()
+ toRemove = (file for file in sources when file.indexOf(source) >= 0)
+ removeSource file, base, yes for file in toRemove
+ compileJoin()
+
removeSource = (source, base, removeJs) ->
+ index = sources.indexOf source
+ sources.splice index, 1
+ sourceCode.splice index, 1
+ if removeJs and not opts.join
+ jsPath = outputPath source, base
+ path.exists jsPath, (exists) ->
+ if exists
+ fs.unlink jsPath, (err) ->
+ throw err if err
+ timeLog "removed #{source}"
+
outputPath = (source, base) ->
filename = path.basename(source, path.extname(source)) + '.js'
srcDir = path.dirname source
baseDir = if base is '.' then srcDir else srcDir.substring base.length
dir = if opts.output then path.join opts.output, baseDir else srcDir
- jsPath = path.join dir, filename
- compile = ->
+ path.join dir, filename
cwd
as .js
files with the same name, but the output
+directory can be customized with --output
. writeJs = (source, js, base) ->
+ jsPath = outputPath source, base
+ jsDir = path.dirname jsPath
+ compile = ->
js = ' ' if js.length <= 0
- fs.writeFile jsPath, js, (err) ->
+ fs.writeFile jsPath, js, (err) ->
if err
printLine err.message
else if opts.compile and opts.watch
- console.log "#{(new Date).toLocaleTimeString()} - compiled #{source}"
- path.exists dir, (exists) ->
- if exists then compile() else exec "mkdir -p #{dir}", compile
jsl
command), printing
-any errors or warnings that arise. lint = (file, js) ->
- printIt = (buffer) -> printLine file + ':\t' + buffer.toString().trim()
+ timeLog "compiled #{source}"
+ path.exists jsDir, (exists) ->
+ if exists then compile() else exec "mkdir -p #{jsDir}", compile
+
timeLog = (message) ->
+ console.log "#{(new Date).toLocaleTimeString()} - #{message}"
jsl
command), printing
+any errors or warnings that arise. lint = (file, js) ->
+ printIt = (buffer) -> printLine file + ':\t' + buffer.toString().trim()
conf = __dirname + '/../../extras/jsl.conf'
jsl = spawn 'jsl', ['-nologo', '-stdin', '-conf', conf]
jsl.stdout.on 'data', printIt
jsl.stderr.on 'data', printIt
jsl.stdin.write js
- jsl.stdin.end()
printTokens = (tokens) ->
+ jsl.stdin.end()
printTokens = (tokens) ->
strings = for token in tokens
[tag, value] = [token[0], token[1].toString().replace(/\n/, '\\n')]
"[#{tag} #{value}]"
- printLine strings.join(' ')
process.argv
that are specified in SWITCHES
. parseOptions = ->
optionParser = new optparse.OptionParser SWITCHES, BANNER
o = opts = optionParser.parse process.argv.slice 2
o.compile or= !!o.output
o.run = not (o.compile or o.print or o.lint)
o.print = !! (o.print or (o.eval or o.stdio and o.compile))
- sources = o.arguments
compileOptions = (filename) -> {filename, bare: opts.bare}
--nodejs
passed to
+ sources = o.arguments
+ sourceCode[i] = null for source, i in sources
+ return compileOptions = (filename) -> {filename, bare: opts.bare}
--nodejs
passed to
the node
binary, preserving the other options. forkNode = ->
nodeArgs = opts.nodejs.split /\s+/
args = process.argv[1..]
args.splice args.indexOf('--nodejs'), 2
spawn process.execPath, nodeArgs.concat(args),
- cwd: process.cwd()
- env: process.env
- customFds: [0, 1, 2]
--help
usage message and exit. Deprecated switches are not
+ cwd: process.cwd()
+ env: process.env
+ customFds: [0, 1, 2] --help
usage message and exit. Deprecated switches are not
shown. usage = ->
- printLine (new optparse.OptionParser SWITCHES, BANNER).help()
--version
message and exit. version = ->
+ printLine (new optparse.OptionParser SWITCHES, BANNER).help()
--version
message and exit. version = ->
printLine "CoffeeScript version #{CoffeeScript.VERSION}"
o = (patternString, action, options) ->
+previous nonterminal.
o = (patternString, action, options) ->
patternString = patternString.replace /\s{2,}/g, ' '
return [patternString, '$$ = $1;', options] unless action
action = if match = unwrap.exec action then match[1] else "(#{action}())"
@@ -35,26 +35,25 @@ their numeric position, so in this rule:
$1
would be the value of the first Expression
, $2
would be the token
for the UNLESS
terminal, and $3
would be the value of the second
Expression
.
grammar =
The Root is the top-level node in the syntax tree. Since we parse bottom-up, -all parsing must end here.
Root: [
+all parsing must end here.
Root: [
o '', -> new Block
o 'Body'
o 'Block TERMINATOR'
- ]
Any list of statements and expressions, separated by line breaks or semicolons.
Body: [
+ ]
Any list of statements and expressions, separated by line breaks or semicolons.
Body: [
o 'Line', -> Block.wrap [$1]
o 'Body TERMINATOR Line', -> $1.push $3
o 'Body TERMINATOR'
- ]
Block and statements, which make up a line in a body.
Line: [
+ ]
Block and statements, which make up a line in a body.
Line: [
o 'Expression'
o 'Statement'
- ]
Pure statements which cannot be expressions.
Statement: [
+ ]
Pure statements which cannot be expressions.
Statement: [
o 'Return'
- o 'Throw'
o 'Comment'
o 'STATEMENT', -> new Literal $1
]
All the different types of expressions in our language. The basic unit of CoffeeScript is the Expression -- everything that can be an expression is one. Blocks serve as the building blocks of many other rules, making -them somewhat circular.
Expression: [
+them somewhat circular.
Expression: [
o 'Value'
o 'Invocation'
o 'Code'
@@ -66,31 +65,34 @@ them somewhat circular.
An indented block of expressions. Note that the Rewriter will convert some postfix forms into blocks for us, by adjusting the -token stream.
Block: [
+token stream.
Block: [
o 'INDENT OUTDENT', -> new Block
o 'INDENT Body OUTDENT', -> $2
- ]
A literal identifier, a variable name or property.
Identifier: [
+ ]
A literal identifier, a variable name or property.
Identifier: [
o 'IDENTIFIER', -> new Literal $1
]
Alphanumerics are separated from the other Literal matchers because -they can also serve as keys in object literals.
AlphaNumeric: [
+they can also serve as keys in object literals.
AlphaNumeric: [
o 'NUMBER', -> new Literal $1
o 'STRING', -> new Literal $1
]
All of our immediate values. Generally these can be passed straight -through and printed to JavaScript.
Literal: [
+through and printed to JavaScript.
Literal: [
o 'AlphaNumeric'
o 'JS', -> new Literal $1
o 'REGEX', -> new Literal $1
+ o 'DEBUGGER', -> new Literal $1
o 'BOOL', ->
val = new Literal $1
val.isUndefined = yes if $1 is 'undefined'
val
- ]
Assignment of a variable, property, or index to a value.
Assign: [
+ ]
Assignment of a variable, property, or index to a value.
Assign: [
o 'Assignable = Expression', -> new Assign $1, $3
+ o 'Assignable = TERMINATOR Expression', -> new Assign $1, $4
o 'Assignable = INDENT Expression OUTDENT', -> new Assign $1, $4
]
Assignment when it happens within an object literal. The difference from -the ordinary Assign is that these allow numbers and strings as keys.
AssignObj: [
+the ordinary Assign is that these allow numbers and strings as keys.
AssignObj: [
o 'ObjAssignable', -> new Value $1
o 'ObjAssignable : Expression', -> new Assign new Value($1), $3, 'object'
o 'ObjAssignable :
@@ -98,85 +100,85 @@ the ordinary Assign is that these allow numbers and strings as
o 'Comment'
]
- ObjAssignable: [
+ ObjAssignable: [
o 'Identifier'
o 'AlphaNumeric'
o 'ThisProperty'
- ]
A return statement from a function body.
Return: [
+ ]
A return statement from a function body.
Return: [
o 'RETURN Expression', -> new Return $2
o 'RETURN', -> new Return
- ]
A block comment.
Comment: [
+ ]
A block comment.
Comment: [
o 'HERECOMMENT', -> new Comment $1
]
The Code node is the function literal. It's defined by an indented block of Block preceded by a function arrow, with an optional parameter -list.
Code: [
+list.
Code: [
o 'PARAM_START ParamList PARAM_END FuncGlyph Block', -> new Code $2, $5, $4
o 'FuncGlyph Block', -> new Code [], $2, $1
]
CoffeeScript has two different symbols for functions. ->
is for ordinary
-functions, and =>
is for functions bound to the current value of this.
FuncGlyph: [
+functions, and =>
is for functions bound to the current value of this.
FuncGlyph: [
o '->', -> 'func'
o '=>', -> 'boundfunc'
- ]
An optional, trailing comma.
OptComma: [
+ ]
An optional, trailing comma.
OptComma: [
o ''
o ','
- ]
The list of parameters that a function accepts can be of any length.
ParamList: [
+ ]
The list of parameters that a function accepts can be of any length.
ParamList: [
o '', -> []
o 'Param', -> [$1]
o 'ParamList , Param', -> $1.concat $3
]
A single parameter in a function definition can be ordinary, or a splat -that hoovers up the remaining arguments.
Param: [
+that hoovers up the remaining arguments.
Param: [
o 'ParamVar', -> new Param $1
o 'ParamVar ...', -> new Param $1, null, on
o 'ParamVar = Expression', -> new Param $1, $3
- ]
Function Parameters
ParamVar: [
+ ]
Function Parameters
ParamVar: [
o 'Identifier'
o 'ThisProperty'
o 'Array'
o 'Object'
- ]
A splat that occurs outside of a parameter list.
Splat: [
+ ]
A splat that occurs outside of a parameter list.
Splat: [
o 'Expression ...', -> new Splat $1
- ]
Variables and properties that can be assigned to.
SimpleAssignable: [
+ ]
Variables and properties that can be assigned to.
SimpleAssignable: [
o 'Identifier', -> new Value $1
o 'Value Accessor', -> $1.add $2
- o 'Invocation Accessor', -> new Value $1, [$2]
+ o 'Invocation Accessor', -> new Value $1, [].concat $2
o 'ThisProperty'
- ]
Everything that can be assigned to.
Assignable: [
+ ]
Everything that can be assigned to.
Assignable: [
o 'SimpleAssignable'
o 'Array', -> new Value $1
o 'Object', -> new Value $1
]
The types of things that can be treated as values -- assigned to, invoked -as functions, indexed into, named as a class, etc.
Value: [
+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 'This'
]
The general group of accessors into an object, by property, by prototype -or by array index or slice.
Accessor: [
+or by array index or slice.
Accessor: [
o '. Identifier', -> new Access $2
o '?. Identifier', -> new Access $2, 'soak'
o ':: Identifier', -> [(new Access new Literal 'prototype'), new Access $2]
o '::', -> new Access new Literal 'prototype'
o 'Index'
- ]
Indexing into an object or array using bracket notation.
Index: [
+ ]
Indexing into an object or array using bracket notation.
Index: [
o 'INDEX_START IndexValue INDEX_END', -> $2
- o 'INDEX_SOAK Index', -> extend $2, soak : yes
+ o 'INDEX_SOAK Index', -> extend $2, soak : yes
]
- IndexValue: [
+ IndexValue: [
o 'Expression', -> new Index $1
o 'Slice', -> new Slice $1
]
In CoffeeScript, an object literal is simply a list of assignments.
Object: [
o '{ AssignList OptComma }', -> new Obj $2, $1.generated
]
Assignment of properties within an object literal can be separated by -comma, as in JavaScript, or simply by newline.
AssignList: [
+comma, as in JavaScript, or simply by newline.
AssignList: [
o '', -> []
o 'AssignObj', -> [$1]
o 'AssignList , AssignObj', -> $1.concat $3
o 'AssignList OptComma TERMINATOR AssignObj', -> $1.concat $4
o 'AssignList OptComma INDENT AssignList OptComma OUTDENT', -> $1.concat $4
]
Class definitions have optional bodies of prototype property assignments, -and optional references to the superclass.
Class: [
+and optional references to the superclass.
Class: [
o 'CLASS', -> new Class
o 'CLASS Block', -> new Class null, null, $2
o 'CLASS EXTENDS Expression', -> new Class null, $3
@@ -185,152 +187,152 @@ and optional references to the superclass.
Ordinary function invocation, or a chained series of calls.
Invocation: [
+ ]
Ordinary function invocation, or a chained series of calls.
Invocation: [
o 'Value OptFuncExist Arguments', -> new Call $1, $3, $2
o 'Invocation OptFuncExist Arguments', -> new Call $1, $3, $2
o 'SUPER', -> new Call 'super', [new Splat new Literal 'arguments']
o 'SUPER Arguments', -> new Call 'super', $2
- ]
An optional existence check on a function.
OptFuncExist: [
+ ]
An optional existence check on a function.
OptFuncExist: [
o '', -> no
o 'FUNC_EXIST', -> yes
- ]
The list of arguments to a function call.
Arguments: [
+ ]
The list of arguments to a function call.
Arguments: [
o 'CALL_START CALL_END', -> []
o 'CALL_START ArgList OptComma CALL_END', -> $2
- ]
A reference to the this current object.
This: [
+ ]
A reference to the this current object.
This: [
o 'THIS', -> new Value new Literal 'this'
o '@', -> new Value new Literal 'this'
- ]
A reference to a property on this.
ThisProperty: [
+ ]
A reference to a property on this.
ThisProperty: [
o '@ Identifier', -> new Value new Literal('this'), [new Access($2)], 'this'
]
The array literal.
Array: [
o '[ ]', -> new Arr []
o '[ ArgList OptComma ]', -> new Arr $2
- ]
Inclusive and exclusive range dots.
RangeDots: [
+ ]
Inclusive and exclusive range dots.
RangeDots: [
o '..', -> 'inclusive'
o '...', -> 'exclusive'
- ]
The CoffeeScript range literal.
Range: [
+ ]
The CoffeeScript range literal.
Range: [
o '[ Expression RangeDots Expression ]', -> new Range $2, $4, $3
- ]
Array slice literals.
Slice: [
+ ]
Array slice literals.
Slice: [
o 'Expression RangeDots Expression', -> new Range $1, $3, $2
o 'Expression RangeDots', -> new Range $1, null, $2
o 'RangeDots Expression', -> new Range null, $2, $1
]
The ArgList is both the list of objects passed into a function call, as well as the contents of an array literal -(i.e. comma-separated expressions). Newlines work as well.
ArgList: [
+(i.e. comma-separated expressions). Newlines work as well.
ArgList: [
o 'Arg', -> [$1]
o 'ArgList , Arg', -> $1.concat $3
o 'ArgList OptComma TERMINATOR Arg', -> $1.concat $4
o 'INDENT ArgList OptComma OUTDENT', -> $2
o 'ArgList OptComma INDENT ArgList OptComma OUTDENT', -> $1.concat $4
- ]
Valid arguments are Blocks or Splats.
Arg: [
+ ]
Valid arguments are Blocks or Splats.
Arg: [
o 'Expression'
o 'Splat'
]
Just simple, comma-separated, required arguments (no fancy syntax). We need this to be separate from the ArgList for use in Switch blocks, where -having the newlines wouldn't make sense.
SimpleArgs: [
+having the newlines wouldn't make sense.
SimpleArgs: [
o 'Expression'
o 'SimpleArgs , Expression', -> [].concat $1, $3
- ]
The variants of try/catch/finally exception handling blocks.
Try: [
+ ]
The variants of try/catch/finally exception handling blocks.
Try: [
o 'TRY Block', -> new Try $2
o 'TRY Block Catch', -> new Try $2, $3[0], $3[1]
o 'TRY Block FINALLY Block', -> new Try $2, null, null, $4
o 'TRY Block Catch FINALLY Block', -> new Try $2, $3[0], $3[1], $5
- ]
A catch clause names its error and runs a block of code.
Catch: [
+ ]
A catch clause names its error and runs a block of code.
Catch: [
o 'CATCH Identifier Block', -> [$2, $3]
- ]
Throw an exception object.
Throw: [
+ ]
Throw an exception object.
Throw: [
o 'THROW Expression', -> new Throw $2
]
Parenthetical expressions. Note that the Parenthetical is a Value, not an Expression, so if you need to use an expression in a place where only values are accepted, wrapping it in parentheses will always do -the trick.
Parenthetical: [
+the trick.
Parenthetical: [
o '( Body )', -> new Parens $2
o '( INDENT Body OUTDENT )', -> new Parens $3
- ]
The condition portion of a while loop.
WhileSource: [
+ ]
The condition portion of a while loop.
WhileSource: [
o 'WHILE Expression', -> new While $2
- o 'WHILE Expression WHEN Expression', -> new While $2, guard: $4
- o 'UNTIL Expression', -> new While $2, invert: true
- o 'UNTIL Expression WHEN Expression', -> new While $2, invert: true, guard: $4
+ o 'WHILE Expression WHEN Expression', -> new While $2, guard: $4
+ o 'UNTIL Expression', -> new While $2, invert: true
+ o 'UNTIL Expression WHEN Expression', -> new While $2, invert: true, guard: $4
]
The while loop can either be normal, with a block of expressions to execute, -or postfix, with a single expression. There is no do..while.
While: [
+or postfix, with a single expression. There is no do..while.
While: [
o 'WhileSource Block', -> $1.addBody $2
o 'Statement WhileSource', -> $2.addBody Block.wrap [$1]
o 'Expression WhileSource', -> $2.addBody Block.wrap [$1]
o 'Loop', -> $1
]
- Loop: [
+ Loop: [
o 'LOOP Block', -> new While(new Literal 'true').addBody $2
o 'LOOP Expression', -> new While(new Literal 'true').addBody Block.wrap [$2]
]
Array, object, and range comprehensions, at the most generic level. Comprehensions can either be normal, with a block of expressions to execute, -or postfix, with a single expression.
For: [
+or postfix, with a single expression.
For: [
o 'Statement ForBody', -> new For $1, $2
o 'Expression ForBody', -> new For $1, $2
o 'ForBody Block', -> new For $2, $1
]
- ForBody: [
- o 'FOR Range', -> source: new Value($2)
+ ForBody: [
+ o 'FOR Range', -> source: new Value($2)
o 'ForStart ForSource', -> $2.own = $1.own; $2.name = $1[0]; $2.index = $1[1]; $2
]
- ForStart: [
+ ForStart: [
o 'FOR ForVariables', -> $2
o 'FOR OWN ForVariables', -> $3.own = yes; $3
]
An array of all accepted values for a variable inside the loop. -This enables support for pattern matching.
ForValue: [
+This enables support for pattern matching.
ForValue: [
o 'Identifier'
o 'Array', -> new Value $1
o 'Object', -> new Value $1
]
An array or range comprehension has variables for the current element and (optional) reference to the current index. Or, key, value, in the case -of object comprehensions.
ForVariables: [
+of object comprehensions.
ForVariables: [
o 'ForValue', -> [$1]
o 'ForValue , ForValue', -> [$1, $3]
]
The source of a comprehension is an array or object with an optional guard clause. If it's an array comprehension, you can also choose to step through -in fixed-size increments.
ForSource: [
- o 'FORIN Expression', -> source: $2
- o 'FOROF Expression', -> source: $2, object: yes
- o 'FORIN Expression WHEN Expression', -> source: $2, guard: $4
- o 'FOROF Expression WHEN Expression', -> source: $2, guard: $4, object: yes
- o 'FORIN Expression BY Expression', -> source: $2, step: $4
- o 'FORIN Expression WHEN Expression BY Expression', -> source: $2, guard: $4, step: $6
- o 'FORIN Expression BY Expression WHEN Expression', -> source: $2, step: $4, guard: $6
+in fixed-size increments.
ForSource: [
+ o 'FORIN Expression', -> source: $2
+ o 'FOROF Expression', -> source: $2, object: yes
+ o 'FORIN Expression WHEN Expression', -> source: $2, guard: $4
+ o 'FOROF Expression WHEN Expression', -> source: $2, guard: $4, object: yes
+ o 'FORIN Expression BY Expression', -> source: $2, step: $4
+ o 'FORIN Expression WHEN Expression BY Expression', -> source: $2, guard: $4, step: $6
+ o 'FORIN Expression BY Expression WHEN Expression', -> source: $2, step: $4, guard: $6
]
- Switch: [
+ Switch: [
o 'SWITCH Expression INDENT Whens OUTDENT', -> new Switch $2, $4
o 'SWITCH Expression INDENT Whens ELSE Block OUTDENT', -> new Switch $2, $4, $6
o 'SWITCH INDENT Whens OUTDENT', -> new Switch null, $3
o 'SWITCH INDENT Whens ELSE Block OUTDENT', -> new Switch null, $3, $5
]
- Whens: [
+ Whens: [
o 'When'
o 'Whens When', -> $1.concat $2
- ]
An individual When clause, with action.
When: [
+ ]
An individual When clause, with action.
When: [
o 'LEADING_WHEN SimpleArgs Block', -> [[$2, $3]]
o 'LEADING_WHEN SimpleArgs Block TERMINATOR', -> [[$2, $3]]
]
The most basic form of if is a condition and an action. The following if-related rules are broken up along these lines in order to avoid -ambiguity.
IfBlock: [
- o 'IF Expression Block', -> new If $2, $3, type: $1
- o 'IfBlock ELSE IF Expression Block', -> $1.addElse new If $4, $5, type: $3
+ambiguity.
IfBlock: [
+ o 'IF Expression Block', -> new If $2, $3, type: $1
+ o 'IfBlock ELSE IF Expression Block', -> $1.addElse new If $4, $5, type: $3
]
The full complement of if expressions, including postfix one-liner -if and unless.
If: [
+if and unless.
If: [
o 'IfBlock'
o 'IfBlock ELSE Block', -> $1.addElse $3
- o 'Statement POST_IF Expression', -> new If $3, Block.wrap([$1]), type: $2, statement: true
- o 'Expression POST_IF Expression', -> new If $3, Block.wrap([$1]), type: $2, statement: true
+ o 'Statement POST_IF Expression', -> new If $3, Block.wrap([$1]), type: $2, statement: true
+ o 'Expression POST_IF Expression', -> new If $3, Block.wrap([$1]), type: $2, statement: true
]
Arithmetic and logical operators, working on one or more operands. Here they are grouped by order of precedence. The actual precedence rules are defined at the bottom of the page. It would be shorter if we could combine most of these rules into a single generic Operand OpSymbol Operand -type rule, but in order to make the precedence binding possible, separate -rules are necessary.
Operation: [
+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 '- Expression', (-> new Op '-', $2), prec: 'UNARY'
+ o '+ Expression', (-> new Op '+', $2), prec: 'UNARY'
o '-- SimpleAssignable', -> new Op '--', $2
o '++ SimpleAssignable', -> new Op '++', $2
@@ -394,9 +396,9 @@ as "tokens".
exports.parser = new Parser
- tokens : tokens.join ' '
- bnf : grammar
- operators : operators.reverse()
- startSymbol : 'Root'
+ tokens : tokens.join ' '
+ bnf : grammar
+ operators : operators.reverse()
+ startSymbol : 'Root'