diff --git a/LICENSE b/LICENSE index 8cb3cfda..dbe6b4e3 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2011 Jeremy Ashkenas +Copyright (c) 2009-2012 Jeremy Ashkenas Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/documentation/coffee/block_comment.coffee b/documentation/coffee/block_comment.coffee index 31ca0bd5..dbeb59a0 100644 --- a/documentation/coffee/block_comment.coffee +++ b/documentation/coffee/block_comment.coffee @@ -1,5 +1,5 @@ ### -CoffeeScript Compiler v1.2.0 +CoffeeScript Compiler v1.3.0 Released under the MIT License ### diff --git a/documentation/coffee/conditionals.coffee b/documentation/coffee/conditionals.coffee index 9ba3bce1..3cff4e2d 100644 --- a/documentation/coffee/conditionals.coffee +++ b/documentation/coffee/conditionals.coffee @@ -8,6 +8,5 @@ else date = if friday then sue else jill -options or= defaults diff --git a/documentation/coffee/existence.coffee b/documentation/coffee/existence.coffee index a42dd668..0e6a585d 100644 --- a/documentation/coffee/existence.coffee +++ b/documentation/coffee/existence.coffee @@ -1,6 +1,7 @@ solipsism = true if mind? and not world? -speed ?= 75 +speed = 0 +speed ?= 15 footprints = yeti ? "bear" diff --git a/documentation/docs/browser.html b/documentation/docs/browser.html index 589b546f..67d03f82 100644 --- a/documentation/docs/browser.html +++ b/documentation/docs/browser.html @@ -1,5 +1,6 @@
browser.coffee | ||||
---|---|---|---|---|
Override exported methods for non-Node.js engines. | CoffeeScript = require './coffee-script'
-CoffeeScript.require = require | |||
Use standard JavaScript | CoffeeScript.eval = (code, options) ->
+CoffeeScript.require = require | |||
Use standard JavaScript | CoffeeScript.eval = (code, options = {}) ->
+ options.bare ?= on
eval CoffeeScript.compile code, options | |||
Running code does not provide access to this scope. | CoffeeScript.run = (code, options = {}) ->
options.bare = on
Function(CoffeeScript.compile code, options)() | |||
If we're not in a browser environment, we're finished with the public API. | return unless window? | |||
Load a remote script from the current domain via XHR. | CoffeeScript.load = (url, callback) ->
diff --git a/documentation/docs/cake.html b/documentation/docs/cake.html
index 69049f64..a5e4011d 100644
--- a/documentation/docs/cake.html
+++ b/documentation/docs/cake.html
@@ -25,7 +25,7 @@ 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
+ args = process.argv[2..]
CoffeeScript.run fs.readFileSync('Cakefile').toString(), filename: 'Cakefile'
oparse = new optparse.OptionParser switches
return printTasks() unless args.length
@@ -34,7 +34,8 @@ original directory name, when running Cake tasks from subdirectories.
catch e
return fatalError "#{e}"
invoke arg for arg in options.arguments | ||
Display the list of Cake tasks in a format similar to | printTasks = ->
- cakefilePath = path.join path.relative(__originalDirname, process.cwd()), 'Cakefile'
+ relative = path.relative or path.resolve
+ cakefilePath = path.join relative(__originalDirname, process.cwd()), 'Cakefile'
console.log "#{cakefilePath} defines the following tasks:\n"
for name, task of tasks
spaces = 20 - name.length
diff --git a/documentation/docs/coffee-script.html b/documentation/docs/coffee-script.html
index bdf5cb31..5ce3816f 100644
--- a/documentation/docs/coffee-script.html
+++ b/documentation/docs/coffee-script.html
@@ -13,14 +13,17 @@ execute all scripts present in | |||
The current CoffeeScript version number. | exports.VERSION = '1.2.0' | |||
Words that cannot be used as identifiers in CoffeeScript code | exports.RESERVED = RESERVED | |||
Expose helpers for testing. | exports.helpers = require './helpers' | |||
Compile a string of CoffeeScript code to JavaScript, using the Coffee/Jison + require.registerExtension '.coffee', (content) -> compile content | ||||
The current CoffeeScript version number. | exports.VERSION = '1.3.0' | |||
Words that cannot be used as identifiers in CoffeeScript code | exports.RESERVED = RESERVED | |||
Expose helpers for testing. | exports.helpers = require './helpers' | |||
Compile a string of CoffeeScript code to JavaScript, using the Coffee/Jison compiler. | exports.compile = compile = (code, options = {}) ->
{merge} = exports.helpers
try
- (parser.parse lexer.tokenize code).compile merge {}, options
+ js = (parser.parse lexer.tokenize code).compile options
+ return js unless options.header
catch err
err.message = "In #{options.filename}, #{err.message}" if options.filename
- throw err | |||
Tokenize a string of CoffeeScript code, and return the array of tokens. | exports.tokens = (code, options) ->
+ throw err
+ header = "Generated by CoffeeScript #{@VERSION}"
+ "// #{header}\n#{js}" | |||
Tokenize a string of CoffeeScript code, and return the array of tokens. | exports.tokens = (code, options) ->
lexer.tokenize code, options | |||
Parse a string of CoffeeScript code or an array of lexed tokens, and
return the AST. You can then compile it by calling | exports.nodes = (source, options) ->
@@ -28,9 +31,9 @@ or traverse it by using | |||
Compile and execute a string of CoffeeScript (on the server), correctly
-setting | exports.run = (code, options) ->
+setting | exports.run = (code, options = {}) ->
mainModule = require.main | ||
Set the filename. | mainModule.filename = process.argv[1] =
- if options.filename then fs.realpathSync(options.filename) else '.' | |||
Clear the module cache. | mainModule.moduleCache and= {} | |||
Assign paths for node_modules loading | mainModule.paths = require('module')._nodeModulePaths path.dirname options.filename | |||
Compile. | if path.extname(mainModule.filename) isnt '.coffee' or require.extensions
+ if options.filename then fs.realpathSync(options.filename) else '.' | |||
Clear the module cache. | mainModule.moduleCache and= {} | |||
Assign paths for node_modules loading | mainModule.paths = require('module')._nodeModulePaths path.dirname fs.realpathSync options.filename | |||
Compile. | if path.extname(mainModule.filename) isnt '.coffee' or require.extensions
mainModule._compile compile(code, options), mainModule.filename
else
mainModule._compile code, mainModule.filename | |||
Compile and evaluate a string of CoffeeScript (in a Node.js-like environment). diff --git a/documentation/docs/command.html b/documentation/docs/command.html index 61843ed3..46bcbb8a 100644 --- a/documentation/docs/command.html +++ b/documentation/docs/command.html @@ -11,11 +11,13 @@ interactive REPL. | {EventEmitter} = require 'events' | |||
Allow CoffeeScript to emit Node.js events. | helpers.extend CoffeeScript, new EventEmitter
printLine = (line) -> process.stdout.write line + '\n'
-printWarn = (line) -> process.stderr.write line + '\n' | |||
The help banner that is printed when | BANNER = '''
- Usage: coffee [options] path/to/script.coffee
+printWarn = (line) -> process.stderr.write line + '\n'
+
+hidden = (file) -> /^\.|~$/.test file | |||
The help banner that is printed when | BANNER = '''
+ Usage: coffee [options] path/to/script.coffee -- [args]
If called without options, `coffee` will run your script.
- ''' | |||
The list of all the valid option flags that | SWITCHES = [
+''' | |||
The list of all the valid option flags that | SWITCHES = [
['-b', '--bare', 'compile without a top-level function wrapper']
['-c', '--compile', 'compile to JavaScript and save as .js files']
['-e', '--eval', 'pass a string from the command line as input']
@@ -47,13 +49,12 @@ Many flags cause us to divert before compiling anything. Flags passed after
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 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
- if opts.run
- opts.literals = sources.splice(1).concat opts.literals
- process.argv = process.argv.slice(0, 2).concat opts.literals
+ 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
@@ -75,11 +76,11 @@ and all subdirectories. |
fs.readdir source, (err, files) ->
throw err if err and err.code isnt 'ENOENT'
return if err?.code is 'ENOENT'
- files = files.map (file) -> path.join source, file
index = sources.indexOf source
- sources[index..index] = files
+ sources[index..index] = (path.join source, file for file in files)
sourceCode[index..index] = files.map -> null
- compilePath file, no, base for file in files
+ for file in files when not hidden file
+ compilePath (path.join source, file), no, base
else if topLevel or path.extname(source) is '.coffee'
watch source, base if opts.watch
fs.readFile source, (err, code) ->
@@ -111,7 +112,7 @@ requested options. If evaluating the script directly sets __filename catch err
CoffeeScript.emit 'failure', err, task
return if CoffeeScript.listeners('failure').length
- return printLine err.message if o.watch
+ return printLine err.message + '\x07' if o.watch
printWarn err instanceof Error and err.stack or "ERROR: #{err}"
process.exit 1 | ||
Attach the appropriate listeners to compile scripts incoming over stdin, and write them back to stdout. | compileStdio = ->
@@ -141,8 +142,12 @@ such as |
watchErr = (e) ->
if e.code is 'ENOENT'
return if sources.indexOf(source) is -1
- removeSource source, base, yes
- compileJoin()
+ try
+ rewatch()
+ compile()
+ catch e
+ removeSource source, base, yes
+ compileJoin()
else throw e
compile = ->
@@ -150,32 +155,22 @@ such as |||
Watch a directory of files for new additions. | watchDir = (source, base) ->
+ watcher = fs.watch source, compile
+ catch e
+ watchErr e
+
+ rewatch = ->
+ watcher?.close()
+ watcher = fs.watch source, compile | |||
Watch a directory of files for new additions. | watchDir = (source, base) ->
readdirTimeout = null
try
watcher = fs.watch source, ->
@@ -186,8 +181,8 @@ such as |
throw err unless err.code is 'ENOENT'
watcher.close()
return unwatchDir source, base
- files = files.map (file) -> path.join source, file
- for file in files when not notSources[file]
+ for file in files when not hidden(file) and not notSources[file]
+ file = path.join source, file
continue if sources.some (s) -> s.indexOf(file) >= 0
sources.push file
sourceCode.push null
@@ -196,7 +191,7 @@ such as |||
Convenience for cleaner setTimeouts. | wait = (milliseconds, func) -> setTimeout func, milliseconds | |||
When watching scripts, it's useful to log changes with the timestamp. | timeLog = (message) ->
+ if exists then compile() else exec "mkdir -p #{jsDir}", compile | |||
Convenience for cleaner setTimeouts. | wait = (milliseconds, func) -> setTimeout func, milliseconds | |||
When watching scripts, it's useful to log changes with the timestamp. | timeLog = (message) ->
console.log "#{(new Date).toLocaleTimeString()} - #{message}" | |||
Pipe compiled JS through JSLint (requires a working | lint = (file, js) ->
printIt = (buffer) -> printLine file + ':\t' + buffer.toString().trim()
@@ -246,13 +240,14 @@ any errors or warnings that arise. | printLine strings.join(' ') | ||
Use the OptionParser module to extract all options from
| parseOptions = ->
optionParser = new optparse.OptionParser SWITCHES, BANNER
- o = opts = optionParser.parse process.argv.slice 2
+ o = opts = optionParser.parse process.argv[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
sourceCode[i] = null for source, i in sources
- return | |||
The compile-time options to pass to the CoffeeScript compiler. | compileOptions = (filename) -> {filename, bare: opts.bare} | |||
Start up a new Node.js instance with the arguments in | ||||
The compile-time options to pass to the CoffeeScript compiler. | compileOptions = (filename) ->
+ {filename, bare: opts.bare, header: opts.compile} | |||
Start up a new Node.js instance with the arguments in | forkNode = ->
nodeArgs = opts.nodejs.split /\s+/
args = process.argv[1..]
diff --git a/documentation/docs/grammar.html b/documentation/docs/grammar.html
index 78e79454..6ae83158 100644
--- a/documentation/docs/grammar.html
+++ b/documentation/docs/grammar.html
@@ -215,6 +215,7 @@ and optional references to the superclass. | 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 + o 'RangeDots', -> new Range null, null, $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: [
@@ -348,7 +349,7 @@ rules are necessary. |
o 'Expression LOGIC Expression', -> new Op $2, $1, $3
o 'Expression RELATION Expression', ->
if $2.charAt(0) is '!'
- new Op($2.slice(1), $1, $3).invert()
+ new Op($2[1..], $1, $3).invert()
else
new Op $2, $1, $3
@@ -381,7 +382,7 @@ down. Following these rules is what makes 2 + 3 * 4 parse as:
['nonassoc', 'INDENT', 'OUTDENT']
['right', '=', ':', 'COMPOUND_ASSIGN', 'RETURN', 'THROW', 'EXTENDS']
['right', 'FORIN', 'FOROF', 'BY', 'WHEN']
- ['right', 'IF', 'ELSE', 'FOR', 'DO', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS']
+ ['right', 'IF', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS']
['right', 'POST_IF']
] | ||
Wrapping Up | | |||
Finally, now that we have our grammar and our operators, we can create our Jison.Parser. We do this by processing all of our rules, recording all diff --git a/documentation/docs/lexer.html b/documentation/docs/lexer.html index 5322eb63..20f5589d 100644 --- a/documentation/docs/lexer.html +++ b/documentation/docs/lexer.html @@ -32,7 +32,7 @@ unless explicitly asked not to. | @tokens = [] # Stream of parsed tokens in the form `['TYPE', value, line]`. | |||
At every position, run through this list of attempted matches,
short-circuiting if any of them succeed. Their order determines precedence:
| i = 0
- while @chunk = code.slice i
+ while @chunk = code[i..]
i += @identifierToken() or
@commentToken() or
@whitespaceToken() or
@@ -84,7 +84,7 @@ though |
@tokens.pop()
id = '!' + id
- if id in ['eval', 'arguments'].concat JS_FORBIDDEN
+ if id in JS_FORBIDDEN
if forcedIdentifier
tag = 'IDENTIFIER'
id = new String id
@@ -108,9 +108,19 @@ though numberToken: ->
return 0 unless match = NUMBER.exec @chunk
number = match[0]
+ if /^0[BOX]/.test number
+ @error "radix prefix '#{number}' must be lowercase"
+ else if /E/.test(number) and not /^0x/.test number
+ @error "exponential notation '#{number}' must be indicated with a lowercase 'e'"
+ else if /^0\d*[89]/.test number
+ @error "decimal literal '#{number}' must not be prefixed with '0'"
+ else if /^0\d+/.test number
+ @error "octal literal '#{number}' must be prefixed with '0o'"
lexedLength = number.length
- if binaryLiteral = /0b([01]+)/.exec number
- number = (parseInt binaryLiteral[1], 2).toString()
+ if octalLiteral = /^0o([0-7]+)/.exec number
+ number = '0x' + (parseInt octalLiteral[1], 8).toString 16
+ if binaryLiteral = /^0b([01]+)/.exec number
+ number = '0x' + (parseInt binaryLiteral[1], 2).toString 16
@token 'NUMBER', number
lexedLength | ||
Matches strings, including multi-line strings. Ensures that quotation marks are balanced within the string's contents, and within nested interpolations. | stringToken: ->
@@ -121,11 +131,13 @@ are balanced within the string's contents, and within nested interpolations.
when '"'
return 0 unless string = @balancedString @chunk, '"'
if 0 < string.indexOf '#{', 1
- @interpolateString string.slice 1, -1
+ @interpolateString string[1...-1]
else
@token 'STRING', @escapeLines string
else
return 0
+ if octalEsc = /^(?:\\.|[^\\])*\\[0-7]/.test string
+ @error "octal escape sequences #{string} are not allowed"
@line += count string, '\n'
string.length | |||
Matches heredocs, adjusting indentation to the correct level, as heredocs preserve whitespace, but ignore indentation to the left. | heredocToken: ->
@@ -144,11 +156,10 @@ preserve whitespace, but ignore indentation to the left. |
if here
@token 'HERECOMMENT', @sanitizeHeredoc here,
herecomment: true, indent: Array(@indent + 1).join(' ')
- @token 'TERMINATOR', '\n'
@line += count comment, '\n'
comment.length |||
Matches JavaScript interpolated directly into the source via backticks. | jsToken: ->
return 0 unless @chunk.charAt(0) is '`' and match = JSTOKEN.exec @chunk
- @token 'JS', (script = match[0]).slice 1, -1
+ @token 'JS', (script = match[0])[1...-1]
script.length | |||
Matches regular expression literals. Lexing regular expressions is difficult to distinguish from division, so we borrow some basic heuristics from JavaScript and Ruby. | regexToken: ->
@@ -275,9 +286,9 @@ parentheses that indicate a method call from regular parentheses, and so on.
prev[0] = 'COMPOUND_ASSIGN'
prev[1] += '='
return value.length
- if value is ';'
- @seenFor = no
- tag = 'TERMINATOR'
+ if value is ';'
+ @seenFor = no
+ tag = 'TERMINATOR'
else if value in MATH then tag = 'MATH'
else if value in COMPARE then tag = 'COMPARE'
else if value in COMPOUND_ASSIGN then tag = 'COMPOUND_ASSIGN'
@@ -332,22 +343,26 @@ parameters specially in order to make things easier for the parser.
a series of delimiters, all of which must be nested correctly within the
contents of the string. This method allows us to have strings within
interpolations within strings, ad infinitum. | balancedString: (str, end) ->
+ continueCount = 0
stack = [end]
for i in [1...str.length]
+ if continueCount
+ --continueCount
+ continue
switch letter = str.charAt i
when '\\'
- i++
+ ++continueCount
continue
when end
stack.pop()
unless stack.length
- return str.slice 0, i + 1
+ return str[0..i]
end = stack[stack.length - 1]
continue
if end is '}' and letter in ['"', "'"]
stack.push end = letter
- else if end is '}' and letter is '/' and match = (HEREGEX.exec(str.slice i) or REGEX.exec(str.slice i))
- i += match[0].length - 1
+ else if end is '}' and letter is '/' and match = (HEREGEX.exec(str[i..]) or REGEX.exec(str[i..]))
+ continueCount += match[0].length - 1
else if end is '}' and letter is '{'
stack.push end = '}'
else if end is '"' and prev is '#' and letter is '{'
@@ -371,10 +386,10 @@ token stream. | i += 1 continue unless letter is '#' and str.charAt(i+1) is '{' and - (expr = @balancedString str.slice(i + 1), '}') + (expr = @balancedString str[i + 1..], '}') continue - tokens.push ['NEOSTRING', str.slice(pi, i)] if pi < i - inner = expr.slice(1, -1) + tokens.push ['NEOSTRING', str[pi...i]] if pi < i + inner = expr[1...-1] if inner.length nested = new Lexer().tokenize inner, line: @line, rewrite: off nested.pop() @@ -386,7 +401,7 @@ token stream. | tokens.push ['TOKENS', nested] i += expr.length pi = i + 1 - tokens.push ['NEOSTRING', str.slice pi] if i > pi < str.length + tokens.push ['NEOSTRING', str[pi..]] if i > pi < str.length return tokens if regex return @token 'STRING', '""' unless tokens.length tokens.unshift ['', ''] unless tokens[0][0] is 'NEOSTRING' @@ -414,14 +429,13 @@ correctly balanced throughout the course of the token stream. (tok = last @tokens, index) and if val then tok[1] = val else tok[1] |
Are we in the midst of an unfinished expression? | unfinished: ->
LINE_CONTINUER.test(@chunk) or
@tag() in ['\\', '.', '?.', 'UNARY', 'MATH', '+', '-', 'SHIFT', 'RELATION'
- 'COMPARE', 'LOGIC', 'COMPOUND_ASSIGN', 'THROW', 'EXTENDS'] | |||
Converts newlines for string literals. | escapeLines: (str, heredoc) ->
+ 'COMPARE', 'LOGIC', 'THROW', 'EXTENDS'] | |||
Converts newlines for string literals. | escapeLines: (str, heredoc) ->
str.replace MULTILINER, if heredoc then '\\n' else '' | |||
Constructs a string token by escaping quotes and newlines. | makeString: (body, quote, heredoc) ->
return quote + quote unless body
body = body.replace /\\([\s\S])/g, (match, contents) ->
if contents in ['\n', quote] then contents else match
body = body.replace /// #{quote} ///g, '\\$&'
- quote + @escapeLines(body, heredoc) + quote
- | |||
Throws a syntax error on the current | error: (message) ->
+ quote + @escapeLines(body, heredoc) + quote | |||
Throws a syntax error on the current | error: (message) ->
throw SyntaxError "#{message} on line #{ @line + 1}" | |||
Constants | | |||
Keywords that CoffeeScript shares in common with JavaScript. | JS_KEYWORDS = [
'true', 'false', 'null', 'this'
'new', 'delete', 'typeof', 'in', 'instanceof'
@@ -448,17 +462,23 @@ to avoid having a JavaScript error at runtime. |
'case', 'default', 'function', 'var', 'void', 'with'
'const', 'let', 'enum', 'export', 'import', 'native'
'__hasProp', '__extends', '__slice', '__bind', '__indexOf'
-] |||
The superset of both JavaScript keywords and reserved words, none of which may -be used as identifiers or properties. | JS_FORBIDDEN = JS_KEYWORDS.concat RESERVED
+ 'implements', 'interface', 'let', 'package',
+ 'private', 'protected', 'public', 'static', 'yield'
+]
-exports.RESERVED = RESERVED.concat(JS_KEYWORDS).concat(COFFEE_KEYWORDS) | |||
Token matching regexes. | IDENTIFIER = /// ^
+STRICT_PROSCRIBED = ['arguments', 'eval'] | |||
The superset of both JavaScript keywords and reserved words, none of which may +be used as identifiers or properties. | JS_FORBIDDEN = JS_KEYWORDS.concat(RESERVED).concat(STRICT_PROSCRIBED)
+
+exports.RESERVED = RESERVED.concat(JS_KEYWORDS).concat(COFFEE_KEYWORDS).concat(STRICT_PROSCRIBED)
+exports.STRICT_PROSCRIBED = STRICT_PROSCRIBED | |||
Token matching regexes. | IDENTIFIER = /// ^
( [$A-Za-z_\x7f-\uffff][$\w\x7f-\uffff]* )
( [^\n\S]* : (?!:) )? # Is this a property name?
///
NUMBER = ///
- ^ 0x[\da-f]+ | # hex
- ^ 0b[01]+ | # binary
+ ^ 0b[01]+ | # binary
+ ^ 0o[0-7]+ | # octal
+ ^ 0x[\da-f]+ | # hex
^ \d*\.?\d+ (?:e[+-]?\d+)? # decimal
///i
diff --git a/documentation/docs/nodes.html b/documentation/docs/nodes.html
index 4a545e9b..69533b5b 100644
--- a/documentation/docs/nodes.html
+++ b/documentation/docs/nodes.html
@@ -2,7 +2,7 @@
nodes are created as the result of actions in the grammar,
but some are created by other nodes as a method of code generation. To convert
the syntax tree into a string of JavaScript code, call | {Scope} = require './scope'
-{RESERVED} = require './lexer' | ||
Import the helpers we plan to use. | {compact, flatten, extend, merge, del, starts, ends, last} = require './helpers'
+{RESERVED, STRICT_PROSCRIBED} = require './lexer' | |||
Import the helpers we plan to use. | {compact, flatten, extend, merge, del, starts, ends, last} = require './helpers'
exports.extend = extend # for parser | |||
Constant functions for nodes that don't need customization. | YES = -> yes
NO = -> no
@@ -147,7 +147,10 @@ our own | else if top
node.front = true
code = node.compile o
- codes.push if node.isStatement o then code else "#{@tab}#{code};"
+ unless node.isStatement o
+ code = "#{@tab}#{code};"
+ code = "#{code}\n" if node instanceof Literal
+ codes.push code
else
codes.push node.compile o, LEVEL_LIST
if top
@@ -232,7 +235,7 @@ JavaScript without translation, such as: strings, numbers,
if o.level >= LEVEL_ACCESS then '(void 0)' else 'void 0'
else if @value is 'this'
if o.scope.method?.bound then o.scope.method.context else @value
- else if @value.reserved and "#{@value}" not in ['eval', 'arguments']
+ else if @value.reserved
"\"#{@value}\""
else
@value
@@ -273,6 +276,7 @@ or vanilla. | | makeReturn: THIS compileNode: (o, level) -> - code = '/*' + multident(@comment, @tab) + "\n#{@tab}*/" + code = '/*' + multident(@comment, @tab) + "\n#{@tab}*/\n" code = o.indent + code if (level or o.level) is LEVEL_TOP code |
Call | | |||
Node for a function invocation. Takes care of converting | exports.Call = class Call extends Base
@@ -429,9 +433,9 @@ inner constructor in order to be able to pass the varargs. |
return """
(function(func, args, ctor) {
#{idt}ctor.prototype = func.prototype;
- #{idt}var child = new ctor, result = func.apply(child, args);
- #{idt}return typeof result === "object" ? result : child;
- #{@tab}})(#{ @variable.compile o, LEVEL_LIST }, #{splatArgs}, function() {})
+ #{idt}var child = new ctor, result = func.apply(child, args), t = typeof result;
+ #{idt}return t == "object" || t == "function" ? result || child : child;
+ #{@tab}})(#{ @variable.compile o, LEVEL_LIST }, #{splatArgs}, function(){})
"""
base = new Value @variable
if (name = base.properties.pop()) and base.isComplex()
@@ -492,6 +496,8 @@ needed to iterate over the values in the range. Used by comprehensions.
@compileVariables o unless @fromVar
return @compileArray(o) unless o.index |||
Set up endpoints. | known = @fromNum and @toNum
idx = del o, 'index'
+ idxName = del o, 'name'
+ namedIndex = idxName and idxName isnt idx
varPart = "#{idx} = #{@fromC}"
varPart += ", #{@toC}" if @toC isnt @toVar
varPart += ", #{@step}" if @step isnt @stepVar
@@ -505,9 +511,18 @@ needed to iterate over the values in the range. Used by comprehensions.
"#{cond} ? #{lt} #{@toVar} : #{gt} #{@toVar}" | |||
Generate the step. | stepPart = if @stepVar
"#{idx} += #{@stepVar}"
else if known
- if from <= to then "#{idx}++" else "#{idx}--"
+ if namedIndex
+ if from <= to then "++#{idx}" else "--#{idx}"
+ else
+ if from <= to then "#{idx}++" else "#{idx}--"
else
- "#{cond} ? #{idx}++ : #{idx}--" | |||
The final loop body. | "#{varPart}; #{condPart}; #{stepPart}" | |||
When used as a value, expand the range into the equivalent array. | compileArray: (o) ->
+ if namedIndex
+ "#{cond} ? ++#{idx} : --#{idx}"
+ else
+ "#{cond} ? #{idx}++ : #{idx}--"
+
+ varPart = "#{idxName} = #{varPart}" if namedIndex
+ stepPart = "#{idxName} = #{stepPart}" if namedIndex | |||
The final loop body. | "#{varPart}; #{condPart}; #{stepPart}" | |||
When used as a value, expand the range into the equivalent array. | compileArray: (o) ->
if @fromNum and @toNum and Math.abs(@fromNum - @toNum) <= 20
range = [+@fromNum..+@toNum]
range.pop() if @exclusive
@@ -538,13 +553,14 @@ is the index of the beginning. | compileNode: (o) ->
{to, from} = @range
fromStr = from and from.compile(o, LEVEL_PAREN) or '0'
- compiled = to and to.compile o, LEVEL_ACCESS
+ compiled = to and to.compile o, LEVEL_PAREN
if to and not (not @range.exclusive and +compiled is -1)
toStr = ', ' + if @range.exclusive
compiled
else if SIMPLENUM.test compiled
- (+compiled + 1).toString()
+ "#{+compiled + 1}"
else
+ compiled = to.compile o, LEVEL_ACCESS
"#{compiled} + 1 || 9e9"
".slice(#{ fromStr }#{ toStr or '' })" | ||
Obj | | |||
An object literal, nothing fancy. | exports.Obj = class Obj extends Base
constructor: (props, @generated = false) ->
@@ -554,6 +570,14 @@ is the index of the beginning. | compileNode: (o) -> props = @properties + propNames = [] + for prop in @properties + prop = prop.variable if prop.isComplex() + if prop? + propName = prop.unwrapAll().value.toString() + if propName in propNames + throw SyntaxError "multiple object literal properties named \"#{propName}\"" + propNames.push propName return (if @front then '({})' else '{}') unless props.length if @generated for node in props when node instanceof Value @@ -615,6 +639,8 @@ list of prototype property assignments. | tail instanceof Access and tail.name.value else @variable.base.value + if decl in STRICT_PROSCRIBED + throw SyntaxError "variable name may not be #{decl}" decl and= IDENTIFIER.test(decl) and decl | |
For all | setContext: (name) ->
@body.traverseChildren false, (node) ->
@@ -630,7 +656,7 @@ constructor. | < lhs = (new Value (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 on the class. | addProperties: (node, name, o) ->
- props = node.base.properties[0..]
+ props = node.base.properties[..]
exprs = while assign = props.shift()
if assign instanceof Assign
base = assign.variable.base
@@ -664,46 +690,62 @@ on the class. | for node, i in exps = child.expressions if node instanceof Value and node.isObject(true) exps[i] = @addProperties node, name, o - child.expressions = exps = flatten exps | ||
Make sure that a constructor is defined for the class, and properly + child.expressions = exps = flatten exps | ||||
| hoistDirectivePrologue: ->
+ index = 0
+ {expressions} = @body
+ ++index while (node = expressions[index]) and node instanceof Comment or
+ node instanceof Value and node.isString()
+ @directives = expressions.splice 0, index | |||
Make sure that a constructor is defined for the class, and properly configured. | ensureConstructor: (name) ->
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 | |||
Instead of generating the JavaScript string directly, we build up the + @ctor.noReturn = yes | ||||
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. | compileNode: (o) ->
decl = @determineName()
- name = decl or @name or '_Class'
+ name = decl or '_Class'
name = "_#{name}" if name.reserved
lname = new Literal name
+ @hoistDirectivePrologue()
@setContext name
@walkBody name, o
@ensureConstructor name
@body.spaced = yes
@body.expressions.unshift @ctor unless @ctor instanceof Code
+ if decl
+ @body.expressions.unshift new Assign (new Value (new Literal name), [new Access new Literal 'name']), (new Literal "'#{name}'")
@body.expressions.push lname
+ @body.expressions.unshift @directives...
@addBoundFunctions o
call = Closure.wrap @body
-
+
if @parent
@superClass = new Literal o.scope.freeVariable 'super', no
@body.expressions.unshift new Extends lname, @superClass
call.args.push @parent
- call.variable.params.push new Param @superClass
-
+ params = call.variable.params or call.variable.base.params
+ params.push new Param @superClass
+
klass = new Parens call, yes
klass = new Assign @variable, klass if @variable
- klass.compile o | |||
Assign | | |||
The Assign is used to assign a local variable to value, or to set the + klass.compile o | ||||
Assign | | |||
The Assign is used to assign a local variable to value, or to set the property of an object -- including within object literals. | exports.Assign = class Assign extends Base
constructor: (@variable, @value, @context, options) ->
@param = options and options.param
@subpattern = options and options.subpattern
+ forbidden = (name = @variable.unwrapAll().value) in STRICT_PROSCRIBED
+ if forbidden and @context isnt 'object'
+ throw SyntaxError "variable name may not be \"#{name}\""
children: ['variable', 'value']
@@ -714,7 +756,7 @@ property of an object -- including within object literals. |
@[if @context is 'object' then 'value' else 'variable'].assigns name
unfoldSoak: (o) ->
- unfoldSoak o, this, 'variable' |||
Compile an assignment, delegating to | ||||
Compile an assignment, delegating to | compileNode: (o) ->
@@ -737,7 +779,7 @@ has not been seen yet within the current scope, declare it. | val = @value.compile o, LEVEL_LIST
return "#{name}: #{val}" if @context is 'object'
val = name + " #{ @context or '=' } " + val
- if o.level <= LEVEL_LIST then val else "(#{val})" |||
Brief implementation of recursive pattern matching, when assigning array or + if o.level <= LEVEL_LIST then val else "(#{val})" | ||||
Brief implementation of recursive pattern matching, when assigning array or object literals to a value. Peeks at their properties to assign inner names. See the ECMAScript Harmony Wiki for details. | compilePatternMatch: (o) ->
@@ -748,7 +790,7 @@ for details. | < code = value.compile o return if o.level >= LEVEL_OP then "(#{code})" else code isObject = @variable.isObject() - if top and olen is 1 and (obj = objects[0]) not instanceof Splat | ||
Unroll simplest cases: | if obj instanceof Assign
+ if top and olen is 1 and (obj = objects[0]) not instanceof Splat | |||
Unroll simplest cases: | if obj instanceof Assign
{variable: {base: idx}, value: obj} = obj
else
if obj.base instanceof Parens
@@ -761,7 +803,7 @@ for details. | < acc = IDENTIFIER.test idx.unwrap().value or 0 value = new Value value value.properties.push new (if acc then Access else Index) idx - if obj.unwrap().value in ['arguments','eval'].concat RESERVED + if obj.unwrap().value in RESERVED throw new SyntaxError "assignment to a reserved word: #{obj.compile o} = #{value.compile o}" return new Assign(obj, value, null, param: @param).compile o, LEVEL_TOP vvar = value.compile o, LEVEL_LIST @@ -770,10 +812,10 @@ for details. | < if not IDENTIFIER.test(vvar) or @variable.assigns(vvar) assigns.push "#{ ref = o.scope.freeVariable 'ref' } = #{vvar}" vvar = ref - for obj, i in objects | |
A regular array pattern-match. | idx = i
+ for obj, i in objects | |||
A regular array pattern-match. | idx = i
if isObject
- if obj instanceof Assign | |||
A regular object pattern-match. | {variable: {base: idx}, value: obj} = obj
- else | |||
A shorthand | if obj.base instanceof Parens
+ if obj instanceof Assign | |||
A regular object pattern-match. | {variable: {base: idx}, value: obj} = obj
+ else | |||
A shorthand | if obj.base instanceof Parens
[obj, idx] = new Value(obj.unwrapAll()).cacheReference o
else
idx = if obj.this then obj.properties[0].name else obj
@@ -800,17 +842,18 @@ for details. | < 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] - if name? and name in ['arguments','eval'].concat RESERVED + 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 assigns.push vvar unless top or @subpattern code = assigns.join ', ' - if o.level < LEVEL_LIST then code else "(#{code})" | ||
When compiling a conditional assignment, take care to ensure that the + if o.level < LEVEL_LIST then code else "(#{code})" | ||||
When compiling a conditional assignment, take care to ensure that the operands are only evaluated once, even though we have to reference them more than once. | compileConditional: (o) ->
- [left, rite] = @variable.cacheReference o
+ [left, right] = @variable.cacheReference o | |||
Disallow conditional assignment of undefined variables. | if left.base instanceof Literal and 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[0...-1], left, new Assign(rite, @value, '=') ).compile o | |||
Compile the assignment from an array splice literal, using JavaScript's + new Op(@context[...-1], left, new Assign(right, @value, '=') ).compile o | ||||
Compile the assignment from an array splice literal, using JavaScript's
| compileSplice: (o) ->
{range: {from, to, exclusive}} = @variable.properties.pop()
name = @variable.compile o
@@ -826,7 +869,7 @@ more than once. | to = "9e9" [valDef, valRef] = @value.cache o, LEVEL_LIST code = "[].splice.apply(#{name}, [#{fromDecl}, #{to}].concat(#{valDef})), #{valRef}" - if o.level > LEVEL_TOP then "(#{code})" else code | ||
Code | | |||
A function definition. This is the only node that creates a new Scope. + if o.level > LEVEL_TOP then "(#{code})" else code | ||||
Code | | |||
A function definition. This is the only node that creates a new Scope. When for the purposes of walking the contents of a function body, the Code has no children -- they're within the inner scope. | exports.Code = class Code extends Base
constructor: (params, body, tag) ->
@@ -839,7 +882,7 @@ has no children -- they're within the inner scope. | isStatement: -> !!@ctor
- jumps: NO |||
Compilation creates a new scope unless explicitly asked to share with the + jumps: NO | ||||
Compilation creates a new scope unless explicitly asked to share with the
outer scope. Handles splat parameters in the parameter list by peeking at
the JavaScript | | | | |
Short-circuit | ||||
A list of parameter names, excluding those generated by the compiler. | paramNames: ->
+ names = []
+ names.push param.names()... for param in @params
+ names | |||
Short-circuit | traverseChildren: (crossScope, func) ->
- super(crossScope, func) if crossScope | |||
Param | | |||
A parameter in a function definition. Beyond a typical Javascript parameter, + super(crossScope, func) if crossScope | ||||
Param | | |||
A parameter in a function definition. Beyond a typical Javascript parameter, these parameters can also attach themselves to the context of the function, as well as be a splat, gathering up a group of parameters into an array. | exports.Param = class Param extends Base
constructor: (@name, @value, @splat) ->
+ if (name = @name.unwrapAll().value) in STRICT_PROSCRIBED
+ throw SyntaxError "parameter name \"#{name}\" is not allowed"
children: ['name', 'value']
@@ -901,7 +956,8 @@ as well as be a splat, gathering up a group of parameters into an array.
node = @name
if node.this
node = node.properties[0].name
- node = new Literal '_' + node.value if node.value.reserved
+ if node.value.reserved
+ node = new Literal o.scope.freeVariable node.value
else if node.isComplex()
node = new Literal o.scope.freeVariable 'arg'
node = new Value node
@@ -909,7 +965,34 @@ as well as be a splat, gathering up a group of parameters into an array.
@reference = node
isComplex: ->
- @name.isComplex() | |||
Splat | | |||
A splat, either as a parameter to a function, an argument to a call, + @name.isComplex() | ||||
Finds the name or names of a | names: (name = @name)->
+ atParam = (obj) ->
+ {value} = obj.properties[0].name
+ return if value.reserved then [] else [value] | |||
| return [name.value] if name instanceof Literal | |||
| return atParam(name) if name instanceof Value
+ names = []
+ for obj in name.objects | |||
| if obj instanceof Assign
+ names.push obj.variable.base.value | |||
| else if obj.isArray() or obj.isObject()
+ names.push @names(obj.base)... | |||
| else if obj.this
+ names.push atParam(obj)... | |||
| else names.push obj.base.value
+ names | |||
Splat | | |||
A splat, either as a parameter to a function, an argument to a call, or as part of a destructuring assignment. | exports.Splat = class Splat extends Base
children: ['name']
@@ -924,8 +1007,8 @@ or as part of a destructuring assignment. | compile: (o) -> if @index? then @compileParam o else @name.compile o - - unwrap: -> @name | ||
Utility function that converts an arbitrary number of elements, mixed with + + unwrap: -> @name | ||||
Utility function that converts an arbitrary number of elements, mixed with splats, to a proper array. | @compileSplattedArray: (o, list, apply) ->
index = -1
continue while (node = list[++index]) and node not instanceof Splat
@@ -941,8 +1024,8 @@ splats, to a proper array. | then "#{ utility 'slice' }.call(#{code})" else "[#{code}]" return args[0] + ".concat(#{ args[1..].join ', ' })" if index is 0 - base = (node.compile o, LEVEL_LIST for node in list[0...index]) - "[#{ base.join ', ' }].concat(#{ args.join ', ' })" | ||
While | | |||
A while loop, the only sort of low-level loop exposed by CoffeeScript. From + base = (node.compile o, LEVEL_LIST for node in list[...index]) + "[#{ base.join ', ' }].concat(#{ args.join ', ' })" | ||||
While | | |||
A while loop, the only sort of low-level loop exposed by CoffeeScript. From it, all other loops can be manufactured. Useful in cases where you need more flexibility or more speed than a comprehension can provide. | exports.While = class While extends Base
constructor: (condition, options) ->
@@ -968,7 +1051,7 @@ flexibility or more speed than a comprehension can provide. | return no unless expressions.length
for node in expressions
return node if node.jumps loop: yes
- no |||
The main difference from a JavaScript while is that the CoffeeScript + no | ||||
The main difference from a JavaScript while is that the CoffeeScript while can be used as a part of a larger expression -- while loops may return an array containing the computed result of each iteration. | compileNode: (o) ->
o.indent += TAB
@@ -989,14 +1072,12 @@ return an array containing the computed result of each iteration.
code = set + @tab + "while (#{ @condition.compile o, LEVEL_PAREN }) {#{body}}"
if @returns
code += "\n#{@tab}return #{rvar};"
- code | |||
Op | | |||
Simple Arithmetic and logical operations. Performs some conversion from + code | ||||
Op | | |||
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'
if op is 'do'
- call = new Call first, first.params or []
- call.do = yes
- return call
+ 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
@@ -1004,10 +1085,10 @@ CoffeeScript operations into their JavaScript equivalents. |
@first = first
@second = second
@flip = !!flip
- return this |||
The map of conversions from CoffeeScript to JavaScript symbols. | CONVERSIONS =
+ return this | |||
The map of conversions from CoffeeScript to JavaScript symbols. | CONVERSIONS =
'==': '==='
'!=': '!=='
- 'of': 'in' | |||
The map of invertible operators. | INVERSIONS =
+ 'of': 'in' | |||
The map of invertible operators. | INVERSIONS =
'!==': '==='
'===': '!=='
@@ -1019,7 +1100,7 @@ CoffeeScript operations into their JavaScript equivalents. |
not @second
isComplex: ->
- not (@isUnary() and (@operator in ['+', '-'])) or @first.isComplex() |||
Am I capable of + not (@isUnary() and (@operator in ['+', '-'])) or @first.isComplex() | ||||
Am I capable of Python-style comparison chaining? | isChainable: ->
@operator in ['<', '>', '>=', '<=', '===', '!==']
@@ -1053,15 +1134,35 @@ CoffeeScript operations into their JavaScript equivalents. |
unfoldSoak: (o) ->
@operator in ['++', '--', 'delete'] and unfoldSoak o, this, 'first'
- compileNode: (o) ->
- isChain = @isChainable() and @first.isChainable() |||
In chains, there's no need to wrap bare obj literals in parens, + generateDo: (exp) -> + passedParams = [] + func = if exp instanceof Assign and (ref = exp.value.unwrap()) instanceof Code + ref + else + exp + for param in func.params or [] + if param.value + passedParams.push param.value + delete param.value + else + passedParams.push param + call = new Call exp, passedParams + call.do = yes + call + + compileNode: (o) -> + isChain = @isChainable() and @first.isChainable() | ||||
In chains, there's no need to wrap bare obj literals in parens, as the chained expression is wrapped. | @first.front = @front unless isChain
+ if @operator is 'delete' and o.scope.check(@first.unwrapAll().value)
+ throw SyntaxError 'delete operand may not be argument or var'
+ if @operator in ['--', '++'] and @first.unwrapAll().value in STRICT_PROSCRIBED
+ throw SyntaxError 'prefix increment/decrement may not have eval or arguments operand'
return @compileUnary o if @isUnary()
return @compileChain o if isChain
return @compileExistence o if @operator is '?'
code = @first.compile(o, LEVEL_OP) + ' ' + @operator + ' ' +
@second.compile(o, LEVEL_OP)
- if o.level <= LEVEL_OP then code else "(#{code})" | |||
Mimic Python's chained comparisons when multiple comparison operators are + if o.level <= LEVEL_OP then code else "(#{code})" | ||||
Mimic Python's chained comparisons when multiple comparison operators are used sequentially. For example: | ||||
Compile a unary Op. | compileUnary: (o) ->
+ new If(new Existence(fst), ref, type: 'if').addElse(@second).compile o | |||
Compile a unary Op. | compileUnary: (o) ->
+ if o.level >= LEVEL_ACCESS
+ return (new Parens this).compile o
parts = [op = @operator]
plusMinus = op in ['+', '-']
parts.push ' ' if op in ['new', 'typeof', 'delete'] or
plusMinus and @first instanceof Op and @first.operator is op
if (plusMinus && @first instanceof Op) or (op is 'new' and @first.isStatement o)
- @first = new Parens @first
+ @first = new Parens @first
parts.push @first.compile o, LEVEL_OP
parts.reverse() if @flip
parts.join ''
toString: (idt) ->
- super idt, @constructor.name + ' ' + @operator | |||
In | exports.In = class In extends Base
+ super idt, @constructor.name + ' ' + @operator | |||
In | exports.In = class In extends Base
constructor: (@object, @array) ->
children: ['object', 'array']
@@ -1102,7 +1205,7 @@ true
if @array instanceof Value and @array.isArray()
for obj in @array.base.objects when obj instanceof Splat
hasSplat = yes
- break | |||
| return @compileOrTest o unless hasSplat
+ break | |||
| return @compileOrTest o unless hasSplat
@compileLoopTest o
compileOrTest: (o) ->
@@ -1123,7 +1226,7 @@ true
if o.level < LEVEL_LIST then code else "(#{code})"
toString: (idt) ->
- super idt, @constructor.name + if @negated then '!' else '' | |||
Try | | |||
A classic try/catch/finally block. | exports.Try = class Try extends Base
+ super idt, @constructor.name + if @negated then '!' else '' | |||
Try | | |||
A classic try/catch/finally block. | exports.Try = class Try extends Base
constructor: (@attempt, @error, @recovery, @ensure) ->
children: ['attempt', 'recovery', 'ensure']
@@ -1135,32 +1238,34 @@ true
makeReturn: (res) ->
@attempt = @attempt .makeReturn res if @attempt
@recovery = @recovery.makeReturn res if @recovery
- this | |||
Compilation is more or less as you would expect -- the finally clause + this | ||||
Compilation is more or less as you would expect -- the finally clause is optional, the catch is not. | compileNode: (o) ->
o.indent += TAB
errorPart = if @error then " (#{ @error.compile o }) " else ' '
tryPart = @attempt.compile o, LEVEL_TOP
-
+
catchPart = if @recovery
+ if @error.value in STRICT_PROSCRIBED
+ throw SyntaxError "catch variable may not be \"#{@error.value}\""
o.scope.add @error.value, 'param' unless o.scope.check @error.value
" catch#{errorPart}{\n#{ @recovery.compile o, LEVEL_TOP }\n#{@tab}}"
else unless @ensure or @recovery
' catch (_error) {}'
-
+
ensurePart = if @ensure then " finally {\n#{ @ensure.compile o, LEVEL_TOP }\n#{@tab}}" else ''
-
+
"""#{@tab}try {
#{tryPart}
- #{@tab}}#{ catchPart or '' }#{ensurePart}""" | |||
Throw | | |||
Simple node to throw an exception. | exports.Throw = class Throw extends Base
+ #{@tab}}#{ catchPart or '' }#{ensurePart}""" | |||
Throw | | |||
Simple node to throw an exception. | exports.Throw = class Throw extends Base
constructor: (@expression) ->
children: ['expression']
isStatement: YES
- jumps: NO | |||
A Throw is already a return, of sorts... | makeReturn: THIS
+ jumps: NO | |||
A Throw is already a return, of sorts... | makeReturn: THIS
compileNode: (o) ->
- @tab + "throw #{ @expression.compile o };" | |||
Existence | | |||
Checks a variable for existence -- not null and not undefined. This is + @tab + "throw #{ @expression.compile o };" | ||||
Existence | | |||
Checks a variable for existence -- not null and not undefined. This is
similar to | exports.Existence = class Existence extends Base
constructor: (@expression) ->
@@ -1175,8 +1280,8 @@ table. | if IDENTIFIER.test(code) and not o.scope.check code
[cmp, cnj] = if @negated then ['===', '||'] else ['!==', '&&']
code = "typeof #{code} #{cmp} \"undefined\" #{cnj} #{code} #{cmp} null"
- else | ||
do not use strict equality here; it will break existing code | code = "#{code} #{if @negated then '==' else '!='} null"
- if o.level <= LEVEL_COND then code else "(#{code})" | |||
Parens | | |||
An extra set of parentheses, specified explicitly in the source. At one time + else | ||||
do not use strict equality here; it will break existing code | code = "#{code} #{if @negated then '==' else '!='} null"
+ if o.level <= LEVEL_COND then code else "(#{code})" | |||
Parens | | |||
An extra set of parentheses, specified explicitly in the source. At one time we tried to clean up the results by detecting and removing redundant parentheses, but no longer -- you can put in as many as you please. @@ -1196,7 +1301,7 @@ parentheses, but no longer -- you can put in as many as you please. code = expr.compile o, LEVEL_PAREN bare = o.level < LEVEL_OP and (expr instanceof Op or expr instanceof Call or (expr instanceof For and expr.returns)) - if bare then code else "(#{code})" | ||||
For | | |||
CoffeeScript's replacement for the for loop is our array and object + if bare then code else "(#{code})" | ||||
For | | |||
CoffeeScript's replacement for the for loop is our array and object comprehensions, that compile into for loops here. They also act as an expression, able to return the result of each filtered iteration. @@ -1216,7 +1321,7 @@ you can map and filter in a single pass. | throw SyntaxError 'cannot pattern match over range loops' if @range and @pattern @returns = false - children: ['body', 'source', 'guard', 'step'] | |||
Welcome to the hairiest method in all of CoffeeScript. Handles the inner + children: ['body', 'source', 'guard', 'step'] | ||||
Welcome to the hairiest method in all of CoffeeScript. Handles the inner loop, filtering, stepping, and result saving for array, object, and range comprehensions. Some of the generated code can be shared in common, and some cannot. | compileNode: (o) ->
@@ -1230,25 +1335,28 @@ some cannot. | < scope.find(name, immediate: yes) if name and not @pattern scope.find(index, immediate: yes) if index rvar = scope.freeVariable 'results' if @returns - ivar = (if @range then name else index) or scope.freeVariable 'i' | ||
the | stepvar = scope.freeVariable "step" if @step and not @range
+ 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 | stepvar = scope.freeVariable "step" if @step and not @range
name = ivar if @pattern
varPart = ''
guardPart = ''
defPart = ''
idt1 = @tab + TAB
if @range
- forPart = source.compile merge(o, {index: ivar, @step})
+ forPart = source.compile merge(o, {index: ivar, name, @step})
else
svar = @source.compile o, LEVEL_LIST
if (name or @own) and not IDENTIFIER.test svar
defPart = "#{@tab}#{ref = scope.freeVariable 'ref'} = #{svar};\n"
svar = ref
if name and not @pattern
- namePart = "#{name} = #{svar}[#{ivar}]"
+ namePart = "#{name} = #{svar}[#{kvar}]"
unless @object
lvar = scope.freeVariable 'len'
- forVarPart = "#{ivar} = 0, #{lvar} = #{svar}.length" + if @step then ", #{stepvar} = #{@step.compile(o, LEVEL_OP)}" else ''
- stepPart = if @step then "#{ivar} += #{stepvar}" else "#{ivar}++"
+ 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 @returns
resultPart = "#{@tab}#{rvar} = [];\n"
@@ -1260,12 +1368,12 @@ some cannot. | < else body = Block.wrap [new If @guard, body] if @guard if @pattern - body.expressions.unshift new Assign @name, new Literal "#{svar}[#{ivar}]" + body.expressions.unshift new Assign @name, new Literal "#{svar}[#{kvar}]" defPart += @pluckDirectCall o, body varPart = "\n#{idt1}#{namePart};" if namePart if @object - forPart = "#{ivar} in #{svar}" - guardPart = "\n#{idt1}if (!#{utility 'hasProp'}.call(#{svar}, #{ivar})) continue;" if @own + forPart = "#{kvar} in #{svar}" + guardPart = "\n#{idt1}if (!#{utility 'hasProp'}.call(#{svar}, #{kvar})) continue;" if @own body = body.compile merge(o, indent: idt1), LEVEL_TOP body = '\n' + body + '\n' if body """ @@ -1290,7 +1398,7 @@ some cannot. | < [val.base, base] = [base, val] body.expressions[idx] = new Call base, expr.args defs += @tab + new Assign(ref, fn).compile(o, LEVEL_TOP) + ';\n' - defs | |
Switch | | |||
A JavaScript switch statement. Converts into a returnable expression on-demand. | exports.Switch = class Switch extends Base
+ defs | |||
Switch | | |||
A JavaScript switch statement. Converts into a returnable expression on-demand. | exports.Switch = class Switch extends Base
constructor: (@subject, @cases, @otherwise) ->
children: ['subject', 'cases', 'otherwise']
@@ -1322,7 +1430,7 @@ some cannot. | < continue if expr instanceof Return or (expr instanceof Literal and expr.jumps() and expr.value isnt 'debugger') code += idt2 + 'break;\n' code += idt1 + "default:\n#{ @otherwise.compile o, LEVEL_TOP }\n" if @otherwise and @otherwise.expressions.length - code + @tab + '}' | ||
If | | |||
If/else statements. Acts as an expression by pushing down requested returns + code + @tab + '}' | ||||
If | | |||
If/else statements. Acts as an expression by pushing down requested returns to the last line of each clause. Single-expression Ifs are compiled into conditional operators if possible, @@ -1336,13 +1444,13 @@ because ternaries are already proper expressions, and don't need conversion. children: ['condition', 'body', 'elseBody'] bodyNode: -> @body?.unwrap() - elseBodyNode: -> @elseBody?.unwrap() | ||||
Rewrite a chain of Ifs to add a default case as the final else. | addElse: (elseBody) ->
+ elseBodyNode: -> @elseBody?.unwrap() | |||
Rewrite a chain of Ifs to add a default case as the final else. | addElse: (elseBody) ->
if @isChain
@elseBodyNode().addElse elseBody
else
@isChain = elseBody instanceof If
@elseBody = @ensureBlock elseBody
- this | |||
The If only compiles into a statement if either of its bodies needs + this | ||||
The If only compiles into a statement if either of its bodies needs to be a statement. Otherwise a conditional operator is safe. | isStatement: (o) ->
o?.level is LEVEL_TOP or
@bodyNode().isStatement(o) or @elseBodyNode()?.isStatement(o)
@@ -1359,7 +1467,7 @@ to be a statement. Otherwise a conditional operator is safe. this
ensureBlock: (node) ->
- if node instanceof Block then node else new Block [node] | |||
Compile the | ||||
Compile the | compileStatement: (o) ->
child = del o, 'chainChild'
exeq = del o, 'isExistentialEquals'
@@ -1370,17 +1478,7 @@ force inner else bodies into statement form. |
cond = @condition.compile o, LEVEL_PAREN
o.indent += TAB
body = @ensureBlock(@body)
- bodyc = body.compile o
- if (
- 1 is body.expressions?.length and
- !@elseBody and !child and
- bodyc and cond and
- -1 is (bodyc.indexOf '\n') and
- 80 > cond.length + bodyc.length
- )
- return "#{@tab}if (#{cond}) #{bodyc.replace /^\s+/, ''}"
- bodyc = "\n#{bodyc}\n#{@tab}" if bodyc
- ifPart = "if (#{cond}) {#{bodyc}}"
+ ifPart = "if (#{cond}) {\n#{body.compile(o)}\n#{@tab}}"
ifPart = @tab + ifPart unless child
return ifPart unless @elseBody
ifPart + ' else ' + if @isChain
@@ -1388,7 +1486,7 @@ force inner else bodies into statement form.
o.chainChild = yes
@elseBody.unwrap().compile o, LEVEL_TOP
else
- "{\n#{ @elseBody.compile o, LEVEL_TOP }\n#{@tab}}" |||
Compile the | compileExpression: (o) ->
+ "{\n#{ @elseBody.compile o, LEVEL_TOP }\n#{@tab}}" | |||
Compile the | compileExpression: (o) ->
cond = @condition.compile o, LEVEL_COND
body = @bodyNode().compile o, LEVEL_LIST
alt = if @elseBodyNode() then @elseBodyNode().compile(o, LEVEL_LIST) else 'void 0'
@@ -1396,10 +1494,10 @@ force inner else bodies into statement form. |
if o.level >= LEVEL_COND then "(#{code})" else code
unfoldSoak: ->
- @soak and this |||
Faux-Nodes+ @soak and this | ||||
Faux-NodesFaux-nodes are never created by the grammar, but are used during code -generation to generate other combinations of nodes. | | |||
Closure | | |||
A faux-node used to wrap an expressions body in a closure. | Closure = | |||
Wrap the expressions body, unless it contains a pure statement, +generation to generate other combinations of nodes. | | |||
Closure | | |||
A faux-node used to wrap an expressions body in a closure. | Closure = | |||
Wrap the expressions body, unless it contains a pure statement,
in which case, no dice. If the body mentions | wrap: (expressions, statement, noReturn) ->
return expressions if expressions.jumps()
@@ -1416,28 +1514,28 @@ then make sure that the closure wrapper preserves the original values.
literalArgs: (node) ->
node instanceof Literal and node.value is 'arguments' and not node.asKey
-
+
literalThis: (node) ->
(node instanceof Literal and node.value is 'this' and not node.asKey) or
- (node instanceof Code and node.bound) | |||
Unfold a node's child if soak, then tuck the node under created | unfoldSoak = (o, parent, name) ->
+ (node instanceof Code and node.bound) | |||
Unfold a node's child if soak, then tuck the node under created | unfoldSoak = (o, parent, name) ->
return unless ifn = parent[name].unfoldSoak o
parent[name] = ifn.body
ifn.body = new Value parent
- ifn | |||
Constants | UTILITIES = | |||
Correctly set up a prototype chain for inheritance, including a reference + ifn | ||||
Constants | UTILITIES = | |||
Correctly set up a prototype chain for inheritance, including a reference
to the superclass for | 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: -> '''
+ """ | |||
Create a function bound to the current value of "this". | bind: -> '''
function(fn, me){ return function(){ return fn.apply(me, arguments); }; }
- ''' | |||
Discover if an item is in an array. | indexOf: -> """
- Array.prototype.indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }
- """ | |||
Shortcuts to speed up the lookup time for native functions. | hasProp: -> 'Object.prototype.hasOwnProperty'
- slice : -> 'Array.prototype.slice' | |||
Levels indicate a node's position in the AST. Useful for knowing if + ''' | ||||
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; }
+ """ | |||
Shortcuts to speed up the lookup time for native functions. | hasProp: -> '{}.hasOwnProperty'
+ slice : -> '[].slice' | |||
Levels indicate a node's position in the AST. Useful for knowing if parens are necessary or superfluous. | LEVEL_TOP = 1 # ...;
LEVEL_PAREN = 2 # (...)
LEVEL_LIST = 3 # [...]
LEVEL_COND = 4 # ... ? x : y
LEVEL_OP = 5 # !...
-LEVEL_ACCESS = 6 # ...[0] | |||
Tabs are two spaces for pretty printing. | TAB = ' '
+LEVEL_ACCESS = 6 # ...[0] | |||
Tabs are two spaces for pretty printing. | TAB = ' '
IDENTIFIER_STR = "[$A-Za-z_\\x7f-\\uffff][$\\w\\x7f-\\uffff]*"
IDENTIFIER = /// ^ #{IDENTIFIER_STR} $ ///
@@ -1460,7 +1558,7 @@ parens are necessary or superfluous. | #DIVIDER -IS_STRING = /^['"]/ | ||
Is a literal value a string? | | |||
Utility Functions | utility = (name) ->
+IS_STRING = /^['"]/ | |||
Is a literal value a string? | | |||
Utility Functions | utility = (name) ->
ref = "__#{name}"
Scope.root.assign ref, UTILITIES[name]()
ref
@@ -1469,4 +1567,4 @@ parens are necessary or superfluous. | code = code.replace /\n/g, '$&' + tab code.replace /\s+$/, '' - | ||
Helper for ensuring that utility functions are assigned at the top level. | undefined |
Helper for ensuring that utility functions are assigned at the top level.
undefined