diff --git a/documentation/docs/browser.html b/documentation/docs/browser.html index 7364615e..8b759611 100644 --- a/documentation/docs/browser.html +++ b/documentation/docs/browser.html @@ -69,6 +69,11 @@ + + register.coffee + + + repl.coffee @@ -111,14 +116,12 @@

This Browser compatibility layer extends core CoffeeScript functions to make things work smoothly when compiling code directly in the browser. We add support for loading remote Coffee scripts via XHR, and -text/coffeescript script tags, source maps via data-URLs, and so on. -

+text/coffeescript script tags, source maps via data-URLs, and so on.

-
-CoffeeScript = require './coffee-script'
-CoffeeScript.require = require
+            
CoffeeScript = require './coffee-script'
+CoffeeScript.require = require
 compile = CoffeeScript.compile
@@ -130,13 +133,12 @@ compile = CoffeeScript.compile
-

Use standard JavaScript eval to eval code. -

+

Use standard JavaScript eval to eval code.

-
CoffeeScript.eval = (code, options = {}) ->
-  options.bare ?= on
+            
CoffeeScript.eval = (code, options = {}) ->
+  options.bare ?= on
   eval compile code, options
@@ -148,14 +150,13 @@ compile = CoffeeScript.compile
-

Running code does not provide access to this scope. -

+

Running code does not provide access to this scope.

-
CoffeeScript.run = (code, options = {}) ->
-  options.bare = on
-  options.shiftLine = on
+            
CoffeeScript.run = (code, options = {}) ->
+  options.bare = on
+  options.shiftLine = on
   Function(compile code, options)()
@@ -167,12 +168,11 @@ compile = CoffeeScript.compile
-

If we're not in a browser environment, we're finished with the public API. -

+

If we're not in a browser environment, we're finished with the public API.

-
return unless window?
+
return unless window?
@@ -185,17 +185,16 @@ compile = CoffeeScript.compile

Include source maps where possible. If we've got a base64 encoder, a JSON serializer, and tools for escaping unicode characters, we're good to go. -Ported from https://developer.mozilla.org/en-US/docs/DOM/window.btoa -

+Ported from https://developer.mozilla.org/en-US/docs/DOM/window.btoa

-
if btoa? and JSON? and unescape? and encodeURIComponent?
-  compile = (code, options = {}) ->
-    options.sourceMap = true
-    options.inline = true
+            
if btoa? and JSON? and unescape? and encodeURIComponent?
+  compile = (code, options = {}) ->
+    options.sourceMap = true
+    options.inline = true
     {js, v3SourceMap} = CoffeeScript.compile code, options
-    "#{js}\n//@ sourceMappingURL=data:application/json;base64,#{btoa unescape encodeURIComponent v3SourceMap}\n//@ sourceURL=coffeescript"
+ "#{js}\n//# sourceMappingURL=data:application/json;base64,#{btoa unescape encodeURIComponent v3SourceMap}\n//# sourceURL=coffeescript"
@@ -206,27 +205,27 @@ Ported from h
-

Load a remote script from the current domain via XHR. -

+

Load a remote script from the current domain via XHR.

-
CoffeeScript.load = (url, callback, options = {}) ->
+            
CoffeeScript.load = (url, callback, options = {}, hold = false) ->
   options.sourceFiles = [url]
-  xhr = if window.ActiveXObject
-    new window.ActiveXObject('Microsoft.XMLHTTP')
-  else
-    new window.XMLHttpRequest()
-  xhr.open 'GET', url, true
-  xhr.overrideMimeType 'text/plain' if 'overrideMimeType' of xhr
-  xhr.onreadystatechange = ->
-    if xhr.readyState is 4
-      if xhr.status in [0, 200]
-        CoffeeScript.run xhr.responseText, options
-      else
-        throw new Error "Could not load #{url}"
-      callback() if callback
-  xhr.send null
+ xhr = if window.ActiveXObject + new window.ActiveXObject('Microsoft.XMLHTTP') + else + new window.XMLHttpRequest() + xhr.open 'GET', url, true + xhr.overrideMimeType 'text/plain' if 'overrideMimeType' of xhr + xhr.onreadystatechange = -> + if xhr.readyState is 4 + if xhr.status in [0, 200] + param = [xhr.responseText, options] + CoffeeScript.run param... unless hold + else + throw new Error "Could not load #{url}" + callback param if callback + xhr.send null
@@ -239,29 +238,38 @@ Ported from h

Activate CoffeeScript in the browser by having it compile and evaluate all script tags with a content-type of text/coffeescript. -This happens on page load. -

+This happens on page load.

-
runScripts = ->
-  scripts = window.document.getElementsByTagName 'script'
-  coffeetypes = ['text/coffeescript', 'text/literate-coffeescript']
-  coffees = (s for s in scripts when s.type in coffeetypes)
-  index = 0
-  length = coffees.length
-  do execute = ->
-    script = coffees[index++]
-    mediatype = script?.type
-    if mediatype in coffeetypes
-      options = {literate: mediatype is 'text/literate-coffeescript'}
-      if script.src
-        CoffeeScript.load script.src, execute, options
-      else
-        options.sourceFiles = ['embedded']
-        CoffeeScript.run script.innerHTML, options
-        execute()
-  null
+
runScripts = ->
+  scripts = window.document.getElementsByTagName 'script'
+  coffeetypes = ['text/coffeescript', 'text/literate-coffeescript']
+  coffees = (s for s in scripts when s.type in coffeetypes)
+  index = 0
+
+  execute = ->
+    param = coffees[index]
+    if param instanceof Array
+      CoffeeScript.run param...
+      index++
+      execute()
+
+  for script, i in coffees
+    do (script, i) ->
+      options = literate: script.type is coffeetypes[1]
+      if script.src
+        CoffeeScript.load script.src,
+          (param) ->
+            coffees[i] = param
+            execute()
+          options
+          true
+      else
+        options.sourceFiles = ['embedded']
+        coffees[i] = [script.innerHTML, options]
+
+  execute()
@@ -272,15 +280,14 @@ This happens on page load.
-

Listen for window load, both in decent browsers and in IE. -

+

Listen for window load, both in decent browsers and in IE.

-
if window.addEventListener
-  window.addEventListener 'DOMContentLoaded', runScripts, no
-else
-  window.attachEvent 'onload', runScripts
+
if window.addEventListener
+  window.addEventListener 'DOMContentLoaded', runScripts, no
+else
+  window.attachEvent 'onload', runScripts
diff --git a/documentation/docs/cake.html b/documentation/docs/cake.html index dbcb09bc..d129a301 100644 --- a/documentation/docs/cake.html +++ b/documentation/docs/cake.html @@ -69,6 +69,11 @@ + + register.coffee + + + repl.coffee @@ -111,15 +116,19 @@

cake is a simplified version of Make (Rake, Jake) for CoffeeScript. You define tasks with names and descriptions in a Cakefile, -and can call them from the command line, or invoke them from other tasks. - -

+and can call them from the command line, or invoke them from other tasks.

Running cake with no arguments will print out a list of all the tasks in the -current directory's Cakefile. -

+current directory's Cakefile.

+

External dependencies.

+
fs           = require 'fs'
+path         = require 'path'
+helpers      = require './helpers'
+optparse     = require './optparse'
+CoffeeScript = require './coffee-script'
+ @@ -129,18 +138,14 @@ current directory's Cakefile.
-

External dependencies. -

+

Keep track of the list of defined tasks, the accepted options, and so on.

-
fs           = require 'fs'
-path         = require 'path'
-helpers      = require './helpers'
-optparse     = require './optparse'
-CoffeeScript = require './coffee-script'
-
-existsSync   = fs.existsSync or path.existsSync
+
tasks     = {}
+options   = {}
+switches  = []
+oparse    = null
@@ -151,15 +156,11 @@ existsSync = fs.existsSync or path.existsSync -

Keep track of the list of defined tasks, the accepted options, and so on. -

+

Mixin the top-level Cake functions for Cakefiles to use directly.

-
tasks     = {}
-options   = {}
-switches  = []
-oparse    = null
+
helpers.extend global,
@@ -170,12 +171,14 @@ oparse = null
-

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.

-
helpers.extend global,
+
  task: (name, description, action) ->
+    [action, description] = [description, action] unless action
+    tasks[name] = {name, description, action}
@@ -186,15 +189,14 @@ oparse = null
-

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.

-
  task: (name, description, action) ->
-    [action, description] = [description, action] unless action
-    tasks[name] = {name, description, action}
+
  option: (letter, flag, description) ->
+    switches.push [letter, flag, description]
@@ -205,15 +207,13 @@ 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.

-
  option: (letter, flag, description) ->
-    switches.push [letter, flag, description]
+
  invoke: (name) ->
+    missingTask name unless tasks[name]
+    tasks[name].action options
@@ -224,14 +224,25 @@ as the first argument to the action.
-

Invoke another task in the current Cakefile. -

+

Run 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.

-
  invoke: (name) ->
-    missingTask name unless tasks[name]
-    tasks[name].action options
+
exports.run = ->
+  global.__originalDirname = fs.realpathSync '.'
+  process.chdir cakefileDirectory __originalDirname
+  args = process.argv[2..]
+  CoffeeScript.run fs.readFileSync('Cakefile').toString(), filename: 'Cakefile'
+  oparse = new optparse.OptionParser switches
+  return printTasks() unless args.length
+  try
+    options = oparse.parse(args)
+  catch e
+    return fatalError "#{e}"
+  invoke arg for arg in options.arguments
@@ -242,26 +253,20 @@ as the first argument to the action.
-

Run 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. -

+

Display the list of Cake tasks in a format similar to rake -T

-
exports.run = ->
-  global.__originalDirname = fs.realpathSync '.'
-  process.chdir cakefileDirectory __originalDirname
-  args = process.argv[2..]
-  CoffeeScript.run fs.readFileSync('Cakefile').toString(), filename: 'Cakefile'
-  oparse = new optparse.OptionParser switches
-  return printTasks() unless args.length
-  try
-    options = oparse.parse(args)
-  catch e
-    return fatalError "#{e}"
-  invoke arg for arg in options.arguments
+
printTasks = ->
+  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
+    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
@@ -272,21 +277,16 @@ original directory name, when running Cake tasks from subdirectories.
-

Display the list of Cake tasks in a format similar to rake -T -

+

Print an error and exit when attempting to use an invalid task/option.

-
printTasks = ->
-  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
-    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
+
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}"
@@ -297,38 +297,16 @@ original directory name, when running Cake tasks from subdirectories.
-

Print an error and exit when attempting to use an invalid task/option. -

- - - -
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}"
- - - - -
  • -
    - -
    - -

    When cake is invoked, search in the current and all parent directories -to find the relevant Cakefile. -

    +to find the relevant Cakefile.

    -
    cakefileDirectory = (dir) ->
    -  return dir if existsSync path.join dir, 'Cakefile'
    -  parent = path.normalize path.join dir, '..'
    -  return cakefileDirectory parent unless parent is dir
    -  throw new Error "Cakefile not found in #{process.cwd()}"
    +
    cakefileDirectory = (dir) ->
    +  return dir if fs.existsSync path.join dir, 'Cakefile'
    +  parent = path.normalize path.join dir, '..'
    +  return cakefileDirectory parent unless parent is dir
    +  throw new Error "Cakefile not found in #{process.cwd()}"
  • diff --git a/documentation/docs/coffee-script.html b/documentation/docs/coffee-script.html index 0ff676f8..becd14b9 100644 --- a/documentation/docs/coffee-script.html +++ b/documentation/docs/coffee-script.html @@ -69,6 +69,11 @@ + + register.coffee + + + repl.coffee @@ -111,20 +116,17 @@

    CoffeeScript can be used both on the server, as a command-line compiler based on Node.js/V8, or to run CoffeeScript directly in the browser. This module contains the main entry functions for tokenizing, parsing, and compiling -source CoffeeScript into JavaScript. -

    +source CoffeeScript into JavaScript.

    -
    -fs            = require 'fs'
    -vm            = require 'vm'
    -path          = require 'path'
    -child_process = require 'child_process'
    -{Lexer}       = require './lexer'
    -{parser}      = require './parser'
    -helpers       = require './helpers'
    -SourceMap     = require './sourcemap'
    +
    fs            = require 'fs'
    +vm            = require 'vm'
    +path          = require 'path'
    +{Lexer}       = require './lexer'
    +{parser}      = require './parser'
    +helpers       = require './helpers'
    +SourceMap     = require './sourcemap'
    @@ -135,12 +137,13 @@ SourceMap = require './sourcemap' -

    The current CoffeeScript version number. -

    +

    The current CoffeeScript version number.

    -
    exports.VERSION = '1.6.3'
    +
    exports.VERSION = '1.7.0'
    +
    +exports.FILE_EXTENSIONS = ['.coffee', '.litcoffee', '.coffee.md']
    @@ -151,12 +154,11 @@ SourceMap = require './sourcemap' -

    Expose helpers for testing. -

    +

    Expose helpers for testing.

    -
    exports.helpers = helpers
    +
    exports.helpers = helpers
    @@ -167,35 +169,17 @@ SourceMap = require './sourcemap' -

    Compile CoffeeScript code to JavaScript, using the Coffee/Jison compiler. - -

    -

    If options.sourceMap is specified, then options.filename must also be specified. All -options that can be passed to SourceMap#generate may also be passed here. - -

    -

    This returns a javascript string, unless options.sourceMap is passed, -in which case this returns a {js, v3SourceMap, sourceMap} -object, where sourceMap is a sourcemap.coffee#SourceMap object, handy for doing programatic -lookups. -

    +

    Function wrapper to add source file information to SyntaxErrors thrown by the +lexer/parser/compiler.

    -
    exports.compile = compile = (code, options = {}) ->
    -  {merge} = helpers
    -
    -  if options.sourceMap
    -    map = new SourceMap
    -
    -  fragments = parser.parse(lexer.tokenize code, options).compileToFragments options
    -
    -  currentLine = 0
    -  currentLine += 1 if options.header
    -  currentLine += 1 if options.shiftLine
    -  currentColumn = 0
    -  js = ""
    -  for fragment in fragments
    +
    withPrettyErrors = (fn) ->
    +  (code, options = {}) ->
    +    try
    +      fn.call @, code, options
    +    catch err
    +      throw helpers.updateSyntaxError err, code, options.filename
    @@ -206,20 +190,31 @@ lookups.
    -

    Update the sourcemap with data from each fragment -

    +

    Compile CoffeeScript code to JavaScript, using the Coffee/Jison compiler.

    +

    If options.sourceMap is specified, then options.filename must also be specified. All +options that can be passed to SourceMap#generate may also be passed here.

    +

    This returns a javascript string, unless options.sourceMap is passed, +in which case this returns a {js, v3SourceMap, sourceMap} +object, where sourceMap is a sourcemap.coffee#SourceMap object, handy for doing programatic +lookups.

    -
        if options.sourceMap
    -      if fragment.locationData
    -        map.add(
    -          [fragment.locationData.first_line, fragment.locationData.first_column]
    -          [currentLine, currentColumn]
    -          {noReplace: true})
    -      newLines = helpers.count fragment.code, "\n"
    -      currentLine += newLines
    -      currentColumn = fragment.code.length - (if newLines then fragment.code.lastIndexOf "\n" else 0)
    +
    exports.compile = compile = withPrettyErrors (code, options) ->
    +  {merge, extend} = helpers
    +  options = extend {}, options
    +
    +  if options.sourceMap
    +    map = new SourceMap
    +
    +  fragments = parser.parse(lexer.tokenize code, options).compileToFragments options
    +
    +  currentLine = 0
    +  currentLine += 1 if options.header
    +  currentLine += 1 if options.shiftLine
    +  currentColumn = 0
    +  js = ""
    +  for fragment in fragments
    @@ -230,24 +225,22 @@ lookups.
    -

    Copy the code from each fragment into the final JavaScript. -

    +

    Update the sourcemap with data from each fragment

    -
        js += fragment.code
    -
    -  if options.header
    -    header = "Generated by CoffeeScript #{@VERSION}"
    -    js = "// #{header}\n#{js}"
    -
    -  if options.sourceMap
    -    answer = {js}
    -    answer.sourceMap = map
    -    answer.v3SourceMap = map.generate(options, code)
    -    answer
    -  else
    -    js
    +
        if options.sourceMap
    +      if fragment.locationData
    +        map.add(
    +          [fragment.locationData.first_line, fragment.locationData.first_column]
    +          [currentLine, currentColumn]
    +          {noReplace: true})
    +      newLines = helpers.count fragment.code, "\n"
    +      currentLine += newLines
    +      if newLines
    +        currentColumn = fragment.code.length - (fragment.code.lastIndexOf("\n") + 1)
    +      else
    +        currentColumn += fragment.code.length
    @@ -258,13 +251,23 @@ lookups.
    -

    Tokenize a string of CoffeeScript code, and return the array of tokens. -

    +

    Copy the code from each fragment into the final JavaScript.

    -
    exports.tokens = (code, options) ->
    -  lexer.tokenize code, options
    +
        js += fragment.code
    +
    +  if options.header
    +    header = "Generated by CoffeeScript #{@VERSION}"
    +    js = "// #{header}\n#{js}"
    +
    +  if options.sourceMap
    +    answer = {js}
    +    answer.sourceMap = map
    +    answer.v3SourceMap = map.generate(options, code)
    +    answer
    +  else
    +    js
    @@ -275,18 +278,12 @@ lookups.
    -

    Parse a string of CoffeeScript code or an array of lexed tokens, and -return the AST. You can then compile it by calling .compile() on the root, -or traverse it by using .traverseChildren() with a callback. -

    +

    Tokenize a string of CoffeeScript code, and return the array of tokens.

    -
    exports.nodes = (source, options) ->
    -  if typeof source is 'string'
    -    parser.parse lexer.tokenize source, options
    -  else
    -    parser.parse source
    +
    exports.tokens = withPrettyErrors (code, options) ->
    +  lexer.tokenize code, options
    @@ -297,15 +294,17 @@ or traverse it by using .traverseChildren() with a callback.
    -

    Compile and execute a string of CoffeeScript (on the server), correctly -setting __filename, __dirname, and relative require(). -

    +

    Parse a string of CoffeeScript code or an array of lexed tokens, and +return the AST. You can then compile it by calling .compile() on the root, +or traverse it by using .traverseChildren() with a callback.

    -
    exports.run = (code, options = {}) ->
    -  mainModule = require.main
    -  options.sourceMap ?= true
    +
    exports.nodes = withPrettyErrors (source, options) ->
    +  if typeof source is 'string'
    +    parser.parse lexer.tokenize source, options
    +  else
    +    parser.parse source
    @@ -316,13 +315,13 @@ setting __filename, __dirname, and relative requ
    -

    Set the filename. -

    +

    Compile and execute a string of CoffeeScript (on the server), correctly +setting __filename, __dirname, and relative require().

    -
      mainModule.filename = process.argv[1] =
    -    if options.filename then fs.realpathSync(options.filename) else '.'
    +
    exports.run = (code, options = {}) ->
    +  mainModule = require.main
    @@ -333,12 +332,12 @@ setting __filename, __dirname, and relative requ
    -

    Clear the module cache. -

    +

    Set the filename.

    -
      mainModule.moduleCache and= {}
    +
      mainModule.filename = process.argv[1] =
    +    if options.filename then fs.realpathSync(options.filename) else '.'
    @@ -349,12 +348,11 @@ setting __filename, __dirname, and relative requ
    -

    Assign paths for node_modules loading -

    +

    Clear the module cache.

    -
      mainModule.paths = require('module')._nodeModulePaths path.dirname fs.realpathSync options.filename or '.'
    +
      mainModule.moduleCache and= {}
    @@ -365,13 +363,15 @@ setting __filename, __dirname, and relative requ
    -

    Compile. -

    +

    Assign paths for node_modules loading

    -
      if not helpers.isCoffee(mainModule.filename) or require.extensions
    -    answer = compile(code, options)
    +
      dir = if options.filename
    +    path.dirname fs.realpathSync options.filename
    +  else
    +    fs.realpathSync '.'
    +  mainModule.paths = require('module')._nodeModulePaths dir
    @@ -382,17 +382,15 @@ setting __filename, __dirname, and relative requ
    -

    Attach sourceMap object to sourceMaps[options.filename] so that -it is accessible by Error.prepareStackTrace. -

    +

    Compile.

    -
        do patchStackTrace
    -    sourceMaps[mainModule.filename] = answer.sourceMap
    -    mainModule._compile answer.js, mainModule.filename
    -  else
    -    mainModule._compile code, mainModule.filename
    +
      if not helpers.isCoffee(mainModule.filename) or require.extensions
    +    answer = compile code, options
    +    code = answer.js ? answer
    +
    +  mainModule._compile code, mainModule.filename
    @@ -404,25 +402,24 @@ it is accessible by Error.prepareStackTrace.

    Compile and evaluate a string of CoffeeScript (in a Node.js-like environment). -The CoffeeScript REPL uses this to run the input. -

    +The CoffeeScript REPL uses this to run the input.

    -
    exports.eval = (code, options = {}) ->
    -  return unless code = code.trim()
    +            
    exports.eval = (code, options = {}) ->
    +  return unless code = code.trim()
       Script = vm.Script
    -  if Script
    -    if options.sandbox?
    -      if options.sandbox instanceof Script.createContext().constructor
    +  if Script
    +    if options.sandbox?
    +      if options.sandbox instanceof Script.createContext().constructor
             sandbox = options.sandbox
    -      else
    +      else
             sandbox = Script.createContext()
    -        sandbox[k] = v for own k, v of options.sandbox
    -      sandbox.global = sandbox.root = sandbox.GLOBAL = sandbox
    -    else
    -      sandbox = global
    -    sandbox.__filename = options.filename || 'eval'
    +        sandbox[k] = v for own k, v of options.sandbox
    +      sandbox.global = sandbox.root = sandbox.GLOBAL = sandbox
    +    else
    +      sandbox = global
    +    sandbox.__filename = options.filename || 'eval'
         sandbox.__dirname  = path.dirname sandbox.__filename
    @@ -434,17 +431,16 @@ The CoffeeScript REPL uses this to run the input.
    -

    define module/require only if they chose not to specify their own -

    +

    define module/require only if they chose not to specify their own

    -
        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
    +            
        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
           _module.filename = sandbox.__filename
    -      _require[r] = require[r] for r in Object.getOwnPropertyNames require when r isnt 'paths'
    + _require[r] = require[r] for r in Object.getOwnPropertyNames require when r isnt 'paths'
    @@ -455,21 +451,30 @@ The CoffeeScript REPL uses this to run the input.
    -

    use the same hack node currently uses for their own REPL -

    +

    use the same hack node currently uses for their own REPL

          _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
    +  o[k] = v for own k, v of options
    +  o.bare = on # ensure return value
       js = compile code, o
    -  if sandbox is global
    +  if sandbox is global
         vm.runInThisContext js
    -  else
    -    vm.runInContext js, sandbox
    + else + vm.runInContext js, sandbox + +exports.register = -> require './register' + +exports._compileFile = (filename, sourceMap = no) -> + raw = fs.readFileSync filename, 'utf8' + stripped = if raw.charCodeAt(0) is 0xFEFF then raw.substring 1 else raw + + try + answer = compile(stripped, {filename, sourceMap, literate: helpers.isLiterate filename}) + catch err @@ -480,17 +485,15 @@ The CoffeeScript REPL uses this to run the input.
    -

    Load and run a CoffeeScript file for Node, stripping any BOMs. -

    +

    As the filename and code of a dynamically loaded file will be different +from the original file compiled with CoffeeScript.run, add that +information to error so it can be pretty-printed later.

    -
    loadFile = (module, filename) ->
    -  raw = fs.readFileSync filename, 'utf8'
    -  stripped = if raw.charCodeAt(0) is 0xFEFF then raw.substring 1 else raw
    -  answer = compile(stripped, {filename, sourceMap: true, literate: helpers.isLiterate filename})
    -  sourceMaps[filename] = answer.sourceMap
    -  module._compile answer.js, filename
    +
        throw helpers.updateSyntaxError err, stripped, filename
    +
    +  answer
    @@ -501,15 +504,11 @@ The CoffeeScript REPL uses this to run the input.
    -

    If the installed version of Node supports require.extensions, register -CoffeeScript as an extension. -

    +

    Instantiate a Lexer for our use here.

    -
    if require.extensions
    -  for ext in ['.coffee', '.litcoffee', '.coffee.md']
    -    require.extensions[ext] = loadFile
    +
    lexer = new Lexer
    @@ -520,18 +519,27 @@ CoffeeScript as an extension.
    -

    Patch Node's module loader to be able to handle mult-dot extensions. -This is a horrible thing that should not be required. Perhaps, one day, -when a truly benevolent dictator comes to rule over the Republik of Node, -it won't be. -

    +

    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".

    -
      Module = require 'module'
    +            
    parser.lexer =
    +  lex: ->
    +    token = @tokens[@pos++]
    +    if token
    +      [tag, @yytext, @yylloc] = token
    +      @errorToken = token.origin or token
    +      @yylineno = @yylloc.first_line
    +    else
    +      tag = ''
     
    -  findExtension = (filename) ->
    -    extensions = path.basename(filename).split '.'
    + tag + setInput: (@tokens) -> + @pos = 0 + upcomingInput: -> + ""
    @@ -542,12 +550,11 @@ it won't be.
    -

    Remove the initial dot from dotfiles. -

    +

    Make all the AST nodes visible to the parser.

    -
        extensions.shift() if extensions[0] is ''
    +
    parser.yy = require './nodes'
    @@ -558,22 +565,11 @@ it won't be.
    -

    Start with the longest possible extension and work our way shortwards. -

    +

    Override Jison's default error handling function.

    -
        while extensions.shift()
    -      curExtension = '.' + extensions.join '.'
    -      return curExtension if Module._extensions[curExtension]
    -    '.js'
    -
    -  Module::load = (filename) ->
    -    @filename = filename
    -    @paths = Module._nodeModulePaths path.dirname filename
    -    extension = findExtension filename
    -    Module._extensions[extension](this, filename)
    -    @loaded = true
    +
    parser.yy.parseError = (message, {token}) ->
    @@ -584,21 +580,21 @@ it won't be.
    -

    If we're on Node, patch child_process.fork so that Coffee scripts are able -to fork both CoffeeScript files, and JavaScript files, directly. -

    +

    Disregard Jison's message, it contains redundant line numer information. +Disregard the token, we take its value directly from the lexer in case +the error is caused by a generated token which might refer to its origin.

    -
    if child_process
    -  {fork} = child_process
    -  child_process.fork = (path, args = [], options = {}) ->
    -    execPath = if helpers.isCoffee(path) then 'coffee' else null
    -    if not Array.isArray args
    -      args = []
    -      options = args or {}
    -    options.execPath or= execPath
    -    fork path, args, options
    +
      {errorToken, tokens} = parser.lexer
    +  [errorTag, errorText, errorLoc] = errorToken
    +
    +  errorText = if errorToken is tokens[tokens.length - 1]
    +    'end of input'
    +  else if errorTag in ['INDENT', 'OUTDENT']
    +    'indentation'
    +  else
    +    helpers.nameWhitespaceCharacter errorText
    @@ -609,12 +605,14 @@ to fork both CoffeeScript files, and JavaScript files, directly.
    -

    Instantiate a Lexer for our use here. -

    +

    The second argument has a loc property, which should have the location +data for this token. Unfortunately, Jison seems to send an outdated loc +(from the previous token), so we take the location information directly +from the lexer.

    -
    lexer = new Lexer
    +
      helpers.throwSyntaxError "unexpected #{errorText}", errorLoc
    @@ -625,27 +623,28 @@ to fork both CoffeeScript files, and JavaScript files, directly.
    -

    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". -

    +

    Based on http://v8.googlecode.com/svn/branches/bleeding_edge/src/messages.js +Modified to handle sourceMap

    -
    parser.lexer =
    -  lex: ->
    -    token = @tokens[@pos++]
    -    if token
    -      [tag, @yytext, @yylloc] = token
    -      @yylineno = @yylloc.first_line
    -    else
    -      tag = ''
    +            
    formatSourcePosition = (frame, getSourceMapping) ->
    +  fileName = undefined
    +  fileLocation = ''
     
    -    tag
    -  setInput: (@tokens) ->
    -    @pos = 0
    -  upcomingInput: ->
    -    ""
    + if frame.isNative() + fileLocation = "native" + else + if frame.isEval() + fileName = frame.getScriptNameOrSourceURL() + fileLocation = "#{frame.getEvalOrigin()}, " unless fileName + else + fileName = frame.getFileName() + + fileName or= "<anonymous>" + + line = frame.getLineNumber() + column = frame.getColumnNumber()
    @@ -656,12 +655,41 @@ directly as a "Jison lexer".
    -

    Make all the AST nodes visible to the parser. -

    +

    Check for a sourceMap position

    -
    parser.yy = require './nodes'
    +
        source = getSourceMapping fileName, line, column
    +    fileLocation =
    +      if source
    +        "#{fileName}:#{source[0]}:#{source[1]}"
    +      else
    +        "#{fileName}:#{line}:#{column}"
    +
    +  functionName = frame.getFunctionName()
    +  isConstructor = frame.isConstructor()
    +  isMethodCall = not (frame.isToplevel() or isConstructor)
    +
    +  if isMethodCall
    +    methodName = frame.getMethodName()
    +    typeName = frame.getTypeName()
    +
    +    if functionName
    +      tp = as = ''
    +      if typeName and functionName.indexOf typeName
    +        tp = "#{typeName}."
    +      if methodName and functionName.indexOf(".#{methodName}") isnt functionName.length - methodName.length - 1
    +        as = " [as #{methodName}]"
    +
    +      "#{tp}#{functionName}#{as} (#{fileLocation})"
    +    else
    +      "#{typeName}.#{methodName or '<anonymous>'} (#{fileLocation})"
    +  else if isConstructor
    +    "new #{functionName or '<anonymous>'} (#{fileLocation})"
    +  else if functionName
    +    "#{functionName} (#{fileLocation})"
    +  else
    +    fileLocation
    @@ -672,12 +700,11 @@ directly as a "Jison lexer".
    -

    Override Jison's default error handling function. -

    +

    Map of filenames -> sourceMap object.

    -
    parser.yy.parseError = (message, {token}) ->
    +
    sourceMaps = {}
    @@ -688,12 +715,15 @@ directly as a "Jison lexer".
    -

    Disregard Jison's message, it contains redundant line numer information. -

    +

    Generates the source map for a coffee file and stores it in the local cache variable.

    -
      message = "unexpected #{if token is 1 then 'end of input' else token}"
    +
    getSourceMap = (filename) ->
    +  return sourceMaps[filename] if sourceMaps[filename]
    +  return unless path?.extname(filename) in exports.FILE_EXTENSIONS
    +  answer = exports._compileFile filename, true
    +  sourceMaps[filename] = answer.sourceMap
    @@ -704,167 +734,24 @@ directly as a "Jison lexer".
    -

    The second argument has a loc property, which should have the location -data for this token. Unfortunately, Jison seems to send an outdated loc -(from the previous token), so we take the location information directly -from the lexer. -

    - - - -
      helpers.throwSyntaxError message, parser.lexer.yylloc
    - - - - -
  • -
    - -
    - -

    Based on michaelficarra/CoffeeScriptRedux NodeJS / V8 have no support for transforming positions in stack traces using sourceMap, so we must monkey-patch Error to display CoffeeScript source -positions. -

    +positions.

    -
    -patched = false
    - -
  • - - -
  • -
    - -
    - -
    -

    Map of filenames -> sourceMap object. -

    +
    Error.prepareStackTrace = (err, stack) ->
    +  getSourceMapping = (filename, line, column) ->
    +    sourceMap = getSourceMap filename
    +    answer = sourceMap.sourceLocation [line - 1, column - 1] if sourceMap
    +    if answer then [answer[0] + 1, answer[1] + 1] else null
     
    -            
    - -
    sourceMaps = {}
    +  frames = for frame in stack
    +    break if frame.getFunction() is exports.run
    +    "  at #{formatSourcePosition frame, getSourceMapping}"
     
    -patchStackTrace = ->
    -  return if patched
    -  patched = true
    -  mainModule = require.main
    - -
  • - - -
  • -
    - -
    - -
    -

    (Assigning to a property of the Module object in the normal module cache is -unsuitable, because node deletes those objects from the cache if an -exception is thrown in the module body.) -

    - -
    - -
    -  Error.prepareStackTrace = (err, stack) ->
    -    sourceFiles = {}
    -
    -    getSourceMapping = (filename, line, column) ->
    -      sourceMap = sourceMaps[filename]
    -      answer = sourceMap.sourceLocation [line - 1, column - 1] if sourceMap
    -      if answer then [answer[0] + 1, answer[1] + 1] else null
    -
    -    frames = for frame in stack
    -      break if frame.getFunction() is exports.run
    -      "  at #{formatSourcePosition frame, getSourceMapping}"
    -
    -    "#{err.name}: #{err.message ? ''}\n#{frames.join '\n'}\n"
    - -
  • - - -
  • -
    - -
    - -
    -

    Based on http://v8.googlecode.com/svn/branches/bleeding_edge/src/messages.js -Modified to handle sourceMap -

    - -
    - -
    formatSourcePosition = (frame, getSourceMapping) ->
    -  fileName = undefined
    -  fileLocation = ''
    -
    -  if frame.isNative()
    -    fileLocation = "native"
    -  else
    -    if frame.isEval()
    -      fileName = frame.getScriptNameOrSourceURL()
    -      fileLocation = "#{frame.getEvalOrigin()}, " unless fileName
    -    else
    -      fileName = frame.getFileName()
    -
    -    fileName or= "<anonymous>"
    -
    -    line = frame.getLineNumber()
    -    column = frame.getColumnNumber()
    - -
  • - - -
  • -
    - -
    - -
    -

    Check for a sourceMap position -

    - -
    - -
        source = getSourceMapping fileName, line, column
    -    fileLocation =
    -      if source
    -        "#{fileName}:#{source[0]}:#{source[1]}, <js>:#{line}:#{column}"
    -      else
    -        "#{fileName}:#{line}:#{column}"
    -
    -
    -  functionName = frame.getFunctionName()
    -  isConstructor = frame.isConstructor()
    -  isMethodCall = not (frame.isToplevel() or isConstructor)
    -
    -  if isMethodCall
    -    methodName = frame.getMethodName()
    -    typeName = frame.getTypeName()
    -
    -    if functionName
    -      tp = as = ''
    -      if typeName and functionName.indexOf typeName
    -        tp = "#{typeName}."
    -      if methodName and functionName.indexOf(".#{methodName}") isnt functionName.length - methodName.length - 1
    -        as = " [as #{methodName}]"
    -
    -      "#{tp}#{functionName}#{as} (#{fileLocation})"
    -    else
    -      "#{typeName}.#{methodName or '<anonymous>'} (#{fileLocation})"
    -  else if isConstructor
    -    "new #{functionName or '<anonymous>'} (#{fileLocation})"
    -  else if functionName
    -    "#{functionName} (#{fileLocation})"
    -  else
    -    fileLocation
    + "#{err.name}: #{err.message ? ''}\n#{frames.join '\n'}\n"
  • diff --git a/documentation/docs/command.html b/documentation/docs/command.html index 85b5b6cc..085c6551 100644 --- a/documentation/docs/command.html +++ b/documentation/docs/command.html @@ -69,6 +69,11 @@ + + register.coffee + + + repl.coffee @@ -112,11 +117,22 @@ into various forms: saved into .js files or printed to stdout or recompiled every time the source is saved, printed as a token stream or as the syntax tree, or launch an -interactive REPL. -

    +interactive REPL.

    +

    External dependencies.

    +
    fs             = require 'fs'
    +path           = require 'path'
    +helpers        = require './helpers'
    +optparse       = require './optparse'
    +CoffeeScript   = require './coffee-script'
    +mkdirp         = require 'mkdirp'
    +{spawn, exec}  = require 'child_process'
    +{EventEmitter} = require 'events'
    +
    +useWinPathSep  = path.sep is '\\'
    + @@ -126,21 +142,16 @@ interactive REPL.
    -

    External dependencies. -

    +

    Allow CoffeeScript to emit Node.js events.

    -
    fs             = require 'fs'
    -path           = require 'path'
    -helpers        = require './helpers'
    -optparse       = require './optparse'
    -CoffeeScript   = require './coffee-script'
    -{spawn, exec}  = require 'child_process'
    -{EventEmitter} = require 'events'
    +            
    helpers.extend CoffeeScript, new EventEmitter
     
    -exists         = fs.exists or path.exists
    -useWinPathSep  = path.sep is '\\'
    +printLine = (line) -> process.stdout.write line + '\n' +printWarn = (line) -> process.stderr.write line + '\n' + +hidden = (file) -> /^\.|~$/.test file
    @@ -151,17 +162,15 @@ useWinPathSep = path.sep is '
    -

    Allow CoffeeScript to emit Node.js events. -

    +

    The help banner that is printed in conjunction with -h/--help.

    -
    helpers.extend CoffeeScript, new EventEmitter
    +            
    BANNER = '''
    +  Usage: coffee [options] path/to/script.coffee -- [args]
     
    -printLine = (line) -> process.stdout.write line + '\n'
    -printWarn = (line) -> process.stderr.write line + '\n'
    -
    -hidden = (file) -> /^\.|~$/.test file
    + If called without options, `coffee` will run your script. +'''
    @@ -172,16 +181,29 @@ useWinPathSep = path.sep is '
    -

    The help banner that is printed when coffee is called without arguments. -

    +

    The list of all the valid option flags that coffee knows how to handle.

    -
    BANNER = '''
    -  Usage: coffee [options] path/to/script.coffee -- [args]
    -
    -  If called without options, `coffee` will run your script.
    -'''
    +
    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']
    +  ['-h', '--help',            'display this help message']
    +  ['-i', '--interactive',     'run an interactive CoffeeScript REPL']
    +  ['-j', '--join [FILE]',     'concatenate the source CoffeeScript before compiling']
    +  ['-m', '--map',             'generate source map and save as .map files']
    +  ['-n', '--nodes',           'print out the parse tree that the parser produces']
    +  [      '--nodejs [ARGS]',   'pass options directly to the "node" binary']
    +  [      '--no-header',       'suppress the "Generated by" header']
    +  ['-o', '--output [DIR]',    'set the output directory for compiled JavaScript']
    +  ['-p', '--print',           'print out the compiled JavaScript']
    +  ['-s', '--stdio',           'listen for and compile scripts over stdio']
    +  ['-l', '--literate',        'treat stdio as literate style coffee-script']
    +  ['-t', '--tokens',          'print out the tokens that the lexer/rewriter produce']
    +  ['-v', '--version',         'display the version number']
    +  ['-w', '--watch',           'watch scripts for changes and rerun commands']
    +]
    @@ -192,29 +214,16 @@ useWinPathSep = path.sep is '
    -

    The list of all the valid option flags that coffee knows how to handle. -

    +

    Top-level objects shared by all the functions.

    -
    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']
    -  ['-h', '--help',            'display this help message']
    -  ['-i', '--interactive',     'run an interactive CoffeeScript REPL']
    -  ['-j', '--join [FILE]',     'concatenate the source CoffeeScript before compiling']
    -  ['-m', '--map',             'generate source map and save as .map files']
    -  ['-n', '--nodes',           'print out the parse tree that the parser produces']
    -  [      '--nodejs [ARGS]',   'pass options directly to the "node" binary']
    -  ['-o', '--output [DIR]',    'set the output directory for compiled JavaScript']
    -  ['-p', '--print',           'print out the compiled JavaScript']
    -  ['-s', '--stdio',           'listen for and compile scripts over stdio']
    -  ['-l', '--literate',        'treat stdio as literate style coffee-script']
    -  ['-t', '--tokens',          'print out the tokens that the lexer/rewriter produce']
    -  ['-v', '--version',         'display the version number']
    -  ['-w', '--watch',           'watch scripts for changes and rerun commands']
    -]
    +
    opts         = {}
    +sources      = []
    +sourceCode   = []
    +notSources   = {}
    +watchedDirs  = {}
    +optionParser = null
    @@ -225,17 +234,14 @@ useWinPathSep = path.sep is '
    -

    Top-level objects shared by all the functions. -

    +

    Run 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

    -
    opts         = {}
    -sources      = []
    -sourceCode   = []
    -notSources   = {}
    -watchers     = {}
    -optionParser = null
    +
    exports.run = ->
    +  parseOptions()
    @@ -246,29 +252,29 @@ optionParser = null
    -

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

    +

    Make the REPL CLI use the global context so as to (a) be consistent with the +node REPL CLI and, therefore, (b) make packages that modify native prototypes +(such as 'colors' and 'sugar') work as expected.

    -
    exports.run = ->
    -  parseOptions()
    -  return forkNode()                      if opts.nodejs
    -  return usage()                         if opts.help
    -  return version()                       if opts.version
    -  return require('./repl').start()       if opts.interactive
    -  if opts.watch and not fs.watch
    -    return printWarn "The --watch feature depends on Node v0.6.0+. You are running #{process.version}."
    -  return compileStdio()                  if opts.stdio
    -  return compileScript null, sources[0]  if opts.eval
    -  return require('./repl').start()       unless sources.length
    -  literals = if opts.run then sources.splice 1 else []
    -  process.argv = process.argv[0..1].concat literals
    -  process.argv[0] = 'coffee'
    -  for source in sources
    -    compilePath source, yes, path.normalize source
    +
      replCliOpts = useGlobal: yes
    +  return forkNode()                             if opts.nodejs
    +  return usage()                                if opts.help
    +  return version()                              if opts.version
    +  return require('./repl').start(replCliOpts)   if opts.interactive
    +  return compileStdio()                         if opts.stdio
    +  return compileScript null, opts.arguments[0]  if opts.eval
    +  return require('./repl').start(replCliOpts)   unless opts.arguments.length
    +  literals = if opts.run then opts.arguments.splice 1 else []
    +  process.argv = process.argv[0..1].concat literals
    +  process.argv[0] = 'coffee'
    +
    +  opts.output = path.resolve opts.output  if opts.output
    +  opts.join   = path.resolve opts.join    if opts.join
    +  for source in opts.arguments
    +    source = path.resolve source
    +    compilePath source, yes, source
    @@ -281,37 +287,57 @@ Many flags cause us to divert before compiling anything. Flags passed after

    Compile a path, which could be a script or a directory. If a directory is passed, recursively compile all '.coffee', '.litcoffee', and '.coffee.md' -extension source files in it and all subdirectories. -

    +extension source files in it and all subdirectories.

    -
    compilePath = (source, topLevel, base) ->
    -  fs.stat source, (err, stats) ->
    -    throw err if err and err.code isnt 'ENOENT'
    -    if err?.code is 'ENOENT'
    -      console.error "File not found: #{source}"
    -      process.exit 1
    -    if stats.isDirectory() and path.dirname(source) isnt 'node_modules'
    -      watchDir source, base if opts.watch
    -      fs.readdir source, (err, files) ->
    -        throw err if err and err.code isnt 'ENOENT'
    -        return if err?.code is 'ENOENT'
    -        index = sources.indexOf source
    -        files = files.filter (file) -> not hidden file
    -        sources[index..index] = (path.join source, file for file in files)
    -        sourceCode[index..index] = files.map -> null
    -        files.forEach (file) ->
    -          compilePath (path.join source, file), no, base
    -    else if topLevel or helpers.isCoffee source
    -      watch source, base if opts.watch
    -      fs.readFile source, (err, code) ->
    -        throw err if err and err.code isnt 'ENOENT'
    -        return if err?.code is 'ENOENT'
    -        compileScript(source, code.toString(), base)
    -    else
    -      notSources[source] = yes
    -      removeSource source, base
    +
    compilePath = (source, topLevel, base) ->
    +  return if source in sources   or
    +            watchedDirs[source] or
    +            not topLevel and (notSources[source] or hidden source)
    +  try
    +    stats = fs.statSync source
    +  catch err
    +    if err.code is 'ENOENT'
    +      console.error "File not found: #{source}"
    +      process.exit 1
    +    throw err
    +  if stats.isDirectory()
    +    if path.basename(source) is 'node_modules'
    +      notSources[source] = yes
    +      return
    +    if opts.run
    +      compilePath findDirectoryIndex(source), topLevel, base
    +      return
    +    watchDir source, base if opts.watch
    +    try
    +      files = fs.readdirSync source
    +    catch err
    +      if err.code is 'ENOENT' then return else throw err
    +    for file in files
    +      compilePath (path.join source, file), no, base
    +  else if topLevel or helpers.isCoffee source
    +    sources.push source
    +    sourceCode.push null
    +    delete notSources[source]
    +    watch source, base if opts.watch
    +    try
    +      code = fs.readFileSync source
    +    catch err
    +      if err.code is 'ENOENT' then return else throw err
    +    compileScript(source, code.toString(), base)
    +  else
    +    notSources[source] = yes
    +
    +findDirectoryIndex = (source) ->
    +  for ext in CoffeeScript.FILE_EXTENSIONS
    +    index = path.join source, "index#{ext}"
    +    try
    +      return index if (fs.statSync index).isFile()
    +    catch err
    +      throw err unless err.code is 'ENOENT'
    +  console.error "Missing index.coffee or index.litcoffee in #{source}"
    +  process.exit 1
    @@ -324,46 +350,48 @@ extension source files in it and all subdirectories.

    Compile a single source script, containing the given code, according to the requested options. If evaluating the script directly sets __filename, -__dirname and module.filename to be correct relative to the script's path. -

    +__dirname and module.filename to be correct relative to the script's path.

    -
    compileScript = (file, input, base=null) ->
    +            
    compileScript = (file, input, base = null) ->
       o = opts
       options = compileOptions file, base
    -  try
    +  try
         t = task = {file, input, options}
    -    CoffeeScript.emit 'compile', task
    -    if      o.tokens      then printTokens CoffeeScript.tokens t.input, t.options
    -    else if o.nodes       then printLine CoffeeScript.nodes(t.input, t.options).toString().trim()
    -    else if o.run         then CoffeeScript.run t.input, t.options
    -    else if o.join and t.file isnt o.join
    -      t.input = helpers.invertLiterate t.input if helpers.isLiterate file
    +    CoffeeScript.emit 'compile', task
    +    if o.tokens
    +      printTokens CoffeeScript.tokens t.input, t.options
    +    else if o.nodes
    +      printLine CoffeeScript.nodes(t.input, t.options).toString().trim()
    +    else if o.run
    +      CoffeeScript.register()
    +      CoffeeScript.run t.input, t.options
    +    else if o.join and t.file isnt o.join
    +      t.input = helpers.invertLiterate t.input if helpers.isLiterate file
           sourceCode[sources.indexOf(t.file)] = t.input
           compileJoin()
    -    else
    +    else
           compiled = CoffeeScript.compile t.input, t.options
           t.output = compiled
    -      if o.map
    +      if o.map
             t.output = compiled.js
             t.sourceMap = compiled.v3SourceMap
     
    -      CoffeeScript.emit 'success', task
    -      if o.print
    +      CoffeeScript.emit 'success', task
    +      if o.print
             printLine t.output.trim()
    -      else if o.compile or o.map
    +      else if o.compile or o.map
             writeJs base, t.file, t.output, options.jsPath, t.sourceMap
    -  catch err
    -    CoffeeScript.emit 'failure', err, task
    -    return if CoffeeScript.listeners('failure').length
    -    useColors = process.stdout.isTTY and not process.env.NODE_DISABLE_COLORS
    -    message = helpers.prettyErrorMessage err, file or '[stdin]', input, useColors
    -    if o.watch
    -      printLine message + '\x07'
    -    else
    +  catch err
    +    CoffeeScript.emit 'failure', err, task
    +    return if CoffeeScript.listeners('failure').length
    +    message = err.stack or "#{err}"
    +    if o.watch
    +      printLine message + '\x07'
    +    else
           printWarn message
    -      process.exit 1
    + process.exit 1
    @@ -375,18 +403,17 @@ requested options. If evaluating the script directly sets __filename

    Attach the appropriate listeners to compile scripts incoming over stdin, -and write them back to stdout. -

    +and write them back to stdout.

    -
    compileStdio = ->
    -  code = ''
    +            
    compileStdio = ->
    +  code = ''
       stdin = process.openStdin()
    -  stdin.on 'data', (buffer) ->
    -    code += buffer.toString() if buffer
    -  stdin.on 'end', ->
    -    compileScript null, code
    + stdin.on 'data', (buffer) -> + code += buffer.toString() if buffer + stdin.on 'end', -> + compileScript null, code
    @@ -398,18 +425,17 @@ and write them back to stdout.

    If all of the source files are done being read, concatenate and compile -them together. -

    +them together.

    -
    joinTimeout = null
    -compileJoin = ->
    -  return unless opts.join
    -  unless sourceCode.some((code) -> code is null)
    -    clearTimeout joinTimeout
    -    joinTimeout = wait 100, ->
    -      compileScript opts.join, sourceCode.join('\n'), opts.join
    +
    joinTimeout = null
    +compileJoin = ->
    +  return unless opts.join
    +  unless sourceCode.some((code) -> code is null)
    +    clearTimeout joinTimeout
    +    joinTimeout = wait 100, ->
    +      compileScript opts.join, sourceCode.join('\n'), opts.join
    @@ -422,48 +448,54 @@ them together.

    Watch a source CoffeeScript file using fs.watch, recompiling it every time the file is updated. May be used in combination with other options, -such as --print. -

    +such as --print.

    -
    watch = (source, base) ->
    +            
    watch = (source, base) ->
    +  watcher        = null
    +  prevStats      = null
    +  compileTimeout = null
     
    -  prevStats = null
    -  compileTimeout = null
    +  watchErr = (err) ->
    +    throw err unless err.code is 'ENOENT'
    +    return unless source in sources
    +    try
    +      rewatch()
    +      compile()
    +    catch
    +      removeSource source, base
    +      compileJoin()
     
    -  watchErr = (e) ->
    -    if e.code is 'ENOENT'
    -      return if sources.indexOf(source) is -1
    -      try
    -        rewatch()
    -        compile()
    -      catch e
    -        removeSource source, base, yes
    -        compileJoin()
    -    else throw e
    -
    -  compile = ->
    +  compile = ->
         clearTimeout compileTimeout
    -    compileTimeout = wait 25, ->
    -      fs.stat source, (err, stats) ->
    -        return watchErr err if err
    -        return rewatch() if prevStats and stats.size is prevStats.size and
    -          stats.mtime.getTime() is prevStats.mtime.getTime()
    +    compileTimeout = wait 25, ->
    +      fs.stat source, (err, stats) ->
    +        return watchErr err if err
    +        return rewatch() if prevStats and
    +                            stats.size is prevStats.size and
    +                            stats.mtime.getTime() is prevStats.mtime.getTime()
             prevStats = stats
    -        fs.readFile source, (err, code) ->
    -          return watchErr err if err
    +        fs.readFile source, (err, code) ->
    +          return watchErr err if err
               compileScript(source, code.toString(), base)
               rewatch()
     
    -  try
    -    watcher = fs.watch source, compile
    -  catch e
    -    watchErr e
    +  startWatcher = ->
    +    watcher = fs.watch source
    +    .on 'change', compile
    +    .on 'error', (err) ->
    +      throw err unless err.code is 'EPERM'
    +      removeSource source, base
     
    -  rewatch = ->
    +  rewatch = ->
         watcher?.close()
    -    watcher = fs.watch source, compile
    + startWatcher() + + try + startWatcher() + catch err + watchErr err
    @@ -474,37 +506,47 @@ such as --print.
    -

    Watch a directory of files for new additions. -

    +

    Watch a directory of files for new additions.

    -
    watchDir = (source, base) ->
    -  readdirTimeout = null
    -  try
    -    watcher = fs.watch source, ->
    -      clearTimeout readdirTimeout
    -      readdirTimeout = wait 25, ->
    -        fs.readdir source, (err, files) ->
    -          if err
    -            throw err unless err.code is 'ENOENT'
    -            watcher.close()
    -            return unwatchDir source, base
    -          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
    -            compilePath file, no, base
    -  catch e
    -    throw e unless e.code is 'ENOENT'
    +            
    watchDir = (source, base) ->
    +  watcher        = null
    +  readdirTimeout = null
     
    -unwatchDir = (source, base) ->
    -  prevSources = sources[..]
    -  toRemove = (file for file in sources when file.indexOf(source) >= 0)
    -  removeSource file, base, yes for file in toRemove
    -  return unless sources.some (s, i) -> prevSources[i] isnt s
    -  compileJoin()
    + startWatcher = -> + watcher = fs.watch source + .on 'error', (err) -> + throw err unless err.code is 'EPERM' + stopWatcher() + .on 'change', -> + clearTimeout readdirTimeout + readdirTimeout = wait 25, -> + try + files = fs.readdirSync source + catch err + throw err unless err.code is 'ENOENT' + return stopWatcher() + for file in files + compilePath (path.join source, file), no, base + + stopWatcher = -> + watcher.close() + removeSourceDir source, base + + watchedDirs[source] = yes + try + startWatcher() + catch err + throw err unless err.code is 'ENOENT' + +removeSourceDir = (source, base) -> + delete watchedDirs[source] + sourcesChanged = no + for file in sources when source is path.dirname file + removeSource file, base + sourcesChanged = yes + compileJoin() if sourcesChanged
    @@ -516,22 +558,24 @@ such as --print.

    Remove a file from our source list, and source code cache. Optionally remove -the compiled JS version as well. -

    +the compiled JS version as well.

    -
    removeSource = (source, base, removeJs) ->
    +            
    removeSource = (source, base) ->
       index = sources.indexOf source
    -  sources.splice index, 1
    -  sourceCode.splice index, 1
    -  if removeJs and not opts.join
    -    jsPath = outputPath source, base
    -    exists jsPath, (itExists) ->
    -      if itExists
    -        fs.unlink jsPath, (err) ->
    -          throw err if err and err.code isnt 'ENOENT'
    -          timeLog "removed #{source}"
    + sources.splice index, 1 + sourceCode.splice index, 1 + unless opts.join + silentUnlink outputPath source, base + silentUnlink outputPath source, base, '.map' + timeLog "removed #{source}" + +silentUnlink = (path) -> + try + fs.unlinkSync path + catch err + throw err unless err.code in ['ENOENT', 'EPERM']
    @@ -542,16 +586,19 @@ the compiled JS version as well.
    -

    Get the corresponding output JavaScript path for a source file. -

    +

    Get the corresponding output JavaScript path for a source file.

    -
    outputPath = (source, base, extension=".js") ->
    -  basename  = helpers.baseFileName source, yes, useWinPathSep
    +            
    outputPath = (source, base, extension=".js") ->
    +  basename  = helpers.baseFileName source, yes, useWinPathSep
       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
    +  if not opts.output
    +    dir = srcDir
    +  else if source is base
    +    dir = opts.output
    +  else
    +    dir = path.join opts.output, path.relative base, srcDir
       path.join dir, basename + extension
    @@ -565,33 +612,30 @@ the compiled JS version as well.

    Write out a JavaScript source file with the compiled code. By default, files are written out in cwd as .js files with the same name, but the output -directory can be customized with --output. - -

    +directory can be customized with --output.

    If generatedSourceMap is provided, this will write a .map file into the -same directory as the .js file. -

    +same directory as the .js file.

    -
    writeJs = (base, sourcePath, js, jsPath, generatedSourceMap = null) ->
    -  sourceMapPath = outputPath sourcePath, base, ".map"
    +            
    writeJs = (base, sourcePath, js, jsPath, generatedSourceMap = null) ->
    +  sourceMapPath = outputPath sourcePath, base, ".map"
       jsDir  = path.dirname jsPath
    -  compile = ->
    -    if opts.compile
    -      js = ' ' if js.length <= 0
    -      if generatedSourceMap then js = "#{js}\n/*\n//@ sourceMappingURL=#{helpers.baseFileName sourceMapPath, no, useWinPathSep}\n*/\n"
    -      fs.writeFile jsPath, js, (err) ->
    -        if err
    +  compile = ->
    +    if opts.compile
    +      js = ' ' if js.length <= 0
    +      if generatedSourceMap then js = "#{js}\n//# sourceMappingURL=#{helpers.baseFileName sourceMapPath, no, useWinPathSep}\n"
    +      fs.writeFile jsPath, js, (err) ->
    +        if err
               printLine err.message
    -        else if opts.compile and opts.watch
    -          timeLog "compiled #{sourcePath}"
    -    if generatedSourceMap
    -      fs.writeFile sourceMapPath, generatedSourceMap, (err) ->
    -        if err
    -          printLine "Could not write source map: #{err.message}"
    -  exists jsDir, (itExists) ->
    -    if itExists then compile() else exec "mkdir -p #{jsDir}", compile
    + else if opts.compile and opts.watch + timeLog "compiled #{sourcePath}" + if generatedSourceMap + fs.writeFile sourceMapPath, generatedSourceMap, (err) -> + if err + printLine "Could not write source map: #{err.message}" + fs.exists jsDir, (itExists) -> + if itExists then compile() else mkdirp jsDir, compile
    @@ -602,12 +646,11 @@ same directory as the .js file.
    -

    Convenience for cleaner setTimeouts. -

    +

    Convenience for cleaner setTimeouts.

    -
    wait = (milliseconds, func) -> setTimeout func, milliseconds
    +
    wait = (milliseconds, func) -> setTimeout func, milliseconds
    @@ -618,13 +661,12 @@ same directory as the .js file.
    -

    When watching scripts, it's useful to log changes with the timestamp. -

    +

    When watching scripts, it's useful to log changes with the timestamp.

    -
    timeLog = (message) ->
    -  console.log "#{(new Date).toLocaleTimeString()} - #{message}"
    +
    timeLog = (message) ->
    +  console.log "#{(new Date).toLocaleTimeString()} - #{message}"
    @@ -635,17 +677,16 @@ same directory as the .js file.
    -

    Pretty-print a stream of tokens, sans location data. -

    +

    Pretty-print a stream of tokens, sans location data.

    -
    printTokens = (tokens) ->
    -  strings = for token in tokens
    -    tag = token[0]
    -    value = token[1].toString().replace(/\n/, '\\n')
    -    "[#{tag} #{value}]"
    -  printLine strings.join(' ')
    +
    printTokens = (tokens) ->
    +  strings = for token in tokens
    +    tag = token[0]
    +    value = token[1].toString().replace(/\n/, '\\n')
    +    "[#{tag} #{value}]"
    +  printLine strings.join(' ')
    @@ -657,20 +698,16 @@ same directory as the .js file.

    Use the OptionParser module to extract all options from -process.argv that are specified in SWITCHES. -

    +process.argv that are specified in SWITCHES.

    -
    parseOptions = ->
    -  optionParser  = new optparse.OptionParser SWITCHES, BANNER
    -  o = opts      = optionParser.parse process.argv[2..]
    -  o.compile     or=  !!o.output
    -  o.run         = not (o.compile or o.print or o.map)
    -  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
    +
    parseOptions = ->
    +  optionParser  = new optparse.OptionParser SWITCHES, BANNER
    +  o = opts      = optionParser.parse process.argv[2..]
    +  o.compile     or=  !!o.output
    +  o.run         = not (o.compile or o.print or o.map)
    +  o.print       = !!  (o.print or (o.eval or o.stdio and o.compile))
    @@ -681,35 +718,34 @@ same directory as the .js file.
    -

    The compile-time options to pass to the CoffeeScript compiler. -

    +

    The compile-time options to pass to the CoffeeScript compiler.

    -
    compileOptions = (filename, base) ->
    +            
    compileOptions = (filename, base) ->
       answer = {
         filename
    -    literate: opts.literate or helpers.isLiterate(filename)
    -    bare: opts.bare
    -    header: opts.compile
    -    sourceMap: opts.map
    +    literate: opts.literate or helpers.isLiterate(filename)
    +    bare: opts.bare
    +    header: opts.compile and not opts['no-header']
    +    sourceMap: opts.map
       }
    -  if filename
    -    if base
    +  if filename
    +    if base
           cwd = process.cwd()
           jsPath = outputPath filename, base
           jsDir = path.dirname jsPath
           answer = helpers.merge answer, {
             jsPath
    -        sourceRoot: path.relative jsDir, cwd
    -        sourceFiles: [path.relative cwd, filename]
    -        generatedFile: helpers.baseFileName(jsPath, no, useWinPathSep)
    +        sourceRoot: path.relative jsDir, cwd
    +        sourceFiles: [path.relative cwd, filename]
    +        generatedFile: helpers.baseFileName(jsPath, no, useWinPathSep)
           }
    -    else
    +    else
           answer = helpers.merge answer,
    -        sourceRoot: ""
    -        sourceFiles: [helpers.baseFileName filename, no, useWinPathSep]
    -        generatedFile: helpers.baseFileName(filename, yes, useWinPathSep) + ".js"
    +        sourceRoot: ""
    +        sourceFiles: [helpers.baseFileName filename, no, useWinPathSep]
    +        generatedFile: helpers.baseFileName(filename, yes, useWinPathSep) + ".js"
       answer
    @@ -722,19 +758,19 @@ same directory as the .js file.

    Start up a new Node.js instance with the arguments in --nodejs passed to -the node binary, preserving the other options. -

    +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]
    +
    forkNode = ->
    +  nodeArgs = opts.nodejs.split /\s+/
    +  args     = process.argv[1..]
    +  args.splice args.indexOf('--nodejs'), 2
    +  p = spawn process.execPath, nodeArgs.concat(args),
    +    cwd:        process.cwd()
    +    env:        process.env
    +    customFds:  [0, 1, 2]
    +  p.on 'exit', (code) -> process.exit code
    @@ -746,13 +782,12 @@ the node binary, preserving the other options.

    Print the --help usage message and exit. Deprecated switches are not -shown. -

    +shown.

    -
    usage = ->
    -  printLine (new optparse.OptionParser SWITCHES, BANNER).help()
    +
    usage = ->
    +  printLine (new optparse.OptionParser SWITCHES, BANNER).help()
    @@ -763,13 +798,12 @@ shown.
    -

    Print the --version message and exit. -

    +

    Print the --version message and exit.

    -
    version = ->
    -  printLine "CoffeeScript version #{CoffeeScript.VERSION}"
    +
    version = ->
    +  printLine "CoffeeScript version #{CoffeeScript.VERSION}"
    diff --git a/documentation/docs/docco.css b/documentation/docs/docco.css index 714cfb99..f690a079 100644 --- a/documentation/docs/docco.css +++ b/documentation/docs/docco.css @@ -51,17 +51,9 @@ b, strong { font-family: "aller-bold"; } -p { +p, ul, ol { margin: 15px 0 0px; } - .annotation ul, .annotation ol { - margin: 25px 0; - } - .annotation ul li, .annotation ol li { - font-size: 14px; - line-height: 18px; - margin: 10px 0; - } h1, h2, h3, h4, h5, h6 { color: #112233; @@ -399,8 +391,7 @@ pre .css .rule .keyword, pre .winutils, pre .javascript .title, pre .lisp .title, -pre .subst, -pre .reserved { +pre .subst { color: #954121; /*font-weight: bold*/ } diff --git a/documentation/docs/grammar.html b/documentation/docs/grammar.html index 021c361a..37b2a3e9 100644 --- a/documentation/docs/grammar.html +++ b/documentation/docs/grammar.html @@ -69,6 +69,11 @@ + + register.coffee + + + repl.coffee @@ -119,15 +124,15 @@ shifts tokens from our token stream, from left to right, and attempts to match the token sequence against the rules below. When a match can be made, it reduces into the nonterminal -(the enclosing name at the top), and we proceed from there. - -

    +(the enclosing name at the top), and we proceed from there.

    If you run the cake build:parser command, Jison constructs a parse table -from our rules and saves it into lib/parser.js. -

    +from our rules and saves it into lib/parser.js.

    +

    The only dependency is on the Jison.Parser.

    +
    {Parser} = require 'jison'
    + @@ -137,26 +142,27 @@ from our rules and saves it into lib/parser.js.
    -

    The only dependency is on the Jison.Parser. -

    +

    Jison DSL

    -
    {Parser} = require 'jison'
    -
  • -
    +
    -

    Jison DSL

    +

    Since we're going to be wrapped in a function by Jison in any case, if our +action immediately returns a value, we can optimize by removing the function +wrapper and just returning the value directly.

    +
    unwrap = /^function\s*\(\)\s*\{\s*return\s*([\s\S]*);\s*\}/
    +
  • @@ -166,9 +172,20 @@ from our rules and saves it into lib/parser.js.
    - +

    Our handy DSL for Jison grammar generation, thanks to +Tim Caswell. For every rule in the grammar, +we pass the pattern-defining string, the action to run, and extra options, +optionally. If no action is specified, we simply pass the value of the +previous nonterminal.

    + +
    o = (patternString, action, options) ->
    +  patternString = patternString.replace /\s{2,}/g, ' '
    +  patternCount = patternString.split(' ').length
    +  return [patternString, '$$ = $1;', options] unless action
    +  action = if match = unwrap.exec action then match[1] else "(#{action}())"
    + @@ -178,14 +195,12 @@ from our rules and saves it into lib/parser.js.
    -

    Since we're going to be wrapped in a function by Jison in any case, if our -action immediately returns a value, we can optimize by removing the function -wrapper and just returning the value directly. -

    +

    All runtime functions we need are defined on "yy"

    -
    unwrap = /^function\s*\(\)\s*\{\s*return\s*([\s\S]*);\s*\}/
    +
      action = action.replace /\bnew /g, '$&yy.'
    +  action = action.replace /\b(?:Block\.wrap|extend)\b/g, 'yy.$&'
    @@ -196,20 +211,22 @@ wrapper and just returning the value directly.
    -

    Our handy DSL for Jison grammar generation, thanks to -Tim Caswell. For every rule in the grammar, -we pass the pattern-defining string, the action to run, and extra options, -optionally. If no action is specified, we simply pass the value of the -previous nonterminal. -

    +

    Returns a function which adds location data to the first parameter passed +in, and returns the parameter. If the parameter is not a node, it will +just be passed through unaffected.

    -
    o = (patternString, action, options) ->
    -  patternString = patternString.replace /\s{2,}/g, ' '
    -  patternCount = patternString.split(' ').length
    -  return [patternString, '$$ = $1;', options] unless action
    -  action = if match = unwrap.exec action then match[1] else "(#{action}())"
    +
      addLocationDataFn = (first, last) ->
    +    if not last
    +      "yy.addLocationDataFn(@#{first})"
    +    else
    +      "yy.addLocationDataFn(@#{first}, @#{last})"
    +
    +  action = action.replace /LOC\(([0-9]*)\)/g, addLocationDataFn('$1')
    +  action = action.replace /LOC\(([0-9]*),\s*([0-9]*)\)/g, addLocationDataFn('$1', '$2')
    +
    +  [patternString, "$$ = #{addLocationDataFn(1, patternCount)}(#{action});", options]
    @@ -220,14 +237,10 @@ previous nonterminal.
    -

    All runtime functions we need are defined on "yy" -

    +

    Grammatical Rules

    -
      action = action.replace /\bnew /g, '$&yy.'
    -  action = action.replace /\b(?:Block\.wrap|extend)\b/g, 'yy.$&'
    - @@ -237,23 +250,18 @@ previous nonterminal.
    -

    Returns a function which adds location data to the first parameter passed -in, and returns the parameter. If the parameter is not a node, it will -just be passed through unaffected. -

    +

    In all of the rules that follow, you'll see the name of the nonterminal as +the key to a list of alternative matches. With each match's action, the +dollar-sign variables are provided by Jison as references to the value of +their numeric position, so in this rule:

    +
    "Expression UNLESS Expression"
    +

    $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.

    -
      addLocationDataFn = (first, last) ->
    -    if not last
    -      "yy.addLocationDataFn(@#{first})"
    -    else
    -      "yy.addLocationDataFn(@#{first}, @#{last})"
    -
    -  action = action.replace /LOC\(([0-9]*)\)/g, addLocationDataFn('$1')
    -  action = action.replace /LOC\(([0-9]*),\s*([0-9]*)\)/g, addLocationDataFn('$1', '$2')
    -
    -  [patternString, "$$ = #{addLocationDataFn(1, patternCount)}(#{action});", options]
    +
    grammar =
    @@ -261,13 +269,19 @@ just be passed through unaffected.
  • -
    +
    -

    Grammatical Rules

    +

    The Root is the top-level node in the syntax tree. Since we parse bottom-up, +all parsing must end here.

    +
      Root: [
    +    o '',                                       -> new Block
    +    o 'Body'
    +  ]
    +
  • @@ -277,9 +291,16 @@ just be passed through unaffected.
    - +

    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'
    +  ]
    + @@ -289,21 +310,14 @@ just be passed through unaffected.
    -

    In all of the rules that follow, you'll see the name of the nonterminal as -the key to a list of alternative matches. With each match's action, the -dollar-sign variables are provided by Jison as references to the value of -their numeric position, so in this rule: - -

    -
    "Expression UNLESS Expression"
    -

    $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. -

    +

    Block and statements, which make up a line in a body.

    -
    grammar =
    +
      Line: [
    +    o 'Expression'
    +    o 'Statement'
    +  ]
    @@ -314,16 +328,14 @@ for the UNLESS terminal, and $3 would be the value of
    -

    The Root is the top-level node in the syntax tree. Since we parse bottom-up, -all parsing must end here. -

    +

    Pure statements which cannot be expressions.

    -
      Root: [
    -    o '',                                       -> new Block
    -    o 'Body'
    -    o 'Block TERMINATOR'
    +            
      Statement: [
    +    o 'Return'
    +    o 'Comment'
    +    o 'STATEMENT',                              -> new Literal $1
       ]
    @@ -335,15 +347,26 @@ all parsing must end here.
    -

    Any list of statements and expressions, separated by line breaks or semicolons. -

    +

    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.

    -
      Body: [
    -    o 'Line',                                   -> Block.wrap [$1]
    -    o 'Body TERMINATOR Line',                   -> $1.push $3
    -    o 'Body TERMINATOR'
    +            
      Expression: [
    +    o 'Value'
    +    o 'Invocation'
    +    o 'Code'
    +    o 'Operation'
    +    o 'Assign'
    +    o 'If'
    +    o 'Try'
    +    o 'While'
    +    o 'For'
    +    o 'Switch'
    +    o 'Class'
    +    o 'Throw'
       ]
    @@ -355,14 +378,15 @@ all parsing must end here.
    -

    Block and statements, which make up a line in a body. -

    +

    An indented block of expressions. Note that the Rewriter +will convert some postfix forms into blocks for us, by adjusting the +token stream.

    -
      Line: [
    -    o 'Expression'
    -    o 'Statement'
    +            
      Block: [
    +    o 'INDENT OUTDENT',                         -> new Block
    +    o 'INDENT Body OUTDENT',                    -> $2
       ]
    @@ -374,15 +398,12 @@ all parsing must end here.
    -

    Pure statements which cannot be expressions. -

    +

    A literal identifier, a variable name or property.

    -
      Statement: [
    -    o 'Return'
    -    o 'Comment'
    -    o 'STATEMENT',                              -> new Literal $1
    +            
      Identifier: [
    +    o 'IDENTIFIER',                             -> new Literal $1
       ]
    @@ -394,27 +415,14 @@ all parsing must end here.
    -

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

    +

    Alphanumerics are separated from the other Literal matchers because +they can also serve as keys in object literals.

    -
      Expression: [
    -    o 'Value'
    -    o 'Invocation'
    -    o 'Code'
    -    o 'Operation'
    -    o 'Assign'
    -    o 'If'
    -    o 'Try'
    -    o 'While'
    -    o 'For'
    -    o 'Switch'
    -    o 'Class'
    -    o 'Throw'
    +            
      AlphaNumeric: [
    +    o 'NUMBER',                                 -> new Literal $1
    +    o 'STRING',                                 -> new Literal $1
       ]
    @@ -426,16 +434,19 @@ 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. -

    +

    All of our immediate values. Generally these can be passed straight +through and printed to JavaScript.

    -
      Block: [
    -    o 'INDENT OUTDENT',                         -> new Block
    -    o 'INDENT Body OUTDENT',                    -> $2
    +            
      Literal: [
    +    o 'AlphaNumeric'
    +    o 'JS',                                     -> new Literal $1
    +    o 'REGEX',                                  -> new Literal $1
    +    o 'DEBUGGER',                               -> new Literal $1
    +    o 'UNDEFINED',                              -> new Undefined
    +    o 'NULL',                                   -> new Null
    +    o 'BOOL',                                   -> new Bool $1
       ]
    @@ -447,13 +458,14 @@ token stream.
    -

    A literal identifier, a variable name or property. -

    +

    Assignment of a variable, property, or index to a value.

    -
      Identifier: [
    -    o 'IDENTIFIER',                             -> new Literal $1
    +            
      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
       ]
    @@ -465,15 +477,23 @@ token stream.
    -

    Alphanumerics are separated from the other Literal matchers because -they can also serve as keys in object literals. -

    +

    Assignment when it happens within an object literal. The difference from +the ordinary Assign is that these allow numbers and strings as keys.

    -
      AlphaNumeric: [
    -    o 'NUMBER',                                 -> new Literal $1
    -    o 'STRING',                                 -> new Literal $1
    +            
      AssignObj: [
    +    o 'ObjAssignable',                          -> new Value $1
    +    o 'ObjAssignable : Expression',             -> new Assign LOC(1)(new Value($1)), $3, 'object'
    +    o 'ObjAssignable :
    +       INDENT Expression OUTDENT',              -> new Assign LOC(1)(new Value($1)), $4, 'object'
    +    o 'Comment'
    +  ]
    +
    +  ObjAssignable: [
    +    o 'Identifier'
    +    o 'AlphaNumeric'
    +    o 'ThisProperty'
       ]
    @@ -485,20 +505,13 @@ they can also serve as keys in object literals.
    -

    All of our immediate values. Generally these can be passed straight -through and printed to JavaScript. -

    +

    A return statement from a function body.

    -
      Literal: [
    -    o 'AlphaNumeric'
    -    o 'JS',                                     -> new Literal $1
    -    o 'REGEX',                                  -> new Literal $1
    -    o 'DEBUGGER',                               -> new Literal $1
    -    o 'UNDEFINED',                              -> new Undefined
    -    o 'NULL',                                   -> new Null
    -    o 'BOOL',                                   -> new Bool $1
    +            
      Return: [
    +    o 'RETURN Expression',                      -> new Return $2
    +    o 'RETURN',                                 -> new Return
       ]
    @@ -510,15 +523,12 @@ through and printed to JavaScript.
    -

    Assignment of a variable, property, or index to a value. -

    +

    A block comment.

    -
      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
    +            
      Comment: [
    +    o 'HERECOMMENT',                            -> new Comment $1
       ]
    @@ -530,24 +540,15 @@ through and printed to JavaScript.
    -

    Assignment when it happens within an object literal. The difference from -the ordinary Assign is that these allow numbers and strings as keys. -

    +

    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.

    -
      AssignObj: [
    -    o 'ObjAssignable',                          -> new Value $1
    -    o 'ObjAssignable : Expression',             -> new Assign LOC(1)(new Value($1)), $3, 'object'
    -    o 'ObjAssignable :
    -       INDENT Expression OUTDENT',              -> new Assign LOC(1)(new Value($1)), $4, 'object'
    -    o 'Comment'
    -  ]
    -
    -  ObjAssignable: [
    -    o 'Identifier'
    -    o 'AlphaNumeric'
    -    o 'ThisProperty'
    +            
      Code: [
    +    o 'PARAM_START ParamList PARAM_END FuncGlyph Block', -> new Code $2, $5, $4
    +    o 'FuncGlyph Block',                        -> new Code [], $2, $1
       ]
    @@ -559,14 +560,14 @@ the ordinary Assign is that these allow numbers and strings as
    -

    A return statement from a function body. -

    +

    CoffeeScript has two different symbols for functions. -> is for ordinary +functions, and => is for functions bound to the current value of this.

    -
      Return: [
    -    o 'RETURN Expression',                      -> new Return $2
    -    o 'RETURN',                                 -> new Return
    +            
      FuncGlyph: [
    +    o '->',                                     -> 'func'
    +    o '=>',                                     -> 'boundfunc'
       ]
    @@ -578,13 +579,13 @@ the ordinary Assign is that these allow numbers and strings as
    -

    A block comment. -

    +

    An optional, trailing comma.

    -
      Comment: [
    -    o 'HERECOMMENT',                            -> new Comment $1
    +            
      OptComma: [
    +    o ''
    +    o ','
       ]
    @@ -596,16 +597,16 @@ the ordinary Assign is that these allow numbers and strings as
    -

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

    +

    The list of parameters that a function accepts can be of any length.

    -
      Code: [
    -    o 'PARAM_START ParamList PARAM_END FuncGlyph Block', -> new Code $2, $5, $4
    -    o 'FuncGlyph Block',                        -> new Code [], $2, $1
    +            
      ParamList: [
    +    o '',                                       -> []
    +    o 'Param',                                  -> [$1]
    +    o 'ParamList , Param',                      -> $1.concat $3
    +    o 'ParamList OptComma TERMINATOR Param',    -> $1.concat $4
    +    o 'ParamList OptComma INDENT ParamList OptComma OUTDENT', -> $1.concat $4
       ]
    @@ -617,15 +618,16 @@ list.
    -

    CoffeeScript has two different symbols for functions. -> is for ordinary -functions, and => is for functions bound to the current value of this. -

    +

    A single parameter in a function definition can be ordinary, or a splat +that hoovers up the remaining arguments.

    -
      FuncGlyph: [
    -    o '->',                                     -> 'func'
    -    o '=>',                                     -> 'boundfunc'
    +            
      Param: [
    +    o 'ParamVar',                               -> new Param $1
    +    o 'ParamVar ...',                           -> new Param $1, null, on
    +    o 'ParamVar = Expression',                  -> new Param $1, $3
    +    o '...',                                    -> new Expansion
       ]
    @@ -637,14 +639,15 @@ functions, and => is for functions bound to the current value of
    -

    An optional, trailing comma. -

    +

    Function Parameters

    -
      OptComma: [
    -    o ''
    -    o ','
    +            
      ParamVar: [
    +    o 'Identifier'
    +    o 'ThisProperty'
    +    o 'Array'
    +    o 'Object'
       ]
    @@ -656,17 +659,12 @@ functions, and => is for functions bound to the current value of
    -

    The list of parameters that a function accepts can be of any length. -

    +

    A splat that occurs outside of a parameter list.

    -
      ParamList: [
    -    o '',                                       -> []
    -    o 'Param',                                  -> [$1]
    -    o 'ParamList , Param',                      -> $1.concat $3
    -    o 'ParamList OptComma TERMINATOR Param',    -> $1.concat $4
    -    o 'ParamList OptComma INDENT ParamList OptComma OUTDENT', -> $1.concat $4
    +            
      Splat: [
    +    o 'Expression ...',                         -> new Splat $1
       ]
    @@ -678,16 +676,15 @@ functions, and => is for functions bound to the current value of
    -

    A single parameter in a function definition can be ordinary, or a splat -that hoovers up the remaining arguments. -

    +

    Variables and properties that can be assigned to.

    -
      Param: [
    -    o 'ParamVar',                               -> new Param $1
    -    o 'ParamVar ...',                           -> new Param $1, null, on
    -    o 'ParamVar = Expression',                  -> new Param $1, $3
    +            
      SimpleAssignable: [
    +    o 'Identifier',                             -> new Value $1
    +    o 'Value Accessor',                         -> $1.add $2
    +    o 'Invocation Accessor',                    -> new Value $1, [].concat $2
    +    o 'ThisProperty'
       ]
    @@ -699,16 +696,14 @@ that hoovers up the remaining arguments.
    -

    Function Parameters -

    +

    Everything that can be assigned to.

    -
      ParamVar: [
    -    o 'Identifier'
    -    o 'ThisProperty'
    -    o 'Array'
    -    o 'Object'
    +            
      Assignable: [
    +    o 'SimpleAssignable'
    +    o 'Array',                                  -> new Value $1
    +    o 'Object',                                 -> new Value $1
       ]
    @@ -720,13 +715,17 @@ that hoovers up the remaining arguments.
    -

    A splat that occurs outside of a parameter list. -

    +

    The types of things that can be treated as values -- assigned to, invoked +as functions, indexed into, named as a class, etc.

    -
      Splat: [
    -    o 'Expression ...',                         -> new Splat $1
    +            
      Value: [
    +    o 'Assignable'
    +    o 'Literal',                                -> new Value $1
    +    o 'Parenthetical',                          -> new Value $1
    +    o 'Range',                                  -> new Value $1
    +    o 'This'
       ]
    @@ -738,16 +737,18 @@ that hoovers up the remaining arguments.
    -

    Variables and properties that can be assigned to. -

    +

    The general group of accessors into an object, by property, by prototype +or by array index or slice.

    -
      SimpleAssignable: [
    -    o 'Identifier',                             -> new Value $1
    -    o 'Value Accessor',                         -> $1.add $2
    -    o 'Invocation Accessor',                    -> new Value $1, [].concat $2
    -    o 'ThisProperty'
    +            
      Accessor: [
    +    o '.  Identifier',                          -> new Access $2
    +    o '?. Identifier',                          -> new Access $2, 'soak'
    +    o ':: Identifier',                          -> [LOC(1)(new Access new Literal('prototype')), LOC(2)(new Access $2)]
    +    o '?:: Identifier',                         -> [LOC(1)(new Access new Literal('prototype'), 'soak'), LOC(2)(new Access $2)]
    +    o '::',                                     -> new Access new Literal 'prototype'
    +    o 'Index'
       ]
    @@ -759,15 +760,18 @@ that hoovers up the remaining arguments.
    -

    Everything that can be assigned to. -

    +

    Indexing into an object or array using bracket notation.

    -
      Assignable: [
    -    o 'SimpleAssignable'
    -    o 'Array',                                  -> new Value $1
    -    o 'Object',                                 -> new Value $1
    +            
      Index: [
    +    o 'INDEX_START IndexValue INDEX_END',       -> $2
    +    o 'INDEX_SOAK  Index',                      -> extend $2, soak : yes
    +  ]
    +
    +  IndexValue: [
    +    o 'Expression',                             -> new Index $1
    +    o 'Slice',                                  -> new Slice $1
       ]
    @@ -779,18 +783,12 @@ that hoovers up the remaining arguments.
    -

    The types of things that can be treated as values -- assigned to, invoked -as functions, indexed into, named as a class, etc. -

    +

    In CoffeeScript, an object literal is simply a list of assignments.

    -
      Value: [
    -    o 'Assignable'
    -    o 'Literal',                                -> new Value $1
    -    o 'Parenthetical',                          -> new Value $1
    -    o 'Range',                                  -> new Value $1
    -    o 'This'
    +            
      Object: [
    +    o '{ AssignList OptComma }',                -> new Obj $2, $1.generated
       ]
    @@ -802,19 +800,17 @@ as functions, indexed into, named as a class, etc.
    -

    The general group of accessors into an object, by property, by prototype -or by array index or slice. -

    +

    Assignment of properties within an object literal can be separated by +comma, as in JavaScript, or simply by newline.

    -
      Accessor: [
    -    o '.  Identifier',                          -> new Access $2
    -    o '?. Identifier',                          -> new Access $2, 'soak'
    -    o ':: Identifier',                          -> [LOC(1)(new Access new Literal('prototype')), LOC(2)(new Access $2)]
    -    o '?:: Identifier',                         -> [LOC(1)(new Access new Literal('prototype'), 'soak'), LOC(2)(new Access $2)]
    -    o '::',                                     -> new Access new Literal 'prototype'
    -    o 'Index'
    +            
      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
       ]
    @@ -826,19 +822,20 @@ or by array index or slice.
    -

    Indexing into an object or array using bracket notation. -

    +

    Class definitions have optional bodies of prototype property assignments, +and optional references to the superclass.

    -
      Index: [
    -    o 'INDEX_START IndexValue INDEX_END',       -> $2
    -    o 'INDEX_SOAK  Index',                      -> extend $2, soak : yes
    -  ]
    -
    -  IndexValue: [
    -    o 'Expression',                             -> new Index $1
    -    o 'Slice',                                  -> new Slice $1
    +            
      Class: [
    +    o 'CLASS',                                           -> new Class
    +    o 'CLASS Block',                                     -> new Class null, null, $2
    +    o 'CLASS EXTENDS Expression',                        -> new Class null, $3
    +    o 'CLASS EXTENDS Expression Block',                  -> new Class null, $3, $4
    +    o 'CLASS SimpleAssignable',                          -> new Class $2
    +    o 'CLASS SimpleAssignable Block',                    -> new Class $2, null, $3
    +    o 'CLASS SimpleAssignable EXTENDS Expression',       -> new Class $2, $4
    +    o 'CLASS SimpleAssignable EXTENDS Expression Block', -> new Class $2, $4, $5
       ]
    @@ -850,13 +847,15 @@ or by array index or slice.
    -

    In CoffeeScript, an object literal is simply a list of assignments. -

    +

    Ordinary function invocation, or a chained series of calls.

    -
      Object: [
    -    o '{ AssignList OptComma }',                -> new Obj $2, $1.generated
    +            
      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
       ]
    @@ -868,18 +867,13 @@ or by array index or slice.
    -

    Assignment of properties within an object literal can be separated by -comma, as in JavaScript, or simply by newline. -

    +

    An optional existence check on a function.

    -
      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
    +            
      OptFuncExist: [
    +    o '',                                       -> no
    +    o 'FUNC_EXIST',                             -> yes
       ]
    @@ -891,21 +885,13 @@ comma, as in JavaScript, or simply by newline.
    -

    Class definitions have optional bodies of prototype property assignments, -and optional references to the superclass. -

    +

    The list of arguments to a function call.

    -
      Class: [
    -    o 'CLASS',                                           -> new Class
    -    o 'CLASS Block',                                     -> new Class null, null, $2
    -    o 'CLASS EXTENDS Expression',                        -> new Class null, $3
    -    o 'CLASS EXTENDS Expression Block',                  -> new Class null, $3, $4
    -    o 'CLASS SimpleAssignable',                          -> new Class $2
    -    o 'CLASS SimpleAssignable Block',                    -> new Class $2, null, $3
    -    o 'CLASS SimpleAssignable EXTENDS Expression',       -> new Class $2, $4
    -    o 'CLASS SimpleAssignable EXTENDS Expression Block', -> new Class $2, $4, $5
    +            
      Arguments: [
    +    o 'CALL_START CALL_END',                    -> []
    +    o 'CALL_START ArgList OptComma CALL_END',   -> $2
       ]
    @@ -917,16 +903,13 @@ and optional references to the superclass.
    -

    Ordinary function invocation, or a chained series of calls. -

    +

    A reference to the this current object.

    -
      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
    +            
      This: [
    +    o 'THIS',                                   -> new Value new Literal 'this'
    +    o '@',                                      -> new Value new Literal 'this'
       ]
    @@ -938,14 +921,12 @@ and optional references to the superclass.
    -

    An optional existence check on a function. -

    +

    A reference to a property on this.

    -
      OptFuncExist: [
    -    o '',                                       -> no
    -    o 'FUNC_EXIST',                             -> yes
    +            
      ThisProperty: [
    +    o '@ Identifier',                           -> new Value LOC(1)(new Literal('this')), [LOC(2)(new Access($2))], 'this'
       ]
    @@ -957,14 +938,13 @@ and optional references to the superclass.
    -

    The list of arguments to a function call. -

    +

    The array literal.

    -
      Arguments: [
    -    o 'CALL_START CALL_END',                    -> []
    -    o 'CALL_START ArgList OptComma CALL_END',   -> $2
    +            
      Array: [
    +    o '[ ]',                                    -> new Arr []
    +    o '[ ArgList OptComma ]',                   -> new Arr $2
       ]
    @@ -976,14 +956,13 @@ and optional references to the superclass.
    -

    A reference to the this current object. -

    +

    Inclusive and exclusive range dots.

    -
      This: [
    -    o 'THIS',                                   -> new Value new Literal 'this'
    -    o '@',                                      -> new Value new Literal 'this'
    +            
      RangeDots: [
    +    o '..',                                     -> 'inclusive'
    +    o '...',                                    -> 'exclusive'
       ]
    @@ -995,13 +974,12 @@ and optional references to the superclass.
    -

    A reference to a property on this. -

    +

    The CoffeeScript range literal.

    -
      ThisProperty: [
    -    o '@ Identifier',                           -> new Value LOC(1)(new Literal('this')), [LOC(2)(new Access($2))], 'this'
    +            
      Range: [
    +    o '[ Expression RangeDots Expression ]',    -> new Range $2, $4, $3
       ]
    @@ -1013,14 +991,15 @@ and optional references to the superclass.
    -

    The array literal. -

    +

    Array slice literals.

    -
      Array: [
    -    o '[ ]',                                    -> new Arr []
    -    o '[ ArgList OptComma ]',                   -> new Arr $2
    +            
      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
    +    o 'RangeDots',                              -> new Range null, null, $1
       ]
    @@ -1032,14 +1011,18 @@ and optional references to the superclass.
    -

    Inclusive and exclusive range dots. -

    +

    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.

    -
      RangeDots: [
    -    o '..',                                     -> 'inclusive'
    -    o '...',                                    -> 'exclusive'
    +            
      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
       ]
    @@ -1051,13 +1034,14 @@ and optional references to the superclass.
    -

    The CoffeeScript range literal. -

    +

    Valid arguments are Blocks or Splats.

    -
      Range: [
    -    o '[ Expression RangeDots Expression ]',    -> new Range $2, $4, $3
    +            
      Arg: [
    +    o 'Expression'
    +    o 'Splat'
    +    o '...',                                     -> new Expansion
       ]
    @@ -1069,16 +1053,15 @@ and optional references to the superclass.
    -

    Array slice literals. -

    +

    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.

    -
      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
    -    o 'RangeDots',                              -> new Range null, null, $1
    +            
      SimpleArgs: [
    +    o 'Expression'
    +    o 'SimpleArgs , Expression',                -> [].concat $1, $3
       ]
    @@ -1090,19 +1073,15 @@ and optional references to the superclass.
    -

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

    +

    The variants of try/catch/finally exception handling blocks.

    -
      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
    +            
      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
       ]
    @@ -1114,14 +1093,14 @@ as well as the contents of an array literal
    -

    Valid arguments are Blocks or Splats. -

    +

    A catch clause names its error and runs a block of code.

    -
      Arg: [
    -    o 'Expression'
    -    o 'Splat'
    +            
      Catch: [
    +    o 'CATCH Identifier Block',                 -> [$2, $3]
    +    o 'CATCH Object Block',                     -> [LOC(2)(new Value($2)), $3]
    +    o 'CATCH Block',                            -> [null, $2]
       ]
    @@ -1133,16 +1112,12 @@ as well as the contents of an array literal
    -

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

    +

    Throw an exception object.

    -
      SimpleArgs: [
    -    o 'Expression'
    -    o 'SimpleArgs , Expression',                -> [].concat $1, $3
    +            
      Throw: [
    +    o 'THROW Expression',                       -> new Throw $2
       ]
    @@ -1154,16 +1129,16 @@ having the newlines wouldn't make sense.
    -

    The variants of try/catch/finally exception handling blocks. -

    +

    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.

    -
      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
    +            
      Parenthetical: [
    +    o '( Body )',                               -> new Parens $2
    +    o '( INDENT Body OUTDENT )',                -> new Parens $3
       ]
    @@ -1175,15 +1150,15 @@ having the newlines wouldn't make sense.
    -

    A catch clause names its error and runs a block of code. -

    +

    The condition portion of a while loop.

    -
      Catch: [
    -    o 'CATCH Identifier Block',                 -> [$2, $3]
    -    o 'CATCH Object Block',                     -> [LOC(2)(new Value($2)), $3]
    -    o 'CATCH Block',                            -> [null, $2]
    +            
      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
       ]
    @@ -1195,13 +1170,21 @@ having the newlines wouldn't make sense.
    -

    Throw an exception object. -

    +

    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.

    -
      Throw: [
    -    o 'THROW Expression',                       -> new Throw $2
    +            
      While: [
    +    o 'WhileSource Block',                      -> $1.addBody $2
    +    o 'Statement  WhileSource',                 -> $2.addBody LOC(1) Block.wrap([$1])
    +    o 'Expression WhileSource',                 -> $2.addBody LOC(1) Block.wrap([$1])
    +    o 'Loop',                                   -> $1
    +  ]
    +
    +  Loop: [
    +    o 'LOOP Block',                             -> new While(LOC(1) new Literal 'true').addBody $2
    +    o 'LOOP Expression',                        -> new While(LOC(1) new Literal 'true').addBody LOC(2) Block.wrap [$2]
       ]
    @@ -1213,17 +1196,26 @@ having the newlines wouldn't make sense.
    -

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

    +

    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.

    -
      Parenthetical: [
    -    o '( Body )',                               -> new Parens $2
    -    o '( INDENT Body OUTDENT )',                -> new Parens $3
    +            
      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: LOC(2) new Value($2)
    +    o 'ForStart ForSource',                     -> $2.own = $1.own; $2.name = $1[0]; $2.index = $1[1]; $2
    +  ]
    +
    +  ForStart: [
    +    o 'FOR ForVariables',                       -> $2
    +    o 'FOR OWN ForVariables',                   -> $3.own = yes; $3
       ]
    @@ -1235,16 +1227,16 @@ the trick.
    -

    The condition portion of a while loop. -

    +

    An array of all accepted values for a variable inside the loop. +This enables support for pattern matching.

    -
      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
    +            
      ForValue: [
    +    o 'Identifier'
    +    o 'ThisProperty'
    +    o 'Array',                                  -> new Value $1
    +    o 'Object',                                 -> new Value $1
       ]
    @@ -1256,22 +1248,15 @@ the trick.
    -

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

    +

    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.

    -
      While: [
    -    o 'WhileSource Block',                      -> $1.addBody $2
    -    o 'Statement  WhileSource',                 -> $2.addBody LOC(1) Block.wrap([$1])
    -    o 'Expression WhileSource',                 -> $2.addBody LOC(1) Block.wrap([$1])
    -    o 'Loop',                                   -> $1
    -  ]
    -
    -  Loop: [
    -    o 'LOOP Block',                             -> new While(LOC(1) new Literal 'true').addBody $2
    -    o 'LOOP Expression',                        -> new While(LOC(1) new Literal 'true').addBody LOC(2) Block.wrap [$2]
    +            
      ForVariables: [
    +    o 'ForValue',                               -> [$1]
    +    o 'ForValue , ForValue',                    -> [$1, $3]
       ]
    @@ -1283,27 +1268,32 @@ or postfix, with a single expression. There is no do..while.
    -

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

    +

    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.

    -
      For: [
    -    o 'Statement  ForBody',                     -> new For $1, $2
    -    o 'Expression ForBody',                     -> new For $1, $2
    -    o 'ForBody    Block',                       -> new For $2, $1
    +            
      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
       ]
     
    -  ForBody: [
    -    o 'FOR Range',                              -> source: LOC(2) new Value($2)
    -    o 'ForStart ForSource',                     -> $2.own = $1.own; $2.name = $1[0]; $2.index = $1[1]; $2
    +  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
       ]
     
    -  ForStart: [
    -    o 'FOR ForVariables',                       -> $2
    -    o 'FOR OWN ForVariables',                   -> $3.own = yes; $3
    +  Whens: [
    +    o 'When'
    +    o 'Whens When',                             -> $1.concat $2
       ]
    @@ -1315,17 +1305,13 @@ or postfix, with a single expression.
    -

    An array of all accepted values for a variable inside the loop. -This enables support for pattern matching. -

    +

    An individual When clause, with action.

    -
      ForValue: [
    -    o 'Identifier'
    -    o 'ThisProperty'
    -    o 'Array',                                  -> new Value $1
    -    o 'Object',                                 -> new Value $1
    +            
      When: [
    +    o 'LEADING_WHEN SimpleArgs Block',            -> [[$2, $3]]
    +    o 'LEADING_WHEN SimpleArgs Block TERMINATOR', -> [[$2, $3]]
       ]
    @@ -1337,16 +1323,15 @@ This enables support for pattern matching.
    -

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

    +

    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.

    -
      ForVariables: [
    -    o 'ForValue',                               -> [$1]
    -    o 'ForValue , ForValue',                    -> [$1, $3]
    +            
      IfBlock: [
    +    o 'IF Expression Block',                    -> new If $2, $3, type: $1
    +    o 'IfBlock ELSE IF Expression Block',       -> $1.addElse LOC(3,5) new If $4, $5, type: $3
       ]
    @@ -1358,33 +1343,16 @@ of object comprehensions.
    -

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

    +

    The full complement of if expressions, including postfix one-liner +if and unless.

    -
      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: [
    -    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: [
    -    o 'When'
    -    o 'Whens When',                             -> $1.concat $2
    +            
      If: [
    +    o 'IfBlock'
    +    o 'IfBlock ELSE Block',                     -> $1.addElse $3
    +    o 'Statement  POST_IF Expression',          -> new If $3, LOC(1)(Block.wrap [$1]), type: $2, statement: true
    +    o 'Expression POST_IF Expression',          -> new If $3, LOC(1)(Block.wrap [$1]), type: $2, statement: true
       ]
    @@ -1396,15 +1364,25 @@ in fixed-size increments.
    -

    An individual When clause, with action. -

    +

    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.

    -
      When: [
    -    o 'LEADING_WHEN SimpleArgs Block',            -> [[$2, $3]]
    -    o 'LEADING_WHEN SimpleArgs Block TERMINATOR', -> [[$2, $3]]
    -  ]
    +
      Operation: [
    +    o 'UNARY Expression',                       -> new Op $1 , $2
    +    o 'UNARY_MATH Expression',                  -> new Op $1 , $2
    +    o '-     Expression',                      (-> new Op '-', $2), prec: 'UNARY_MATH'
    +    o '+     Expression',                      (-> new Op '+', $2), prec: 'UNARY_MATH'
    +
    +    o '-- SimpleAssignable',                    -> new Op '--', $2
    +    o '++ SimpleAssignable',                    -> new Op '++', $2
    +    o 'SimpleAssignable --',                    -> new Op '--', $1, null, true
    +    o 'SimpleAssignable ++',                    -> new Op '++', $1, null, true
    @@ -1415,16 +1393,33 @@ in fixed-size increments.
    -

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

    +

    The existential operator.

    -
      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
    +            
        o 'Expression ?',                           -> new Existence $1
    +
    +    o 'Expression +  Expression',               -> new Op '+' , $1, $3
    +    o 'Expression -  Expression',               -> new Op '-' , $1, $3
    +
    +    o 'Expression MATH     Expression',         -> new Op $2, $1, $3
    +    o 'Expression **       Expression',         -> new Op $2, $1, $3
    +    o 'Expression SHIFT    Expression',         -> new Op $2, $1, $3
    +    o 'Expression COMPARE  Expression',         -> new Op $2, $1, $3
    +    o 'Expression LOGIC    Expression',         -> new Op $2, $1, $3
    +    o 'Expression RELATION Expression',         ->
    +      if $2.charAt(0) is '!'
    +        new Op($2[1..], $1, $3).invert()
    +      else
    +        new Op $2, $1, $3
    +
    +    o 'SimpleAssignable COMPOUND_ASSIGN
    +       Expression',                             -> new Assign $1, $3, $2
    +    o 'SimpleAssignable COMPOUND_ASSIGN
    +       INDENT Expression OUTDENT',              -> new Assign $1, $4, $2
    +    o 'SimpleAssignable COMPOUND_ASSIGN TERMINATOR
    +       Expression',                             -> new Assign $1, $4, $2
    +    o 'SimpleAssignable EXTENDS Expression',    -> new Extends $1, $3
       ]
    @@ -1436,19 +1431,10 @@ ambiguity.
    -

    The full complement of if expressions, including postfix one-liner -if and unless. -

    +

    Precedence

    -
      If: [
    -    o 'IfBlock'
    -    o 'IfBlock ELSE Block',                     -> $1.addElse $3
    -    o 'Statement  POST_IF Expression',          -> new If $3, LOC(1)(Block.wrap [$1]), type: $2, statement: true
    -    o 'Expression POST_IF Expression',          -> new If $3, LOC(1)(Block.wrap [$1]), type: $2, statement: true
    -  ]
    - @@ -1458,25 +1444,34 @@ ambiguity.
    -

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

    - +

    Operators at the top of this list have higher precedence than the ones lower +down. Following these rules is what makes 2 + 3 * 4 parse as:

    +
    2 + (3 * 4)
    +

    And not:

    +
    (2 + 3) * 4
    +
    -
      Operation: [
    -    o 'UNARY Expression',                       -> new Op $1 , $2
    -    o '-     Expression',                      (-> new Op '-', $2), prec: 'UNARY'
    -    o '+     Expression',                      (-> new Op '+', $2), prec: 'UNARY'
    -
    -    o '-- SimpleAssignable',                    -> new Op '--', $2
    -    o '++ SimpleAssignable',                    -> new Op '++', $2
    -    o 'SimpleAssignable --',                    -> new Op '--', $1, null, true
    -    o 'SimpleAssignable ++',                    -> new Op '++', $1, null, true
    +
    operators = [
    +  ['left',      '.', '?.', '::', '?::']
    +  ['left',      'CALL_START', 'CALL_END']
    +  ['nonassoc',  '++', '--']
    +  ['left',      '?']
    +  ['right',     'UNARY']
    +  ['right',     '**']
    +  ['right',     'UNARY_MATH']
    +  ['left',      'MATH']
    +  ['left',      '+', '-']
    +  ['left',      'SHIFT']
    +  ['left',      'RELATION']
    +  ['left',      'COMPARE']
    +  ['left',      'LOGIC']
    +  ['nonassoc',  'INDENT', 'OUTDENT']
    +  ['right',     '=', ':', 'COMPOUND_ASSIGN', 'RETURN', 'THROW', 'EXTENDS']
    +  ['right',     'FORIN', 'FOROF', 'BY', 'WHEN']
    +  ['right',     'IF', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS']
    +  ['left',      'POST_IF']
    +]
    @@ -1487,48 +1482,34 @@ rules are necessary.
    -

    The existential operator. -

    +

    Wrapping Up

    -
        o 'Expression ?',                           -> new Existence $1
    -
    -    o 'Expression +  Expression',               -> new Op '+' , $1, $3
    -    o 'Expression -  Expression',               -> new Op '-' , $1, $3
    -
    -    o 'Expression MATH     Expression',         -> new Op $2, $1, $3
    -    o 'Expression SHIFT    Expression',         -> new Op $2, $1, $3
    -    o 'Expression COMPARE  Expression',         -> new Op $2, $1, $3
    -    o 'Expression LOGIC    Expression',         -> new Op $2, $1, $3
    -    o 'Expression RELATION Expression',         ->
    -      if $2.charAt(0) is '!'
    -        new Op($2[1..], $1, $3).invert()
    -      else
    -        new Op $2, $1, $3
    -
    -    o 'SimpleAssignable COMPOUND_ASSIGN
    -       Expression',                             -> new Assign $1, $3, $2
    -    o 'SimpleAssignable COMPOUND_ASSIGN
    -       INDENT Expression OUTDENT',              -> new Assign $1, $4, $2
    -    o 'SimpleAssignable COMPOUND_ASSIGN TERMINATOR
    -       Expression',                             -> new Assign $1, $4, $2
    -    o 'SimpleAssignable EXTENDS Expression',    -> new Extends $1, $3
    -  ]
    -
  • -
    +
    -

    Precedence

    +

    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 +terminals (every symbol which does not appear as the name of a rule above) +as "tokens".

    +
    tokens = []
    +for name, alternatives of grammar
    +  grammar[name] = for alt in alternatives
    +    for token in alt[0].split ' '
    +      tokens.push token unless grammar[token]
    +    alt[1] = "return #{alt[1]}" if name is 'Root'
    +    alt
    +
  • @@ -1538,121 +1519,18 @@ rules are necessary.
    - -
    - - - - -
  • -
    - -
    - -
    -

    Operators at the top of this list have higher precedence than the ones lower -down. Following these rules is what makes 2 + 3 * 4 parse as: - -

    -
    2 + (3 * 4)
    -

    And not: - -

    -
    (2 + 3) * 4
    - -
    - -
    operators = [
    -  ['left',      '.', '?.', '::', '?::']
    -  ['left',      'CALL_START', 'CALL_END']
    -  ['nonassoc',  '++', '--']
    -  ['left',      '?']
    -  ['right',     'UNARY']
    -  ['left',      'MATH']
    -  ['left',      '+', '-']
    -  ['left',      'SHIFT']
    -  ['left',      'RELATION']
    -  ['left',      'COMPARE']
    -  ['left',      'LOGIC']
    -  ['nonassoc',  'INDENT', 'OUTDENT']
    -  ['right',     '=', ':', 'COMPOUND_ASSIGN', 'RETURN', 'THROW', 'EXTENDS']
    -  ['right',     'FORIN', 'FOROF', 'BY', 'WHEN']
    -  ['right',     'IF', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS']
    -  ['right',     'POST_IF']
    -]
    - -
  • - - -
  • -
    - -
    - -
    -

    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 -terminals (every symbol which does not appear as the name of a rule above) -as "tokens". -

    - -
    - -
    tokens = []
    -for name, alternatives of grammar
    -  grammar[name] = for alt in alternatives
    -    for token in alt[0].split ' '
    -      tokens.push token unless grammar[token]
    -    alt[1] = "return #{alt[1]}" if name is 'Root'
    -    alt
    - -
  • - - -
  • -
    - -
    - -

    Initialize the Parser with our list of terminal tokens, our grammar rules, and the name of the root. Reverse the operators because Jison orders precedence from low to high, and we have it high to low -(as in Yacc). -

    +(as in Yacc).

    -
    exports.parser = new Parser
    -  tokens      : tokens.join ' '
    +            
    exports.parser = new Parser
    +  tokens      : tokens.join ' '
       bnf         : grammar
       operators   : operators.reverse()
    -  startSymbol : 'Root'
    + startSymbol : 'Root'
  • diff --git a/documentation/docs/helpers.html b/documentation/docs/helpers.html index 6efe3f55..bdfa4767 100644 --- a/documentation/docs/helpers.html +++ b/documentation/docs/helpers.html @@ -69,6 +69,11 @@ + + register.coffee + + + repl.coffee @@ -110,11 +115,14 @@

    This file contains the common helper functions that we'd like to share among the Lexer, Rewriter, and the Nodes. Merge objects, flatten -arrays, count characters, that sort of thing. -

    +arrays, count characters, that sort of thing.

    +

    Peek at the beginning of a given string to see if it matches a sequence.

    +
    exports.starts = (string, literal, start) ->
    +  literal is string.substr start, literal.length
    + @@ -124,13 +132,13 @@ arrays, count characters, that sort of thing.
    -

    Peek at the beginning of a given string to see if it matches a sequence. -

    +

    Peek at the end of a given string to see if it matches a sequence.

    -
    exports.starts = (string, literal, start) ->
    -  literal is string.substr start, literal.length
    +
    exports.ends = (string, literal, back) ->
    +  len = literal.length
    +  literal is string.substr string.length - len - (back or 0), len
    @@ -141,14 +149,11 @@ arrays, count characters, that sort of thing.
    -

    Peek at the end of a given string to see if it matches a sequence. -

    +

    Repeat a string n times.

    -
    exports.ends = (string, literal, back) ->
    -  len = literal.length
    -  literal is string.substr string.length - len - (back or 0), len
    +
    exports.repeat = repeat = (str, n) ->
    @@ -159,12 +164,16 @@ arrays, count characters, that sort of thing.
    -

    Repeat a string n times. -

    +

    Use clever algorithm to have O(log(n)) string concatenation operations.

    -
    exports.repeat = repeat = (str, n) ->
    +
      res = ''
    +  while n > 0
    +    res += str if n & 1
    +    n >>>= 1
    +    str += str
    +  res
    @@ -175,17 +184,12 @@ arrays, count characters, that sort of thing.
    -

    Use clever algorithm to have O(log(n)) string concatenation operations. -

    +

    Trim out all falsy values from an array.

    -
      res = ''
    -  while n > 0
    -    res += str if n & 1
    -    n >>>= 1
    -    str += str
    -  res
    +
    exports.compact = (array) ->
    +  item for item in array when item
    @@ -196,13 +200,15 @@ arrays, count characters, that sort of thing.
    -

    Trim out all falsy values from an array. -

    +

    Count the number of occurrences of a string in a string.

    -
    exports.compact = (array) ->
    -  item for item in array when item
    +
    exports.count = (string, substr) ->
    +  num = pos = 0
    +  return 1/0 unless substr.length
    +  num++ while pos = 1 + string.indexOf substr, pos
    +  num
    @@ -213,16 +219,14 @@ arrays, count characters, that sort of thing.
    -

    Count the number of occurrences of a string in a string. -

    +

    Merge objects, returning a fresh copy with attributes from both sides. +Used every time Base#compile is called, to allow properties in the +options hash to propagate down the tree without polluting other branches.

    -
    exports.count = (string, substr) ->
    -  num = pos = 0
    -  return 1/0 unless substr.length
    -  num++ while pos = 1 + string.indexOf substr, pos
    -  num
    +
    exports.merge = (options, overrides) ->
    +  extend (extend {}, options), overrides
    @@ -233,15 +237,14 @@ arrays, count characters, that sort of thing.
    -

    Merge objects, returning a fresh copy with attributes from both sides. -Used every time Base#compile is called, to allow properties in the -options hash to propagate down the tree without polluting other branches. -

    +

    Extend a source object with the properties of another object (shallow copy).

    -
    exports.merge = (options, overrides) ->
    -  extend (extend {}, options), overrides
    +
    extend = exports.extend = (object, properties) ->
    +  for key, val of properties
    +    object[key] = val
    +  object
    @@ -252,15 +255,19 @@ options hash to propagate down the tree without polluting other branches.
    -

    Extend a source object with the properties of another object (shallow copy). -

    +

    Return a flattened version of an array. +Handy for getting a list of children from the nodes.

    -
    extend = exports.extend = (object, properties) ->
    -  for key, val of properties
    -    object[key] = val
    -  object
    +
    exports.flatten = flatten = (array) ->
    +  flattened = []
    +  for element in array
    +    if element instanceof Array
    +      flattened = flattened.concat flatten element
    +    else
    +      flattened.push element
    +  flattened
    @@ -271,20 +278,15 @@ options hash to propagate down the tree without polluting other branches.
    -

    Return a flattened version of an array. -Handy for getting a list of children from the nodes. -

    +

    Delete a key from an object, returning the value. Useful when a node is +looking for a particular method in an options hash.

    -
    exports.flatten = flatten = (array) ->
    -  flattened = []
    -  for element in array
    -    if element instanceof Array
    -      flattened = flattened.concat flatten element
    -    else
    -      flattened.push element
    -  flattened
    +
    exports.del = (obj, key) ->
    +  val =  obj[key]
    +  delete obj[key]
    +  val
    @@ -295,16 +297,11 @@ Handy for getting a list of children from the nodes.
    -

    Delete a key from an object, returning the value. Useful when a node is -looking for a particular method in an options hash. -

    +

    Gets the last item of an array(-like) object.

    -
    exports.del = (obj, key) ->
    -  val =  obj[key]
    -  delete obj[key]
    -  val
    +
    exports.last = last = (array, back) -> array[array.length - (back or 0) - 1]
    @@ -315,12 +312,13 @@ looking for a particular method in an options hash.
    -

    Gets the last item of an array(-like) object. -

    +

    Typical Array::some

    -
    exports.last = last = (array, back) -> array[array.length - (back or 0) - 1]
    +
    exports.some = Array::some ? (fn) ->
    +  return true for e in this when fn e
    +  false
    @@ -331,14 +329,22 @@ looking for a particular method in an options hash.
    -

    Typical Array::some -

    +

    Simple function for inverting Literate CoffeeScript code by putting the +documentation in comments, producing a string of CoffeeScript code that +can be compiled "normally".

    -
    exports.some = Array::some ? (fn) ->
    -  return true for e in this when fn e
    -  false
    +
    exports.invertLiterate = (code) ->
    +  maybe_code = true
    +  lines = for line in code.split('\n')
    +    if maybe_code and /^([ ]{4}|[ ]{0,3}\t)/.test line
    +      line
    +    else if maybe_code = /^\s*$/.test line
    +      line
    +    else
    +      '# ' + line
    +  lines.join '\n'
    @@ -349,23 +355,19 @@ looking for a particular method in an options hash.
    -

    Simple function for inverting Literate CoffeeScript code by putting the -documentation in comments, producing a string of CoffeeScript code that -can be compiled "normally". -

    +

    Merge two jison-style location data objects together. +If last is not provided, this will simply return first.

    -
    exports.invertLiterate = (code) ->
    -  maybe_code = true
    -  lines = for line in code.split('\n')
    -    if maybe_code and /^([ ]{4}|[ ]{0,3}\t)/.test line
    -      line
    -    else if maybe_code = /^\s*$/.test line
    -      line
    -    else
    -      '# ' + line
    -  lines.join '\n'
    +
    buildLocationData = (first, last) ->
    +  if not last
    +    first
    +  else
    +    first_line: first.first_line
    +    first_column: first.first_column
    +    last_line: last.last_line
    +    last_column: last.last_column
    @@ -376,20 +378,18 @@ can be compiled "normally".
    -

    Merge two jison-style location data objects together. -If last is not provided, this will simply return first. -

    +

    This returns a function which takes an object as a parameter, and if that +object is an AST node, updates that object's locationData. +The object is returned either way.

    -
    buildLocationData = (first, last) ->
    -  if not last
    -    first
    -  else
    -    first_line: first.first_line
    -    first_column: first.first_column
    -    last_line: last.last_line
    -    last_column: last.last_column
    +
    exports.addLocationDataFn = (first, last) ->
    +  (obj) ->
    +    if ((typeof obj) is 'object') and (!!obj['updateLocationDataIfMissing'])
    +      obj.updateLocationDataIfMissing buildLocationData(first, last)
    +
    +    return obj
    @@ -400,19 +400,20 @@ If last is not provided, this will simply return first
    -

    This returns a function which takes an object as a parameter, and if that -object is an AST node, updates that object's locationData. -The object is returned either way. -

    +

    Convert jison location data to a string. +obj can be a token, or a locationData.

    -
    exports.addLocationDataFn = (first, last) ->
    -    (obj) ->
    -      if ((typeof obj) is 'object') and (!!obj['updateLocationDataIfMissing'])
    -        obj.updateLocationDataIfMissing buildLocationData(first, last)
    +            
    exports.locationDataToString = (obj) ->
    +  if ("2" of obj) and ("first_line" of obj[2]) then locationData = obj[2]
    +  else if "first_line" of obj then locationData = obj
     
    -      return obj
    + if locationData + "#{locationData.first_line + 1}:#{locationData.first_column + 1}-" + + "#{locationData.last_line + 1}:#{locationData.last_column + 1}" + else + "No location data"
    @@ -423,21 +424,19 @@ The object is returned either way.
    -

    Convert jison location data to a string. -obj can be a token, or a locationData. -

    +

    A .coffee.md compatible version of basename, that returns the file sans-extension.

    -
    exports.locationDataToString = (obj) ->
    -    if ("2" of obj) and ("first_line" of obj[2]) then locationData = obj[2]
    -    else if "first_line" of obj then locationData = obj
    -
    -    if locationData
    -      "#{locationData.first_line + 1}:#{locationData.first_column + 1}-" +
    -      "#{locationData.last_line + 1}:#{locationData.last_column + 1}"
    -    else
    -      "No location data"
    +
    exports.baseFileName = (file, stripExt = no, useWinPathSep = no) ->
    +  pathSep = if useWinPathSep then /\\|\// else /\//
    +  parts = file.split(pathSep)
    +  file = parts[parts.length - 1]
    +  return file unless stripExt and file.indexOf('.') >= 0
    +  parts = file.split('.')
    +  parts.pop()
    +  parts.pop() if parts[parts.length - 1] is 'coffee' and parts.length > 1
    +  parts.join('.')
    @@ -448,20 +447,11 @@ The object is returned either way.
    -

    A .coffee.md compatible version of basename, that returns the file sans-extension. -

    +

    Determine if a filename represents a CoffeeScript file.

    -
    exports.baseFileName = (file, stripExt = no, useWinPathSep = no) ->
    -  pathSep = if useWinPathSep then /\\|\// else /\//
    -  parts = file.split(pathSep)
    -  file = parts[parts.length - 1]
    -  return file unless stripExt
    -  parts = file.split('.')
    -  parts.pop()
    -  parts.pop() if parts[parts.length - 1] is 'coffee' and parts.length > 1
    -  parts.join('.')
    +
    exports.isCoffee = (file) -> /\.((lit)?coffee|coffee\.md)$/.test file
    @@ -472,12 +462,11 @@ The object is returned either way.
    -

    Determine if a filename represents a CoffeeScript file. -

    +

    Determine if a filename represents a Literate CoffeeScript file.

    -
    exports.isCoffee = (file) -> /\.((lit)?coffee|coffee\.md)$/.test file
    +
    exports.isLiterate = (file) -> /\.(litcoffee|coffee\.md)$/.test file
    @@ -488,12 +477,17 @@ The object is returned either way.
    -

    Determine if a filename represents a Literate CoffeeScript file. -

    +

    Throws a SyntaxError from a given location. +The error's toString will return an error message following the "standard" +format ::: plus the line with the error and a +marker showing where the error is.

    -
    exports.isLiterate = (file) -> /\.(litcoffee|coffee\.md)$/.test file
    +
    exports.throwSyntaxError = (message, location) ->
    +  error = new SyntaxError message
    +  error.location = location
    +  error.toString = syntaxErrorToString
    @@ -504,18 +498,15 @@ The object is returned either way.
    -

    Throws a SyntaxError with a source file location data attached to it in a -property called location. -

    +

    Instead of showing the compiler's stacktrace, show our custom error message +(this is useful when the error bubbles up in Node.js applications that +compile CoffeeScript for example).

    -
    exports.throwSyntaxError = (message, location) ->
    -  location.last_line ?= location.first_line
    -  location.last_column ?= location.first_column
    -  error = new SyntaxError message
    -  error.location = location
    -  throw error
    +
      error.stack = error.toString()
    +
    +  throw error
    @@ -526,20 +517,12 @@ property called location.
    -

    Creates a nice error message like, following the "standard" format -

    -

    ::: plus the line with the error and a marker -showing where the error is. -

    +

    Update a compiler SyntaxError with source code information if it didn't have +it already.

    -
    exports.prettyErrorMessage = (error, fileName, code, useColors) ->
    -  return error.stack or "#{error}" unless error.location
    -
    -  {first_line, first_column, last_line, last_column} = error.location
    -  codeLine = code.split('\n')[first_line]
    -  start    = first_column
    +
    exports.updateSyntaxError = (error, code, filename) ->
    @@ -550,24 +533,26 @@ showing where the error is.
    -

    Show only the first line on multi-line errors. -

    +

    Avoid screwing up the stack property of other errors (i.e. possible bugs).

    -
      end      = if first_line is last_line then last_column + 1 else codeLine.length
    -  marker   = repeat(' ', start) + repeat('^', end - start)
    +            
      if error.toString is syntaxErrorToString
    +    error.code or= code
    +    error.filename or= filename
    +    error.stack = error.toString()
    +  error
     
    -  if useColors
    -    colorize  = (str) -> "\x1B[1;31m#{str}\x1B[0m"
    -    codeLine = codeLine[...start] + colorize(codeLine[start...end]) + codeLine[end..]
    -    marker    = colorize marker
    +syntaxErrorToString = ->
    +  return Error::toString.call @ unless @code and @location
     
    -  message = """
    -  #{fileName}:#{first_line + 1}:#{first_column + 1}: error: #{error.message}
    -  #{codeLine}
    -  #{marker}
    -            """
    + {first_line, first_column, last_line, last_column} = @location + last_line ?= first_line + last_column ?= first_column + + filename = @filename or '[stdin]' + codeLine = @code.split('\n')[first_line] + start = first_column
    @@ -578,14 +563,47 @@ showing where the error is.
    -

    Uncomment to add stacktrace. -message += "\n#{error.stack}" -

    +

    Show only the first line on multi-line errors.

    -
    -  message
    +
      end      = if first_line is last_line then last_column + 1 else codeLine.length
    +  marker   = repeat(' ', start) + repeat('^', end - start)
    + + + + +
  • +
    + +
    + +
    +

    Check to see if we're running on a color-enabled TTY.

    + +
    + +
      if process?
    +    colorsEnabled = process.stdout.isTTY and not process.env.NODE_DISABLE_COLORS
    +
    +  if @colorful ? colorsEnabled
    +    colorize = (str) -> "\x1B[1;31m#{str}\x1B[0m"
    +    codeLine = codeLine[...start] + colorize(codeLine[start...end]) + codeLine[end..]
    +    marker   = colorize marker
    +
    +  """
    +    #{filename}:#{first_line + 1}:#{first_column + 1}: error: #{@message}
    +    #{codeLine}
    +    #{marker}
    +  """
    +
    +exports.nameWhitespaceCharacter = (string) ->
    +  switch string
    +    when ' ' then 'space'
    +    when '\n' then 'newline'
    +    when '\r' then 'carriage return'
    +    when '\t' then 'tab'
    +    else string
  • diff --git a/documentation/docs/index.html b/documentation/docs/index.html index 9ee66be1..113f3295 100644 --- a/documentation/docs/index.html +++ b/documentation/docs/index.html @@ -69,6 +69,11 @@ + + register.coffee + + + repl.coffee @@ -108,12 +113,11 @@
    -

    Loader for CoffeeScript as a Node.js library. -

    +

    Loader for CoffeeScript as a Node.js library.

    -
    exports[key] = val for key, val of require './coffee-script'
    +
    exports[key] = val for key, val of require './coffee-script'
    diff --git a/documentation/docs/lexer.html b/documentation/docs/lexer.html index 148921d2..3a375b8d 100644 --- a/documentation/docs/lexer.html +++ b/documentation/docs/lexer.html @@ -69,6 +69,11 @@ + + register.coffee + + + repl.coffee @@ -111,19 +116,15 @@

    The CoffeeScript Lexer. Uses a series of token-matching regexes to attempt matches against the beginning of the source code. When a match is found, a token is produced, we consume the match, and start again. Tokens are in the -form: - -

    -
    [tag, value, locationData]
    -

    where locationData is {first_line, first_column, last_line, last_column}, which is a +form:

    +
    [tag, value, locationData]
    +

    where locationData is {first_line, first_column, last_line, last_column}, which is a format that can be fed directly into Jison. These -are read by jison in the parser.lexer function defined in coffee-script.coffee. -

    +are read by jison in the parser.lexer function defined in coffee-script.coffee.

    -
    -{Rewriter, INVERSES} = require './rewriter'
    +
    {Rewriter, INVERSES} = require './rewriter'
    @@ -134,13 +135,12 @@ are read by jison in the parser.lexer function defined in coffee-sc
    -

    Import the helpers we need. -

    +

    Import the helpers we need.

    {count, starts, compact, last, repeat, invertLiterate,
    -locationDataToString,  throwSyntaxError} = require './helpers'
    +locationDataToString, throwSyntaxError} = require './helpers'
    @@ -148,10 +148,10 @@ locationDataToString, throwSyntaxError} = require './helpe
  • -
    +
    -

    The Lexer Class

    +

    The Lexer Class

    @@ -164,9 +164,14 @@ locationDataToString, throwSyntaxError} = require './helpe
    - +

    The Lexer class reads a stream of CoffeeScript and divvies it up into tagged +tokens. Some potential ambiguity in the grammar has been avoided by +pushing some extra smarts into the Lexer.

    +
    +
    exports.Lexer = class Lexer
    +
  • @@ -176,14 +181,33 @@ locationDataToString, throwSyntaxError} = require './helpe
    -

    The Lexer class reads a stream of CoffeeScript and divvies it up into tagged -tokens. Some potential ambiguity in the grammar has been avoided by -pushing some extra smarts into the Lexer. -

    +

    tokenize is the Lexer's main method. Scan by attempting to match tokens +one at a time, using a regular expression anchored at the start of the +remaining code, or a custom recursive token-matching method +(for interpolations). When the next token has been recorded, we move forward +within the code past the token, and begin again.

    +

    Each tokenizing method is responsible for returning the number of characters +it has consumed.

    +

    Before returning the token stream, run it through the Rewriter +unless explicitly asked not to.

    -
    exports.Lexer = class Lexer
    +
      tokenize: (code, opts = {}) ->
    +    @literate   = opts.literate  # Are we lexing literate CoffeeScript?
    +    @indent     = 0              # The current indentation level.
    +    @baseIndent = 0              # The overall minimum indentation level
    +    @indebt     = 0              # The over-indentation at the current level.
    +    @outdebt    = 0              # The under-outdentation at the current level.
    +    @indents    = []             # The stack of all current indentation levels.
    +    @ends       = []             # The stack for pairing up tokens.
    +    @tokens     = []             # Stream of parsed tokens in the form `['TYPE', value, location data]`.
    +
    +    @chunkLine =
    +      opts.line or 0         # The start line for the current @chunk.
    +    @chunkColumn =
    +      opts.column or 0       # The start column of the current @chunk.
    +    code = @clean code         # The stripped, cleaned original source code.
    @@ -194,37 +218,25 @@ pushing some extra smarts into the Lexer.
    -

    tokenize is the Lexer's main method. Scan by attempting to match tokens -one at a time, using a regular expression anchored at the start of the -remaining code, or a custom recursive token-matching method -(for interpolations). When the next token has been recorded, we move forward -within the code past the token, and begin again. - -

    -

    Each tokenizing method is responsible for returning the number of characters -it has consumed. - -

    -

    Before returning the token stream, run it through the Rewriter -unless explicitly asked not to. -

    +

    At every position, run through this list of attempted matches, +short-circuiting if any of them succeed. Their order determines precedence: +@literalToken is the fallback catch-all.

    -
      tokenize: (code, opts = {}) ->
    -    @literate = opts.literate  # Are we lexing literate CoffeeScript?
    -    @indent   = 0              # The current indentation level.
    -    @indebt   = 0              # The over-indentation at the current level.
    -    @outdebt  = 0              # The under-outdentation at the current level.
    -    @indents  = []             # The stack of all current indentation levels.
    -    @ends     = []             # The stack for pairing up tokens.
    -    @tokens   = []             # Stream of parsed tokens in the form `['TYPE', value, location data]`.
    -
    -    @chunkLine =
    -        opts.line or 0         # The start line for the current @chunk.
    -    @chunkColumn =
    -        opts.column or 0       # The start column of the current @chunk.
    -    code = @clean code         # The stripped, cleaned original source code.
    +
        i = 0
    +    while @chunk = code[i..]
    +      consumed = \
    +           @identifierToken() or
    +           @commentToken()    or
    +           @whitespaceToken() or
    +           @lineToken()       or
    +           @heredocToken()    or
    +           @stringToken()     or
    +           @numberToken()     or
    +           @regexToken()      or
    +           @jsToken()         or
    +           @literalToken()
    @@ -235,26 +247,18 @@ unless explicitly asked not to.
    -

    At every position, run through this list of attempted matches, -short-circuiting if any of them succeed. Their order determines precedence: -@literalToken is the fallback catch-all. -

    +

    Update position

    -
        i = 0
    -    while @chunk = code[i..]
    -      consumed = \
    -           @identifierToken() or
    -           @commentToken()    or
    -           @whitespaceToken() or
    -           @lineToken()       or
    -           @heredocToken()    or
    -           @stringToken()     or
    -           @numberToken()     or
    -           @regexToken()      or
    -           @jsToken()         or
    -           @literalToken()
    +
          [@chunkLine, @chunkColumn] = @getLineAndColumnFromChunk consumed
    +
    +      i += consumed
    +
    +    @closeIndentation()
    +    @error "missing #{tag}" if tag = @ends.pop()
    +    return @tokens if opts.rewrite is off
    +    (new Rewriter).rewrite @tokens
    @@ -265,19 +269,20 @@ short-circuiting if any of them succeed. Their order determines precedence:
    -

    Update position -

    +

    Preprocess the code to remove leading and trailing whitespace, carriage +returns, etc. If we're lexing literate CoffeeScript, strip external Markdown +by removing all lines that aren't indented by at least four spaces or a tab.

    -
          [@chunkLine, @chunkColumn] = @getLineAndColumnFromChunk consumed
    -
    -      i += consumed
    -
    -    @closeIndentation()
    -    @error "missing #{tag}" if tag = @ends.pop()
    -    return @tokens if opts.rewrite is off
    -    (new Rewriter).rewrite @tokens
    +
      clean: (code) ->
    +    code = code.slice(1) if code.charCodeAt(0) is BOM
    +    code = code.replace(/\r/g, '').replace TRAILING_SPACES, ''
    +    if WHITESPACE.test code
    +      code = "\n#{code}"
    +      @chunkLine--
    +    code = invertLiterate code if @literate
    +    code
    @@ -288,35 +293,32 @@ short-circuiting if any of them succeed. Their order determines precedence:
    -

    Preprocess the code to remove leading and trailing whitespace, carriage -returns, etc. If we're lexing literate CoffeeScript, strip external Markdown -by removing all lines that aren't indented by at least four spaces or a tab. -

    +

    Tokenizers

    -
      clean: (code) ->
    -    code = code.slice(1) if code.charCodeAt(0) is BOM
    -    code = code.replace(/\r/g, '').replace TRAILING_SPACES, ''
    -    if WHITESPACE.test code
    -        code = "\n#{code}"
    -        @chunkLine--
    -    code = invertLiterate code if @literate
    -    code
    -
  • -
    +
    -

    Tokenizers

    +

    Matches identifying literals: variables, keywords, method names, etc. +Check to ensure that JavaScript reserved words aren't being used as +identifiers. Because CoffeeScript reserves a handful of keywords that are +allowed in JavaScript, we're careful not to tag them as keywords when +referenced as property names here, so you can still do jQuery.is() even +though is means === otherwise.

    +
      identifierToken: ->
    +    return 0 unless match = IDENTIFIER.exec @chunk
    +    [input, id, colon] = match
    +
  • @@ -326,9 +328,69 @@ by removing all lines that aren't indented by at least four spaces or a tab.
    - +

    Preserve length of id for location data

    +
    +
        idLength = id.length
    +    poppedToken = undefined
    +
    +    if id is 'own' and @tag() is 'FOR'
    +      @token 'OWN', id
    +      return id.length
    +    forcedIdentifier = colon or
    +      (prev = last @tokens) and (prev[0] in ['.', '?.', '::', '?::'] or
    +      not prev.spaced and prev[0] is '@')
    +    tag = 'IDENTIFIER'
    +
    +    if not forcedIdentifier and (id in JS_KEYWORDS or id in COFFEE_KEYWORDS)
    +      tag = id.toUpperCase()
    +      if tag is 'WHEN' and @tag() in LINE_BREAK
    +        tag = 'LEADING_WHEN'
    +      else if tag is 'FOR'
    +        @seenFor = yes
    +      else if tag is 'UNLESS'
    +        tag = 'IF'
    +      else if tag in UNARY
    +        tag = 'UNARY'
    +      else if tag in RELATION
    +        if tag isnt 'INSTANCEOF' and @seenFor
    +          tag = 'FOR' + tag
    +          @seenFor = no
    +        else
    +          tag = 'RELATION'
    +          if @value() is '!'
    +            poppedToken = @tokens.pop()
    +            id = '!' + id
    +
    +    if id in JS_FORBIDDEN
    +      if forcedIdentifier
    +        tag = 'IDENTIFIER'
    +        id  = new String id
    +        id.reserved = yes
    +      else if id in RESERVED
    +        @error "reserved word \"#{id}\""
    +
    +    unless forcedIdentifier
    +      id  = COFFEE_ALIAS_MAP[id] if id in COFFEE_ALIASES
    +      tag = switch id
    +        when '!'                 then 'UNARY'
    +        when '==', '!='          then 'COMPARE'
    +        when '&&', '||'          then 'LOGIC'
    +        when 'true', 'false'     then 'BOOL'
    +        when 'break', 'continue' then 'STATEMENT'
    +        else  tag
    +
    +    tagToken = @token tag, id, 0, idLength
    +    if poppedToken
    +      [tagToken[2].first_line, tagToken[2].first_column] =
    +        [poppedToken[2].first_line, poppedToken[2].first_column]
    +    if colon
    +      colonOffset = input.lastIndexOf ':'
    +      @token ':', ':', colonOffset, colon.length
    +
    +    input.length
    + @@ -338,19 +400,29 @@ by removing all lines that aren't indented by at least four spaces or a tab.
    -

    Matches identifying literals: variables, keywords, method names, etc. -Check to ensure that JavaScript reserved words aren't being used as -identifiers. Because CoffeeScript reserves a handful of keywords that are -allowed in JavaScript, we're careful not to tag them as keywords when -referenced as property names here, so you can still do jQuery.is() even -though is means === otherwise. -

    +

    Matches numbers, including decimals, hex, and exponential notation. +Be careful not to interfere with ranges-in-progress.

    -
      identifierToken: ->
    -    return 0 unless match = IDENTIFIER.exec @chunk
    -    [input, id, colon] = match
    +
      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 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, 0, lexedLength
    +    lexedLength
    @@ -361,69 +433,24 @@ though is means === otherwise.
    -

    Preserve length of id for location data -

    +

    Matches strings, including multi-line strings. Ensures that quotation marks +are balanced within the string's contents, and within nested interpolations.

    -
        idLength = id.length
    -    poppedToken = undefined
    -
    -    if id is 'own' and @tag() is 'FOR'
    -      @token 'OWN', id
    -      return id.length
    -    forcedIdentifier = colon or
    -      (prev = last @tokens) and (prev[0] in ['.', '?.', '::', '?::'] or
    -      not prev.spaced and prev[0] is '@')
    -    tag = 'IDENTIFIER'
    -
    -    if not forcedIdentifier and (id in JS_KEYWORDS or id in COFFEE_KEYWORDS)
    -      tag = id.toUpperCase()
    -      if tag is 'WHEN' and @tag() in LINE_BREAK
    -        tag = 'LEADING_WHEN'
    -      else if tag is 'FOR'
    -        @seenFor = yes
    -      else if tag is 'UNLESS'
    -        tag = 'IF'
    -      else if tag in UNARY
    -        tag = 'UNARY'
    -      else if tag in RELATION
    -        if tag isnt 'INSTANCEOF' and @seenFor
    -          tag = 'FOR' + tag
    -          @seenFor = no
    -        else
    -          tag = 'RELATION'
    -          if @value() is '!'
    -            poppedToken = @tokens.pop()
    -            id = '!' + id
    -
    -    if id in JS_FORBIDDEN
    -      if forcedIdentifier
    -        tag = 'IDENTIFIER'
    -        id  = new String id
    -        id.reserved = yes
    -      else if id in RESERVED
    -        @error "reserved word \"#{id}\""
    -
    -    unless forcedIdentifier
    -      id  = COFFEE_ALIAS_MAP[id] if id in COFFEE_ALIASES
    -      tag = switch id
    -        when '!'                 then 'UNARY'
    -        when '==', '!='          then 'COMPARE'
    -        when '&&', '||'          then 'LOGIC'
    -        when 'true', 'false'     then 'BOOL'
    -        when 'break', 'continue' then 'STATEMENT'
    -        else  tag
    -
    -    tagToken = @token tag, id, 0, idLength
    -    if poppedToken
    -      [tagToken[2].first_line, tagToken[2].first_column] =
    -        [poppedToken[2].first_line, poppedToken[2].first_column]
    -    if colon
    -      colonOffset = input.lastIndexOf ':'
    -      @token ':', ':', colonOffset, colon.length
    -
    -    input.length
    +
      stringToken: ->
    +    switch quote = @chunk.charAt 0
    +      when "'" then [string] = SIMPLESTR.exec @chunk
    +      when '"' then string = @balancedString @chunk, '"'
    +    return 0 unless string
    +    trimmed = @removeNewlines string[1...-1]
    +    if quote is '"' and 0 < string.indexOf '#{', 1
    +      @interpolateString trimmed, strOffset: 1, lexedLength: string.length
    +    else
    +      @token 'STRING', quote + @escapeLines(trimmed) + quote, 0, string.length
    +    if octalEsc = /^(?:\\.|[^\\])*\\(?:0[0-7]|[1-7])/.test string
    +      @error "octal escape sequences #{string} are not allowed"
    +    string.length
    @@ -434,30 +461,21 @@ though is means === otherwise.
    -

    Matches numbers, including decimals, hex, and exponential notation. -Be careful not to interfere with ranges-in-progress. -

    +

    Matches heredocs, adjusting indentation to the correct level, as heredocs +preserve whitespace, but ignore indentation to the left.

    -
      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 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, 0, lexedLength
    -    lexedLength
    +
      heredocToken: ->
    +    return 0 unless match = HEREDOC.exec @chunk
    +    heredoc = match[0]
    +    quote = heredoc.charAt 0
    +    doc = @sanitizeHeredoc match[2], quote: quote, indent: null
    +    if quote is '"' and 0 <= doc.indexOf '#{'
    +      @interpolateString doc, heredoc: yes, strOffset: 3, lexedLength: heredoc.length
    +    else
    +      @token 'STRING', @makeString(doc, quote, yes), 0, heredoc.length
    +    heredoc.length
    @@ -468,29 +486,19 @@ Be careful not to interfere with ranges-in-progress.
    -

    Matches strings, including multi-line strings. Ensures that quotation marks -are balanced within the string's contents, and within nested interpolations. -

    +

    Matches and consumes comments.

    -
      stringToken: ->
    -    switch @chunk.charAt 0
    -      when "'"
    -        return 0 unless match = SIMPLESTR.exec @chunk
    -        string = match[0]
    -        @token 'STRING', string.replace(MULTILINER, '\\\n'), 0, string.length
    -      when '"'
    -        return 0 unless string = @balancedString @chunk, '"'
    -        if 0 < string.indexOf '#{', 1
    -          @interpolateString string[1...-1], strOffset: 1, lexedLength: string.length
    -        else
    -          @token 'STRING', @escapeLines string, 0, string.length
    -      else
    -        return 0
    -    if octalEsc = /^(?:\\.|[^\\])*\\(?:0[0-7]|[1-7])/.test string
    -      @error "octal escape sequences #{string} are not allowed"
    -    string.length
    +
      commentToken: ->
    +    return 0 unless match = @chunk.match COMMENT
    +    [comment, here] = match
    +    if here
    +      @token 'HERECOMMENT',
    +        (@sanitizeHeredoc here,
    +          herecomment: true, indent: repeat ' ', @indent),
    +        0, comment.length
    +    comment.length
    @@ -501,22 +509,14 @@ are balanced within the string's contents, and within nested interpolations.
    -

    Matches heredocs, adjusting indentation to the correct level, as heredocs -preserve whitespace, but ignore indentation to the left. -

    +

    Matches JavaScript interpolated directly into the source via backticks.

    -
      heredocToken: ->
    -    return 0 unless match = HEREDOC.exec @chunk
    -    heredoc = match[0]
    -    quote = heredoc.charAt 0
    -    doc = @sanitizeHeredoc match[2], quote: quote, indent: null
    -    if quote is '"' and 0 <= doc.indexOf '#{'
    -      @interpolateString doc, heredoc: yes, strOffset: 3, lexedLength: heredoc.length
    -    else
    -      @token 'STRING', @makeString(doc, quote, yes), 0, heredoc.length
    -    heredoc.length
    +
      jsToken: ->
    +    return 0 unless @chunk.charAt(0) is '`' and match = JSTOKEN.exec @chunk
    +    @token 'JS', (script = match[0])[1...-1], 0, script.length
    +    script.length
    @@ -527,20 +527,20 @@ preserve whitespace, but ignore indentation to the left.
    -

    Matches and consumes comments. -

    +

    Matches regular expression literals. Lexing regular expressions is difficult +to distinguish from division, so we borrow some basic heuristics from +JavaScript and Ruby.

    -
      commentToken: ->
    -    return 0 unless match = @chunk.match COMMENT
    -    [comment, here] = match
    -    if here
    -      @token 'HERECOMMENT',
    -        (@sanitizeHeredoc here,
    -          herecomment: true, indent: repeat ' ', @indent),
    -        0, comment.length
    -    comment.length
    +
      regexToken: ->
    +    return 0 if @chunk.charAt(0) isnt '/'
    +    return length if length = @heregexToken()
    +
    +    prev = last @tokens
    +    return 0 if prev and (prev[0] in (if prev.spaced then NOT_REGEX else NOT_SPACED_REGEX))
    +    return 0 unless match = REGEX.exec @chunk
    +    [match, regex, flags] = match
    @@ -551,15 +551,14 @@ preserve whitespace, but ignore indentation to the left.
    -

    Matches JavaScript interpolated directly into the source via backticks. -

    +

    Avoid conflicts with floor division operator.

    -
      jsToken: ->
    -    return 0 unless @chunk.charAt(0) is '`' and match = JSTOKEN.exec @chunk
    -    @token 'JS', (script = match[0])[1...-1], 0, script.length
    -    script.length
    +
        return 0 if regex is '//'
    +    if regex[..1] is '/*' then @error 'regular expressions cannot begin with `*`'
    +    @token 'REGEX', "#{regex}#{flags}", 0, match.length
    +    match.length
    @@ -570,27 +569,28 @@ preserve whitespace, but ignore indentation to the left.
    -

    Matches regular expression literals. Lexing regular expressions is difficult -to distinguish from division, so we borrow some basic heuristics from -JavaScript and Ruby. -

    +

    Matches multiline extended regular expressions.

    -
      regexToken: ->
    -    return 0 if @chunk.charAt(0) isnt '/'
    -    if match = HEREGEX.exec @chunk
    -      length = @heregexToken match
    -      return length
    -
    -    prev = last @tokens
    -    return 0 if prev and (prev[0] in (if prev.spaced then NOT_REGEX else NOT_SPACED_REGEX))
    -    return 0 unless match = REGEX.exec @chunk
    -    [match, regex, flags] = match
    -    if regex[..1] is '/*' then @error 'regular expressions cannot begin with `*`'
    -    if regex is '//' then regex = '/(?:)/'
    -    @token 'REGEX', "#{regex}#{flags}", 0, match.length
    -    match.length
    +
      heregexToken: ->
    +    return 0 unless match = HEREGEX.exec @chunk
    +    [heregex, body, flags] = match
    +    if 0 > body.indexOf '#{'
    +      re = @escapeLines body.replace(HEREGEX_OMIT, '$1$2').replace(/\//g, '\\/'), yes
    +      if re.match /^\*/ then @error 'regular expressions cannot begin with `*`'
    +      @token 'REGEX', "/#{ re or '(?:)' }/#{flags}", 0, heregex.length
    +      return heregex.length
    +    @token 'IDENTIFIER', 'RegExp', 0, 0
    +    @token 'CALL_START', '(', 0, 0
    +    tokens = []
    +    for token in @interpolateString(body, regex: yes)
    +      [tag, value] = token
    +      if tag is 'TOKENS'
    +        tokens.push value...
    +      else if tag is 'NEOSTRING'
    +        continue unless value = value.replace HEREGEX_OMIT, '$1$2'
    +
    @@ -601,27 +601,21 @@ JavaScript and Ruby.
    -

    Matches multiline extended regular expressions. -

    +

    Convert NEOSTRING into STRING

    -
      heregexToken: (match) ->
    -    [heregex, body, flags] = match
    -    if 0 > body.indexOf '#{'
    -      re = body.replace(HEREGEX_OMIT, '').replace(/\//g, '\\/')
    -      if re.match /^\*/ then @error 'regular expressions cannot begin with `*`'
    -      @token 'REGEX', "/#{ re or '(?:)' }/#{flags}", 0, heregex.length
    -      return heregex.length
    -    @token 'IDENTIFIER', 'RegExp', 0, 0
    -    @token 'CALL_START', '(', 0, 0
    -    tokens = []
    -    for token in @interpolateString(body, regex: yes)
    -      [tag, value] = token
    -      if tag is 'TOKENS'
    -        tokens.push value...
    -      else if tag is 'NEOSTRING'
    -        continue unless value = value.replace HEREGEX_OMIT, ''
    +
            value = value.replace /\\/g, '\\\\'
    +        token[0] = 'STRING'
    +        token[1] = @makeString(value, '"', yes)
    +        tokens.push token
    +      else
    +        @error "Unexpected #{tag}"
    +
    +      prev = last @tokens
    +      plusToken = ['+', '+']
    +      plusToken[2] = prev[2] # Copy location data
    +      tokens.push plusToken
    @@ -632,22 +626,18 @@ JavaScript and Ruby.
    -

    Convert NEOSTRING into STRING -

    +

    Remove the extra "+"

    -
            value = value.replace /\\/g, '\\\\'
    -        token[0] = 'STRING'
    -        token[1] = @makeString(value, '"', yes)
    -        tokens.push token
    -      else
    -        @error "Unexpected #{tag}"
    +            
        tokens.pop()
     
    -      prev = last @tokens
    -      plusToken = ['+', '+']
    -      plusToken[2] = prev[2] # Copy location data
    -      tokens.push plusToken
    + unless tokens[0]?[0] is 'STRING' + @token 'STRING', '""', 0, 0 + @token '+', '+', 0, 0 + @tokens.push tokens... + + if flags
    @@ -658,19 +648,16 @@ JavaScript and Ruby.
    -

    Remove the extra "+" -

    +

    Find the flags in the heregex

    -
        tokens.pop()
    +            
          flagsOffset = heregex.lastIndexOf flags
    +      @token ',', ',', flagsOffset, 0
    +      @token 'STRING', '"' + flags + '"', flagsOffset, flags.length
     
    -    unless tokens[0]?[0] is 'STRING'
    -      @token 'STRING', '""', 0, 0
    -      @token '+', '+', 0, 0
    -    @tokens.push tokens...
    -
    -    if flags
    + @token ')', ')', heregex.length-1, 0 + heregex.length
    @@ -681,17 +668,47 @@ JavaScript and Ruby.
    -

    Find the flags in the heregex -

    +

    Matches newlines, indents, and outdents, and determines which is which. +If we can detect that the current line is continued onto the the next line, +then the newline is suppressed:

    +
    elements
    +  .each( ... )
    +  .map( ... )
    +

    Keeps track of the level of indentation, because a single outdent token +can close multiple indents, so we need to know how far in we happen to be.

    -
          flagsOffset = heregex.lastIndexOf flags
    -      @token ',', ',', flagsOffset, 0
    -      @token 'STRING', '"' + flags + '"', flagsOffset, flags.length
    +            
      lineToken: ->
    +    return 0 unless match = MULTI_DENT.exec @chunk
    +    indent = match[0]
    +    @seenFor = no
    +    size = indent.length - 1 - indent.lastIndexOf '\n'
    +    noNewlines = @unfinished()
    +    if size - @indebt is @indent
    +      if noNewlines then @suppressNewlines() else @newlineToken 0
    +      return indent.length
     
    -    @token ')', ')', heregex.length-1, 0
    -    heregex.length
    + if size > @indent + if noNewlines + @indebt = size - @indent + @suppressNewlines() + return indent.length + unless @tokens.length + @baseIndent = @indent = size + return indent.length + diff = size - @indent + @outdebt + @token 'INDENT', diff, indent.length - size, size + @indents.push diff + @ends.push 'OUTDENT' + @outdebt = @indebt = 0 + @indent = size + else if size < @baseIndent + @error 'missing indentation', indent.length + else + @indebt = 0 + @outdentToken @indent - size, noNewlines, indent.length + indent.length
    @@ -702,45 +719,29 @@ JavaScript and Ruby.
    -

    Matches newlines, indents, and outdents, and determines which is which. -If we can detect that the current line is continued onto the the next line, -then the newline is suppressed: - -

    -
    elements
    -  .each( ... )
    -  .map( ... )
    -

    Keeps track of the level of indentation, because a single outdent token -can close multiple indents, so we need to know how far in we happen to be. -

    +

    Record an outdent token or multiple tokens, if we happen to be moving back +inwards past several recorded indents. Sets new @indent value.

    -
      lineToken: ->
    -    return 0 unless match = MULTI_DENT.exec @chunk
    -    indent = match[0]
    -    @seenFor = no
    -    size = indent.length - 1 - indent.lastIndexOf '\n'
    -    noNewlines = @unfinished()
    -    if size - @indebt is @indent
    -      if noNewlines then @suppressNewlines() else @newlineToken 0
    -      return indent.length
    -
    -    if size > @indent
    -      if noNewlines
    -        @indebt = size - @indent
    -        @suppressNewlines()
    -        return indent.length
    -      diff = size - @indent + @outdebt
    -      @token 'INDENT', diff, indent.length - size, size
    -      @indents.push diff
    -      @ends.push 'OUTDENT'
    -      @outdebt = @indebt = 0
    -    else
    -      @indebt = 0
    -      @outdentToken @indent - size, noNewlines, indent.length
    -    @indent = size
    -    indent.length
    +
      outdentToken: (moveOut, noNewlines, outdentLength) ->
    +    decreasedIndent = @indent - moveOut
    +    while moveOut > 0
    +      lastIndent = @indents[@indents.length - 1]
    +      if not lastIndent
    +        moveOut = 0
    +      else if lastIndent is @outdebt
    +        moveOut -= @outdebt
    +        @outdebt = 0
    +      else if lastIndent < @outdebt
    +        @outdebt -= lastIndent
    +        moveOut  -= lastIndent
    +      else
    +        dent = @indents.pop() + @outdebt
    +        if outdentLength and @chunk[outdentLength] in INDENTABLE_CLOSERS
    +          decreasedIndent -= dent - moveOut
    +          moveOut = dent
    +        @outdebt = 0
    @@ -751,34 +752,19 @@ can close multiple indents, so we need to know how far in we happen to be.
    -

    Record an outdent token or multiple tokens, if we happen to be moving back -inwards past several recorded indents. -

    +

    pair might call outdentToken, so preserve decreasedIndent

    -
      outdentToken: (moveOut, noNewlines, outdentLength) ->
    -    while moveOut > 0
    -      len = @indents.length - 1
    -      if @indents[len] is undefined
    -        moveOut = 0
    -      else if @indents[len] is @outdebt
    -        moveOut -= @outdebt
    -        @outdebt = 0
    -      else if @indents[len] < @outdebt
    -        @outdebt -= @indents[len]
    -        moveOut  -= @indents[len]
    -      else
    -        dent = @indents.pop() + @outdebt
    +            
            @pair 'OUTDENT'
    +        @token 'OUTDENT', moveOut, 0, outdentLength
             moveOut -= dent
    -        @outdebt = 0
    -        @pair 'OUTDENT'
    -        @token 'OUTDENT', dent, 0, outdentLength
    -    @outdebt -= moveOut if dent
    -    @tokens.pop() while @value() is ';'
    +    @outdebt -= moveOut if dent
    +    @tokens.pop() while @value() is ';'
     
    -    @token 'TERMINATOR', '\n', outdentLength, 0 unless @tag() is 'TERMINATOR' or noNewlines
    -    this
    + @token 'TERMINATOR', '\n', outdentLength, 0 unless @tag() is 'TERMINATOR' or noNewlines + @indent = decreasedIndent + this
    @@ -790,17 +776,16 @@ inwards past several recorded indents.

    Matches and consumes non-meaningful whitespace. Tag the previous token -as being "spaced", because there are some cases where it makes a difference. -

    +as being "spaced", because there are some cases where it makes a difference.

    -
      whitespaceToken: ->
    -    return 0 unless (match = WHITESPACE.exec @chunk) or
    -                    (nline = @chunk.charAt(0) is '\n')
    -    prev = last @tokens
    -    prev[if match then 'spaced' else 'newLine'] = true if prev
    -    if match then match[0].length else 0
    +
      whitespaceToken: ->
    +    return 0 unless (match = WHITESPACE.exec @chunk) or
    +                    (nline = @chunk.charAt(0) is '\n')
    +    prev = last @tokens
    +    prev[if match then 'spaced' else 'newLine'] = true if prev
    +    if match then match[0].length else 0
    @@ -811,15 +796,14 @@ as being "spaced", because there are some cases where it makes a diffe
    -

    Generate a newline token. Consecutive newlines get merged together. -

    +

    Generate a newline token. Consecutive newlines get merged together.

    -
      newlineToken: (offset) ->
    -    @tokens.pop() while @value() is ';'
    -    @token 'TERMINATOR', '\n', offset, 0 unless @tag() is 'TERMINATOR'
    -    this
    +
      newlineToken: (offset) ->
    +    @tokens.pop() while @value() is ';'
    +    @token 'TERMINATOR', '\n', offset, 0 unless @tag() is 'TERMINATOR'
    +    this
    @@ -831,14 +815,13 @@ as being "spaced", because there are some cases where it makes a diffe

    Use a \ at a line-ending to suppress the newline. -The slash is removed here once its job is done. -

    +The slash is removed here once its job is done.

    -
      suppressNewlines: ->
    -    @tokens.pop() if @value() is '\\'
    -    this
    +
      suppressNewlines: ->
    +    @tokens.pop() if @value() is '\\'
    +    this
    @@ -853,47 +836,47 @@ The slash is removed here once its job is done. Multi-character operators are also literal tokens, so that Jison can assign the proper order of operations. There are some symbols that we tag specially here. ; and newlines are both treated as a TERMINATOR, we distinguish -parentheses that indicate a method call from regular parentheses, and so on. -

    +parentheses that indicate a method call from regular parentheses, and so on.

    -
      literalToken: ->
    -    if match = OPERATOR.exec @chunk
    +            
      literalToken: ->
    +    if match = OPERATOR.exec @chunk
           [value] = match
    -      @tagParameters() if CODE.test value
    -    else
    -      value = @chunk.charAt 0
    +      @tagParameters() if CODE.test value
    +    else
    +      value = @chunk.charAt 0
         tag  = value
    -    prev = last @tokens
    -    if value is '=' and prev
    -      if not prev[1].reserved and prev[1] in JS_FORBIDDEN
    -        @error "reserved word \"#{@value()}\" can't be assigned"
    -      if prev[1] in ['||', '&&']
    -        prev[0] = 'COMPOUND_ASSIGN'
    -        prev[1] += '='
    -        return value.length
    -    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'
    -    else if value in UNARY           then tag = 'UNARY'
    -    else if value in SHIFT           then tag = 'SHIFT'
    -    else if value in LOGIC or value is '?' and prev?.spaced then tag = 'LOGIC'
    -    else if prev and not prev.spaced
    -      if value is '(' and prev[0] in CALLABLE
    -        prev[0] = 'FUNC_EXIST' if prev[0] is '?'
    -        tag = 'CALL_START'
    -      else if value is '[' and prev[0] in INDEXABLE
    -        tag = 'INDEX_START'
    -        switch prev[0]
    -          when '?'  then prev[0] = 'INDEX_SOAK'
    -    switch value
    -      when '(', '{', '[' then @ends.push INVERSES[value]
    -      when ')', '}', ']' then @pair value
    -    @token tag, value
    +    prev = last @tokens
    +    if value is '=' and prev
    +      if not prev[1].reserved and prev[1] in JS_FORBIDDEN
    +        @error "reserved word \"#{@value()}\" can't be assigned"
    +      if prev[1] in ['||', '&&']
    +        prev[0] = 'COMPOUND_ASSIGN'
    +        prev[1] += '='
    +        return value.length
    +    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'
    +    else if value in UNARY           then tag = 'UNARY'
    +    else if value in UNARY_MATH      then tag = 'UNARY_MATH'
    +    else if value in SHIFT           then tag = 'SHIFT'
    +    else if value in LOGIC or value is '?' and prev?.spaced then tag = 'LOGIC'
    +    else if prev and not prev.spaced
    +      if value is '(' and prev[0] in CALLABLE
    +        prev[0] = 'FUNC_EXIST' if prev[0] is '?'
    +        tag = 'CALL_START'
    +      else if value is '[' and prev[0] in INDEXABLE
    +        tag = 'INDEX_START'
    +        switch prev[0]
    +          when '?'  then prev[0] = 'INDEX_SOAK'
    +    switch value
    +      when '(', '{', '[' then @ends.push INVERSES[value]
    +      when ')', '}', ']' then @pair value
    +    @token tag, value
         value.length
    @@ -902,10 +885,10 @@ parentheses that indicate a method call from regular parentheses, and so on.
  • -
    +
    -

    Token Manipulators

    +

    Token Manipulators

    @@ -918,9 +901,25 @@ parentheses that indicate a method call from regular parentheses, and so on.
    - +

    Sanitize a heredoc or herecomment by +erasing all external indentation on the left-hand side.

    +
    +
      sanitizeHeredoc: (doc, options) ->
    +    {indent, herecomment} = options
    +    if herecomment
    +      if HEREDOC_ILLEGAL.test doc
    +        @error "block comment cannot contain \"*/\", starting"
    +      return doc if doc.indexOf('\n') < 0
    +    else
    +      while match = HEREDOC_INDENT.exec doc
    +        attempt = match[1]
    +        indent = attempt if indent is null or 0 < attempt.length < indent.length
    +    doc = doc.replace /// \n #{indent} ///g, '\n' if indent
    +    doc = doc.replace /^\n/, '' unless herecomment
    +    doc
    +
  • @@ -930,25 +929,29 @@ parentheses that indicate a method call from regular parentheses, and so on.
    -

    Sanitize a heredoc or herecomment by -erasing all external indentation on the left-hand side. -

    +

    A source of ambiguity in our grammar used to be parameter lists in function +definitions versus argument lists in function calls. Walk backwards, tagging +parameters specially in order to make things easier for the parser.

    -
      sanitizeHeredoc: (doc, options) ->
    -    {indent, herecomment} = options
    -    if herecomment
    -      if HEREDOC_ILLEGAL.test doc
    -        @error "block comment cannot contain \"*/\", starting"
    -      return doc if doc.indexOf('\n') < 0
    -    else
    -      while match = HEREDOC_INDENT.exec doc
    -        attempt = match[1]
    -        indent = attempt if indent is null or 0 < attempt.length < indent.length
    -    doc = doc.replace /// \n #{indent} ///g, '\n' if indent
    -    doc = doc.replace /^\n/, '' unless herecomment
    -    doc
    +
      tagParameters: ->
    +    return this if @tag() isnt ')'
    +    stack = []
    +    {tokens} = this
    +    i = tokens.length
    +    tokens[--i][0] = 'PARAM_END'
    +    while tok = tokens[--i]
    +      switch tok[0]
    +        when ')'
    +          stack.push tok
    +        when '(', 'CALL_START'
    +          if stack.length then stack.pop()
    +          else if tok[0] is '('
    +            tok[0] = 'PARAM_START'
    +            return this
    +          else return this
    +    this
    @@ -959,30 +962,12 @@ erasing all external indentation on the left-hand side.
    -

    A source of ambiguity in our grammar used to be parameter lists in function -definitions versus argument lists in function calls. Walk backwards, tagging -parameters specially in order to make things easier for the parser. -

    +

    Close up all remaining open blocks at the end of the file.

    -
      tagParameters: ->
    -    return this if @tag() isnt ')'
    -    stack = []
    -    {tokens} = this
    -    i = tokens.length
    -    tokens[--i][0] = 'PARAM_END'
    -    while tok = tokens[--i]
    -      switch tok[0]
    -        when ')'
    -          stack.push tok
    -        when '(', 'CALL_START'
    -          if stack.length then stack.pop()
    -          else if tok[0] is '('
    -            tok[0] = 'PARAM_START'
    -            return this
    -          else return this
    -    this
    +
      closeIndentation: ->
    +    @outdentToken @indent
    @@ -993,13 +978,40 @@ parameters specially in order to make things easier for the parser.
    -

    Close up all remaining open blocks at the end of the file. -

    +

    Matches a balanced group such as a single or double-quoted string. Pass in +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.

    -
      closeIndentation: ->
    -    @outdentToken @indent
    +
      balancedString: (str, end) ->
    +    continueCount = 0
    +    stack = [end]
    +    for i in [1...str.length]
    +      if continueCount
    +        --continueCount
    +        continue
    +      switch letter = str.charAt i
    +        when '\\'
    +          ++continueCount
    +          continue
    +        when end
    +          stack.pop()
    +          unless stack.length
    +            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[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 '{'
    +        stack.push end = '}'
    +      prev = letter
    +    @error "missing #{ stack.pop() }, starting"
    @@ -1010,61 +1022,12 @@ parameters specially in order to make things easier for the parser.
    -

    Matches a balanced group such as a single or double-quoted string. Pass in -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 '\\'
    -          ++continueCount
    -          continue
    -        when end
    -          stack.pop()
    -          unless stack.length
    -            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[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 '{'
    -        stack.push end = '}'
    -      prev = letter
    -    @error "missing #{ stack.pop() }, starting"
    - - - - -
  • -
    - -
    - -

    Expand variables and expressions inside double-quoted strings using -Ruby-like notation for substitution of arbitrary expressions. - -

    -
    "Hello #{name.capitalize()}."
    -

    If it encounters an interpolation, this method will recursively create a +Ruby-like notation for substitution of arbitrary expressions.

    +
    "Hello #{name.capitalize()}."
    +

    If it encounters an interpolation, this method will recursively create a new Lexer, tokenize the interpolated contents, and merge them into the -token stream. - -

    +token stream.

    • str is the start of the string contents (IE with the " or """ stripped off.)
    • @@ -1079,11 +1042,35 @@ current chunk.
    -
      interpolateString: (str, options = {}) ->
    +            
      interpolateString: (str, options = {}) ->
         {heredoc, regex, offsetInChunk, strOffset, lexedLength} = options
    -    offsetInChunk = offsetInChunk || 0
    -    strOffset = strOffset || 0
    -    lexedLength = lexedLength || str.length
    + offsetInChunk ||= 0 + strOffset ||= 0 + lexedLength ||= str.length
    + +
  • + + +
  • +
    + +
    + +
    +

    Parse the string.

    + +
    + +
        tokens = []
    +    pi = 0
    +    i  = -1
    +    while letter = str.charAt i += 1
    +      if letter is '\\'
    +        i += 1
    +        continue
    +      unless letter is '#' and str.charAt(i+1) is '{' and
    +             (expr = @balancedString str[i + 1..], '}')
    +        continue
  • @@ -1094,14 +1081,23 @@ current chunk.
    -

    Clip leading \n from heredoc -

    +

    NEOSTRING is a fake token. This will be converted to a string below.

    -
        if heredoc and str.length > 0 and str[0] == '\n'
    -      str = str[1...]
    -      strOffset++
    +
          tokens.push @makeToken('NEOSTRING', str[pi...i], strOffset + pi) if pi < i
    +      unless errorToken
    +        errorToken = @makeToken '', 'string interpolation', offsetInChunk + i + 1, 2
    +      inner = expr[1...-1]
    +      if inner.length
    +        [line, column] = @getLineAndColumnFromChunk(strOffset + i + 1)
    +        nested = new Lexer().tokenize inner, line: line, column: column, rewrite: off
    +        popped = nested.pop()
    +        popped = nested.shift() if nested[0]?[0] is 'TERMINATOR'
    +        if len = nested.length
    +          if len > 1
    +            nested.unshift @makeToken '(', '(', strOffset + i + 1, 0
    +            nested.push    @makeToken ')', ')', strOffset + i + 1 + inner.length, 0
    @@ -1112,21 +1108,14 @@ current chunk.
    -

    Parse the string. -

    +

    Push a fake 'TOKENS' token, which will get turned into real tokens below.

    -
        tokens = []
    -    pi = 0
    -    i  = -1
    -    while letter = str.charAt i += 1
    -      if letter is '\\'
    -        i += 1
    -        continue
    -      unless letter is '#' and str.charAt(i+1) is '{' and
    -             (expr = @balancedString str[i + 1..], '}')
    -        continue
    +
              tokens.push ['TOKENS', nested]
    +      i += expr.length
    +      pi = i + 1
    +    tokens.push @makeToken('NEOSTRING', str[pi..], strOffset + pi) if i > pi < str.length
    @@ -1137,22 +1126,11 @@ current chunk.
    -

    NEOSTRING is a fake token. This will be converted to a string below. -

    +

    If regex, then return now and let the regex code deal with all these fake tokens

    -
          tokens.push @makeToken('NEOSTRING', str[pi...i], strOffset + pi) if pi < i
    -      inner = expr[1...-1]
    -      if inner.length
    -        [line, column] = @getLineAndColumnFromChunk(strOffset + i + 1)
    -        nested = new Lexer().tokenize inner, line: line, column: column, rewrite: off
    -        popped = nested.pop()
    -        popped = nested.shift() if nested[0]?[0] is 'TERMINATOR'
    -        if len = nested.length
    -          if len > 1
    -            nested.unshift @makeToken '(', '(', strOffset + i + 1, 0
    -            nested.push    @makeToken ')', ')', strOffset + i + 1 + inner.length, 0
    +
        return tokens if regex
    @@ -1163,15 +1141,11 @@ current chunk.
    -

    Push a fake 'TOKENS' token, which will get turned into real tokens below. -

    +

    If we didn't find any tokens, then just return an empty string.

    -
              tokens.push ['TOKENS', nested]
    -      i += expr.length
    -      pi = i + 1
    -    tokens.push @makeToken('NEOSTRING', str[pi..], strOffset + pi) if i > pi < str.length
    +
        return @token 'STRING', '""', offsetInChunk, lexedLength unless tokens.length
    @@ -1182,12 +1156,14 @@ current chunk.
    -

    If regex, then return now and let the regex code deal with all these fake tokens -

    +

    If the first token is not a string, add a fake empty string to the beginning.

    -
        return tokens if regex
    +
        tokens.unshift @makeToken('NEOSTRING', '', offsetInChunk) unless tokens[0][0] is 'NEOSTRING'
    +
    +    if interpolated = tokens.length > 1
    +      @token '(', '(', offsetInChunk, 0, errorToken
    @@ -1198,12 +1174,13 @@ current chunk.
    -

    If we didn't find any tokens, then just return an empty string. -

    +

    Push all the tokens

    -
        return @token 'STRING', '""', offsetInChunk, lexedLength unless tokens.length
    +
        for token, i in tokens
    +      [tag, value] = token
    +      if i
    @@ -1214,14 +1191,18 @@ current chunk.
    -

    If the first token is not a string, add a fake empty string to the beginning. -

    +

    Create a 0-length "+" token.

    -
        tokens.unshift @makeToken('NEOSTRING', '', offsetInChunk) unless tokens[0][0] is 'NEOSTRING'
    -
    -    @token '(', '(', offsetInChunk, 0 if interpolated = tokens.length > 1
    +
            plusToken = @token '+', '+' if i
    +        locationToken = if tag == 'TOKENS' then value[0] else token
    +        plusToken[2] =
    +          first_line: locationToken[2].first_line
    +          first_column: locationToken[2].first_column
    +          last_line: locationToken[2].first_line
    +          last_column: locationToken[2].first_column
    +      if tag is 'TOKENS'
    @@ -1232,14 +1213,13 @@ current chunk.
    -

    Push all the tokens -

    +

    Push all the tokens in the fake 'TOKENS' token. These already have +sane location data.

    -
        for token, i in tokens
    -      [tag, value] = token
    -      if i
    +
            @tokens.push value...
    +      else if tag is 'NEOSTRING'
    @@ -1250,19 +1230,20 @@ current chunk.
    -

    Create a 0-length "+" token. -

    +

    Convert NEOSTRING into STRING

    -
            plusToken = @token '+', '+' if i
    -        locationToken = if tag == 'TOKENS' then value[0] else token
    -        plusToken[2] =
    -          first_line: locationToken[2].first_line
    -          first_column: locationToken[2].first_column
    -          last_line: locationToken[2].first_line
    -          last_column: locationToken[2].first_column
    -      if tag is 'TOKENS'
    +
            token[0] = 'STRING'
    +        token[1] = @makeString value, '"', heredoc
    +        @tokens.push token
    +      else
    +        @error "Unexpected #{tag}"
    +    if interpolated
    +      rparen = @makeToken ')', ')', offsetInChunk + lexedLength, 0
    +      rparen.stringEnd = true
    +      @tokens.push rparen
    +    tokens
    @@ -1273,14 +1254,14 @@ current chunk.
    -

    Push all the tokens in the fake 'TOKENS' token. These already have -sane location data. -

    +

    Pairs up a closing token, ensuring that all listed pairs of tokens are +correctly balanced throughout the course of the token stream.

    -
            @tokens.push value...
    -      else if tag is 'NEOSTRING'
    +
      pair: (tag) ->
    +    unless tag is wanted = last @ends
    +      @error "unmatched #{tag}" unless 'OUTDENT' is wanted
    @@ -1291,21 +1272,15 @@ sane location data.
    -

    Convert NEOSTRING into STRING -

    - +

    Auto-close INDENT to support syntax like this:

    +
    el.click((event) ->
    +  el.hide())
    +
    -
            token[0] = 'STRING'
    -        token[1] = @makeString value, '"', heredoc
    -        @tokens.push token
    -      else
    -        @error "Unexpected #{tag}"
    -    if interpolated
    -      rparen = @makeToken ')', ')', offsetInChunk + lexedLength, 0
    -      rparen.stringEnd = true
    -      @tokens.push rparen
    -    tokens
    +
          @outdentToken last(@indents), true
    +      return @pair tag
    +    @ends.pop()
    @@ -1316,16 +1291,10 @@ sane location data.
    -

    Pairs up a closing token, ensuring that all listed pairs of tokens are -correctly balanced throughout the course of the token stream. -

    +

    Helpers

    -
      pair: (tag) ->
    -    unless tag is wanted = last @ends
    -      @error "unmatched #{tag}" unless 'OUTDENT' is wanted
    - @@ -1335,18 +1304,30 @@ correctly balanced throughout the course of the token stream.
    -

    Auto-close INDENT to support syntax like this: - -

    -
    el.click((event) ->
    -  el.hide())
    +

    Returns the line and column number from an offset into the current chunk.

    +

    offset is a number of characters into @chunk.

    -
          @indent -= size = last @indents
    -      @outdentToken size, true
    -      return @pair tag
    -    @ends.pop()
    +
      getLineAndColumnFromChunk: (offset) ->
    +    if offset is 0
    +      return [@chunkLine, @chunkColumn]
    +
    +    if offset >= @chunk.length
    +      string = @chunk
    +    else
    +      string = @chunk[..offset-1]
    +
    +    lineCount = count string, '\n'
    +
    +    column = @chunkColumn
    +    if lineCount > 0
    +      lines = string.split '\n'
    +      column = last(lines).length
    +    else
    +      column += string.length
    +
    +    [@chunkLine + lineCount, column]
    @@ -1354,13 +1335,19 @@ correctly balanced throughout the course of the token stream.
  • -
    +
    -

    Helpers

    +

    Same as "token", exception this just returns the token without adding it +to the results.

    +
      makeToken: (tag, value, offsetInChunk = 0, length = value.length) ->
    +    locationData = {}
    +    [locationData.first_line, locationData.first_column] =
    +      @getLineAndColumnFromChunk offsetInChunk
    +
  • @@ -1370,9 +1357,19 @@ correctly balanced throughout the course of the token stream.
    - +

    Use length - 1 for the final offset - we're supplying the last_line and the last_column, +so if last_column == first_column, then we're looking at a character of length 1.

    + +
        lastCharacter = Math.max 0, length - 1
    +    [locationData.last_line, locationData.last_column] =
    +      @getLineAndColumnFromChunk offsetInChunk + lastCharacter
    +
    +    token = [tag, value, locationData]
    +
    +    token
    + @@ -1382,33 +1379,19 @@ correctly balanced throughout the course of the token stream.
    -

    Returns the line and column number from an offset into the current chunk. - -

    -

    offset is a number of characters into @chunk. -

    +

    Add a token to the results. +offset is the offset into the current @chunk where the token starts. +length is the length of the token in the @chunk, after the offset. If +not specified, the length of value will be used.

    +

    Returns the new token.

    -
      getLineAndColumnFromChunk: (offset) ->
    -    if offset is 0
    -      return [@chunkLine, @chunkColumn]
    -
    -    if offset >= @chunk.length
    -      string = @chunk
    -    else
    -      string = @chunk[..offset-1]
    -
    -    lineCount = count string, '\n'
    -
    -    column = @chunkColumn
    -    if lineCount > 0
    -      lines = string.split '\n'
    -      column = last(lines).length
    -    else
    -      column += string.length
    -
    -    [@chunkLine + lineCount, column]
    +
      token: (tag, value, offsetInChunk, length, origin) ->
    +    token = @makeToken tag, value, offsetInChunk, length
    +    token.origin = origin if origin
    +    @tokens.push token
    +    token
    @@ -1419,16 +1402,12 @@ correctly balanced throughout the course of the token stream.
    -

    Same as "token", exception this just returns the token without adding it -to the results. -

    +

    Peek at a tag in the current token stream.

    -
      makeToken: (tag, value, offsetInChunk = 0, length = value.length) ->
    -    locationData = {}
    -    [locationData.first_line, locationData.first_column] =
    -      @getLineAndColumnFromChunk offsetInChunk
    +
      tag: (index, tag) ->
    +    (tok = last @tokens, index) and if tag then tok[0] = tag else tok[0]
    @@ -1439,19 +1418,12 @@ to the results.
    -

    Use length - 1 for the final offset - we're supplying the last_line and the last_column, -so if last_column == first_column, then we're looking at a character of length 1. -

    +

    Peek at a value in the current token stream.

    -
        lastCharacter = Math.max 0, length - 1
    -    [locationData.last_line, locationData.last_column] =
    -      @getLineAndColumnFromChunk offsetInChunk + lastCharacter
    -
    -    token = [tag, value, locationData]
    -
    -    token
    +
      value: (index, val) ->
    +    (tok = last @tokens, index) and if val then tok[1] = val else tok[1]
    @@ -1462,21 +1434,14 @@ so if last_column == first_column, then we're looking at a character of leng
    -

    Add a token to the results. -offset is the offset into the current @chunk where the token starts. -length is the length of the token in the @chunk, after the offset. If -not specified, the length of value will be used. - -

    -

    Returns the new token. -

    +

    Are we in the midst of an unfinished expression?

    -
      token: (tag, value, offsetInChunk, length) ->
    -    token = @makeToken tag, value, offsetInChunk, length
    -    @tokens.push token
    -    token
    +
      unfinished: ->
    +    LINE_CONTINUER.test(@chunk) or
    +    @tag() in ['\\', '.', '?.', '?::', 'UNARY', 'MATH', 'UNARY_MATH', '+', '-',
    +               '**', 'SHIFT', 'RELATION', 'COMPARE', 'LOGIC', 'THROW', 'EXTENDS']
    @@ -1487,13 +1452,13 @@ not specified, the length of value will be used.
    -

    Peek at a tag in the current token stream. -

    +

    Remove newlines from beginning and (non escaped) from end of string literals.

    -
      tag: (index, tag) ->
    -    (tok = last @tokens, index) and if tag then tok[0] = tag else tok[0]
    +
      removeNewlines: (str) ->
    +    str.replace(/^\s*\n\s*/, '')
    +       .replace(/([^\\]|\\\\)\s*\n\s*$/, '$1')
    @@ -1504,13 +1469,11 @@ not specified, the length of value will be used.
    -

    Peek at a value in the current token stream. -

    +

    Converts newlines for string literals.

    -
      value: (index, val) ->
    -    (tok = last @tokens, index) and if val then tok[1] = val else tok[1]
    +
      escapeLines: (str, heredoc) ->
    @@ -1521,15 +1484,16 @@ not specified, the length of value will be used.
    -

    Are we in the midst of an unfinished expression? -

    +

    Ignore escaped backslashes and remove escaped newlines

    -
      unfinished: ->
    -    LINE_CONTINUER.test(@chunk) or
    -    @tag() in ['\\', '.', '?.', '?::', 'UNARY', 'MATH', '+', '-', 'SHIFT', 'RELATION'
    -               'COMPARE', 'LOGIC', 'THROW', 'EXTENDS']
    +
        str = str.replace /\\[^\S\n]*(\n|\\)\s*/g, (escaped, character) ->
    +      if character is '\n' then '' else escaped
    +    if heredoc
    +      str.replace MULTILINER, '\\n'
    +    else
    +      str.replace /\s*\n\s*/g, ' '
    @@ -1540,13 +1504,12 @@ not specified, the length of value will be used.
    -

    Converts newlines for string literals. -

    +

    Constructs a string token by escaping quotes and newlines.

    -
      escapeLines: (str, heredoc) ->
    -    str.replace MULTILINER, if heredoc then '\\n' else ''
    +
      makeString: (body, quote, heredoc) ->
    +    return quote + quote unless body
    @@ -1557,17 +1520,14 @@ not specified, the length of value will be used.
    -

    Constructs a string token by escaping quotes and newlines. -

    +

    Ignore escaped backslashes and unescape quotes

    -
      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
    +
        body = body.replace /// \\( #{quote} | \\ ) ///g, (match, contents) ->
    +      if contents is quote then contents else match
    +    body = body.replace /// #{quote} ///g, '\\$&'
    +    quote + @escapeLines(body, heredoc) + quote
    @@ -1578,12 +1538,11 @@ not specified, the length of value will be used.
    -

    Throws a compiler error on the current position. -

    +

    Throws a compiler error on the current position.

    -
      error: (message) ->
    +
      error: (message, offset = 0) ->
    @@ -1595,12 +1554,12 @@ not specified, the length of value will be used.

    TODO: Are there some cases we could improve the error line number by -passing the offset in the chunk where the error happened? -

    +passing the offset in the chunk where the error happened?

    -
        throwSyntaxError message, first_line: @chunkLine, first_column: @chunkColumn
    +
        [first_line, first_column] = @getLineAndColumnFromChunk offset
    +    throwSyntaxError message, {first_line, first_column}
    @@ -1608,10 +1567,10 @@ passing the offset in the chunk where the error happened?
  • -
    +
    -

    Constants

    +

    Constants

    @@ -1624,9 +1583,18 @@ passing the offset in the chunk where the error happened?
    - +

    Keywords that CoffeeScript shares in common with JavaScript.

    +
    +
    JS_KEYWORDS = [
    +  'true', 'false', 'null', 'this'
    +  'new', 'delete', 'typeof', 'in', 'instanceof'
    +  'return', 'throw', 'break', 'continue', 'debugger'
    +  'if', 'else', 'switch', 'for', 'while', 'do', 'try', 'catch', 'finally'
    +  'class', 'extends', 'super'
    +]
    +
  • @@ -1636,18 +1604,25 @@ passing the offset in the chunk where the error happened?
    -

    Keywords that CoffeeScript shares in common with JavaScript. -

    +

    CoffeeScript-only keywords.

    -
    JS_KEYWORDS = [
    -  'true', 'false', 'null', 'this'
    -  'new', 'delete', 'typeof', 'in', 'instanceof'
    -  'return', 'throw', 'break', 'continue', 'debugger'
    -  'if', 'else', 'switch', 'for', 'while', 'do', 'try', 'catch', 'finally'
    -  'class', 'extends', 'super'
    -]
    +
    COFFEE_KEYWORDS = ['undefined', 'then', 'unless', 'until', 'loop', 'of', 'by', 'when']
    +
    +COFFEE_ALIAS_MAP =
    +  and  : '&&'
    +  or   : '||'
    +  is   : '=='
    +  isnt : '!='
    +  not  : '!'
    +  yes  : 'true'
    +  no   : 'false'
    +  on   : 'true'
    +  off  : 'false'
    +
    +COFFEE_ALIASES  = (key for key of COFFEE_ALIAS_MAP)
    +COFFEE_KEYWORDS = COFFEE_KEYWORDS.concat COFFEE_ALIASES
    @@ -1658,26 +1633,20 @@ passing the offset in the chunk where the error happened?
    -

    CoffeeScript-only keywords. -

    +

    The list of keywords that are reserved by JavaScript, but not used, or are +used by CoffeeScript internally. We throw an error when these are encountered, +to avoid having a JavaScript error at runtime.

    -
    COFFEE_KEYWORDS = ['undefined', 'then', 'unless', 'until', 'loop', 'of', 'by', 'when']
    +            
    RESERVED = [
    +  'case', 'default', 'function', 'var', 'void', 'with', 'const', 'let', 'enum'
    +  'export', 'import', 'native', '__hasProp', '__extends', '__slice', '__bind'
    +  '__indexOf', 'implements', 'interface', 'package', 'private', 'protected'
    +  'public', 'static', 'yield'
    +]
     
    -COFFEE_ALIAS_MAP =
    -  and  : '&&'
    -  or   : '||'
    -  is   : '=='
    -  isnt : '!='
    -  not  : '!'
    -  yes  : 'true'
    -  no   : 'false'
    -  on   : 'true'
    -  off  : 'false'
    -
    -COFFEE_ALIASES  = (key for key of COFFEE_ALIAS_MAP)
    -COFFEE_KEYWORDS = COFFEE_KEYWORDS.concat COFFEE_ALIASES
    +STRICT_PROSCRIBED = ['arguments', 'eval']
    @@ -1688,21 +1657,15 @@ COFFEE_KEYWORDS = COFFEE_KEYWORDS.concat COFFEE_ALIASES
    -

    The list of keywords that are reserved by JavaScript, but not used, or are -used by CoffeeScript internally. We throw an error when these are encountered, -to avoid having a JavaScript error at runtime. -

    +

    The superset of both JavaScript keywords and reserved words, none of which may +be used as identifiers or properties.

    -
    RESERVED = [
    -  'case', 'default', 'function', 'var', 'void', 'with', 'const', 'let', 'enum'
    -  'export', 'import', 'native', '__hasProp', '__extends', '__slice', '__bind'
    -  '__indexOf', 'implements', 'interface', 'package', 'private', 'protected'
    -  'public', 'static', 'yield'
    -]
    +            
    JS_FORBIDDEN = JS_KEYWORDS.concat(RESERVED).concat(STRICT_PROSCRIBED)
     
    -STRICT_PROSCRIBED = ['arguments', 'eval']
    +exports.RESERVED = RESERVED.concat(JS_KEYWORDS).concat(COFFEE_KEYWORDS).concat(STRICT_PROSCRIBED) +exports.STRICT_PROSCRIBED = STRICT_PROSCRIBED
    @@ -1713,16 +1676,11 @@ STRICT_PROSCRIBED = ['arguments', -

    The superset of both JavaScript keywords and reserved words, none of which may -be used as identifiers or properties. -

    +

    The character code of the nasty Microsoft madness otherwise known as the BOM.

    -
    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
    +
    BOM = 65279
    @@ -1733,12 +1691,45 @@ exports.STRICT_PROSCRIBED = STRICT_PROSCRIBED
    -

    The character code of the nasty Microsoft madness otherwise known as the BOM. -

    +

    Token matching regexes.

    -
    BOM = 65279
    +
    IDENTIFIER = /// ^
    +  ( [$A-Za-z_\x7f-\uffff][$\w\x7f-\uffff]* )
    +  ( [^\n\S]* : (?!:) )?  # Is this a property name?
    +///
    +
    +NUMBER     = ///
    +  ^ 0b[01]+    |              # binary
    +  ^ 0o[0-7]+   |              # octal
    +  ^ 0x[\da-f]+ |              # hex
    +  ^ \d*\.?\d+ (?:e[+-]?\d+)?  # decimal
    +///i
    +
    +HEREDOC    = /// ^ ("""|''') ((?: \\[\s\S] | [^\\] )*?) (?:\n[^\n\S]*)? \1 ///
    +
    +OPERATOR   = /// ^ (
    +  ?: [-=]>             # function
    +   | [-+*/%<>&|^!?=]=  # compound assign / compare
    +   | >>>=?             # zero-fill right shift
    +   | ([-+:])\1         # doubles
    +   | ([&|<>*/%])\2=?   # logic / shift / power / floor division / modulo
    +   | \?(\.|::)         # soak access
    +   | \.{2,3}           # range or splat
    +) ///
    +
    +WHITESPACE = /^[^\n\S]+/
    +
    +COMMENT    = /^###([^#][\s\S]*?)(?:###[^\n\S]*|###$)|^(?:\s*#(?!##[^#]).*)+/
    +
    +CODE       = /^[-=]>/
    +
    +MULTI_DENT = /^(?:\n[^\n\S]*)+/
    +
    +SIMPLESTR  = /^'[^\\']*(?:\\[\s\S][^\\']*)*'/
    +
    +JSTOKEN    = /^`[^\\`]*(?:\\.[^\\`]*)*`/
    @@ -1749,46 +1740,31 @@ exports.STRICT_PROSCRIBED = STRICT_PROSCRIBED
    -

    Token matching regexes. -

    +

    Regex-matching-regexes.

    -
    IDENTIFIER = /// ^
    -  ( [$A-Za-z_\x7f-\uffff][$\w\x7f-\uffff]* )
    -  ( [^\n\S]* : (?!:) )?  # Is this a property name?
    +            
    REGEX = /// ^
    +  (/ (?! [\s=] )   # disallow leading whitespace or equals signs
    +  [^ [ / \n \\ ]*  # every other thing
    +  (?:
    +    (?: \\[\s\S]   # anything escaped
    +      | \[         # character class
    +           [^ \] \n \\ ]*
    +           (?: \\[\s\S] [^ \] \n \\ ]* )*
    +         ]
    +    ) [^ [ / \n \\ ]*
    +  )*
    +  /) ([imgy]{0,4}) (?!\w)
     ///
     
    -NUMBER     = ///
    -  ^ 0b[01]+    |              # binary
    -  ^ 0o[0-7]+   |              # octal
    -  ^ 0x[\da-f]+ |              # hex
    -  ^ \d*\.?\d+ (?:e[+-]?\d+)?  # decimal
    -///i
    +HEREGEX      = /// ^ /{3} ((?:\\?[\s\S])+?) /{3} ([imgy]{0,4}) (?!\w) ///
     
    -HEREDOC    = /// ^ ("""|''') ([\s\S]*?) (?:\n[^\n\S]*)? \1 ///
    -
    -OPERATOR   = /// ^ (
    -  ?: [-=]>             # function
    -   | [-+*/%<>&|^!?=]=  # compound assign / compare
    -   | >>>=?             # zero-fill right shift
    -   | ([-+:])\1         # doubles
    -   | ([&|<>])\2=?      # logic / shift
    -   | \?(\.|::)         # soak access
    -   | \.{2,3}           # range or splat
    -) ///
    -
    -WHITESPACE = /^[^\n\S]+/
    -
    -COMMENT    = /^###([^#][\s\S]*?)(?:###[^\n\S]*|(?:###)$)|^(?:\s*#(?!##[^#]).*)+/
    -
    -CODE       = /^[-=]>/
    -
    -MULTI_DENT = /^(?:\n[^\n\S]*)+/
    -
    -SIMPLESTR  = /^'[^\\']*(?:\\.[^\\']*)*'/
    -
    -JSTOKEN    = /^`[^\\`]*(?:\\.[^\\`]*)*`/
    +HEREGEX_OMIT = /// + ((?:\\\\)+) # consume (and preserve) an even number of backslashes + | \\(\s|/) # preserve escaped whitespace and "de-escape" slashes + | \s+(?:#.*)? # remove whitespace and comments +///g
    @@ -1799,28 +1775,19 @@ JSTOKEN = /^`[^\\`]*(?:\\.[^\\`]*)*`/
    -

    Regex-matching-regexes. -

    +

    Token cleaning regexes.

    -
    REGEX = /// ^
    -  (/ (?! [\s=] )   # disallow leading whitespace or equals signs
    -  [^ [ / \n \\ ]*  # every other thing
    -  (?:
    -    (?: \\[\s\S]   # anything escaped
    -      | \[         # character class
    -           [^ \] \n \\ ]*
    -           (?: \\[\s\S] [^ \] \n \\ ]* )*
    -         ]
    -    ) [^ [ / \n \\ ]*
    -  )*
    -  /) ([imgy]{0,4}) (?!\w)
    -///
    +            
    MULTILINER      = /\n/g
     
    -HEREGEX      = /// ^ /{3} ([\s\S]+?) /{3} ([imgy]{0,4}) (?!\w) ///
    +HEREDOC_INDENT  = /\n+([^\n\S]*)/g
     
    -HEREGEX_OMIT = /\s+(?:#.*)?/g
    +HEREDOC_ILLEGAL = /\*\// + +LINE_CONTINUER = /// ^ \s* (?: , | \??\.(?![.\d]) | :: ) /// + +TRAILING_SPACES = /\s+$/
    @@ -1831,20 +1798,14 @@ HEREGEX_OMIT = /\s+(?:#.*)?/g
    -

    Token cleaning regexes. -

    +

    Compound assignment tokens.

    -
    MULTILINER      = /\n/g
    -
    -HEREDOC_INDENT  = /\n+([^\n\S]*)/g
    -
    -HEREDOC_ILLEGAL = /\*\//
    -
    -LINE_CONTINUER  = /// ^ \s* (?: , | \??\.(?![.\d]) | :: ) ///
    -
    -TRAILING_SPACES = /\s+$/
    +
    COMPOUND_ASSIGN = [
    +  '-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?=', '<<=', '>>=', '>>>='
    +  '&=', '^=', '|=', '**=', '//=', '%%='
    +]
    @@ -1855,14 +1816,13 @@ TRAILING_SPACES = /\s+$/
    -

    Compound assignment tokens. -

    +

    Unary tokens.

    -
    COMPOUND_ASSIGN = [
    -  '-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?=', '<<=', '>>=', '>>>=', '&=', '^=', '|='
    -]
    +
    UNARY = ['NEW', 'TYPEOF', 'DELETE', 'DO']
    +
    +UNARY_MATH = ['!', '~']
    @@ -1873,12 +1833,11 @@ TRAILING_SPACES = /\s+$/
    -

    Unary tokens. -

    +

    Logical tokens.

    -
    UNARY   = ['!', '~', 'NEW', 'TYPEOF', 'DELETE', 'DO']
    +
    LOGIC = ['&&', '||', '&', '|', '^']
    @@ -1889,12 +1848,11 @@ TRAILING_SPACES = /\s+$/
    -

    Logical tokens. -

    +

    Bit-shifting tokens.

    -
    LOGIC   = ['&&', '||', '&', '|', '^']
    +
    SHIFT = ['<<', '>>', '>>>']
    @@ -1905,12 +1863,11 @@ TRAILING_SPACES = /\s+$/
    -

    Bit-shifting tokens. -

    +

    Comparison tokens.

    -
    SHIFT   = ['<<', '>>', '>>>']
    +
    COMPARE = ['==', '!=', '<', '>', '<=', '>=']
    @@ -1921,12 +1878,11 @@ TRAILING_SPACES = /\s+$/
    -

    Comparison tokens. -

    +

    Mathematical tokens.

    -
    COMPARE = ['==', '!=', '<', '>', '<=', '>=']
    +
    MATH = ['*', '/', '%', '//', '%%']
    @@ -1937,12 +1893,11 @@ TRAILING_SPACES = /\s+$/
    -

    Mathematical tokens. -

    +

    Relational tokens that are negatable with not prefix.

    -
    MATH    = ['*', '/', '%']
    +
    RELATION = ['IN', 'OF', 'INSTANCEOF']
    @@ -1953,12 +1908,11 @@ TRAILING_SPACES = /\s+$/
    -

    Relational tokens that are negatable with not prefix. -

    +

    Boolean tokens.

    -
    RELATION = ['IN', 'OF', 'INSTANCEOF']
    +
    BOOL = ['TRUE', 'FALSE']
    @@ -1969,12 +1923,14 @@ TRAILING_SPACES = /\s+$/
    -

    Boolean tokens. -

    +

    Tokens which a regular expression will never immediately follow, but which +a division operator might.

    +

    See: http://www.mozilla.org/js/language/js20-2002-04/rationale/syntax.html#regular-expressions

    +

    Our list is shorter, due to sans-parentheses method calls.

    -
    BOOL = ['TRUE', 'FALSE']
    +
    NOT_REGEX = ['NUMBER', 'REGEX', 'BOOL', 'NULL', 'UNDEFINED', '++', '--']
    @@ -1985,19 +1941,12 @@ TRAILING_SPACES = /\s+$/
    -

    Tokens which a regular expression will never immediately follow, but which -a division operator might. - -

    -

    See: http://www.mozilla.org/js/language/js20-2002-04/rationale/syntax.html#regular-expressions - -

    -

    Our list is shorter, due to sans-parentheses method calls. -

    +

    If the previous token is not spaced, there are more preceding tokens that +force a division parse:

    -
    NOT_REGEX = ['NUMBER', 'REGEX', 'BOOL', 'NULL', 'UNDEFINED', '++', '--']
    +
    NOT_SPACED_REGEX = NOT_REGEX.concat ')', '}', 'THIS', 'IDENTIFIER', 'STRING', ']'
    @@ -2008,13 +1957,14 @@ a division operator might.
    -

    If the previous token is not spaced, there are more preceding tokens that -force a division parse: -

    +

    Tokens which could legitimately be invoked or indexed. An opening +parentheses or bracket following these tokens will be recorded as the start +of a function invocation or indexing operation.

    -
    NOT_SPACED_REGEX = NOT_REGEX.concat ')', '}', 'THIS', 'IDENTIFIER', 'STRING', ']'
    +
    CALLABLE  = ['IDENTIFIER', 'STRING', 'REGEX', ')', ']', '}', '?', '::', '@', 'THIS', 'SUPER']
    +INDEXABLE = CALLABLE.concat 'NUMBER', 'BOOL', 'NULL', 'UNDEFINED'
    @@ -2025,15 +1975,13 @@ force a division parse:
    -

    Tokens which could legitimately be invoked or indexed. An opening -parentheses or bracket following these tokens will be recorded as the start -of a function invocation or indexing operation. -

    +

    Tokens that, when immediately preceding a WHEN, indicate that the WHEN +occurs at the start of a line. We disambiguate these from trailing whens to +avoid an ambiguity in the grammar.

    -
    CALLABLE  = ['IDENTIFIER', 'STRING', 'REGEX', ')', ']', '}', '?', '::', '@', 'THIS', 'SUPER']
    -INDEXABLE = CALLABLE.concat 'NUMBER', 'BOOL', 'NULL', 'UNDEFINED'
    +
    LINE_BREAK = ['INDENT', 'OUTDENT', 'TERMINATOR']
    @@ -2044,14 +1992,11 @@ INDEXABLE = CALLABLE.concat 'NUMBER', -

    Tokens that, when immediately preceding a WHEN, indicate that the WHEN -occurs at the start of a line. We disambiguate these from trailing whens to -avoid an ambiguity in the grammar. -

    +

    Additional indent in front of these is ignored.

    -
    LINE_BREAK = ['INDENT', 'OUTDENT', 'TERMINATOR']
    +
    INDENTABLE_CLOSERS = [')', '}', ']']
    diff --git a/documentation/docs/nodes.html b/documentation/docs/nodes.html index 14e83767..029f34cd 100644 --- a/documentation/docs/nodes.html +++ b/documentation/docs/nodes.html @@ -69,6 +69,11 @@ + + register.coffee + + + repl.coffee @@ -111,16 +116,14 @@

    nodes.coffee contains all of the node classes for the syntax tree. Most 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 compile() on the root. -

    +the syntax tree into a string of JavaScript code, call compile() on the root.

    -
    -Error.stackTraceLimit = Infinity
    +            
    Error.stackTraceLimit = Infinity
     
    -{Scope} = require './scope'
    -{RESERVED, STRICT_PROSCRIBED} = require './lexer'
    +{Scope} = require './scope' +{RESERVED, STRICT_PROSCRIBED} = require './lexer'
    @@ -131,13 +134,12 @@ Error.stackTraceLimit = Infinity
    -

    Import the helpers we plan to use. -

    +

    Import the helpers we plan to use.

    {compact, flatten, extend, merge, del, starts, ends, last, some,
    -addLocationDataFn, locationDataToString, throwSyntaxError} = require './helpers'
    +addLocationDataFn, locationDataToString, throwSyntaxError} = require './helpers' @@ -148,13 +150,12 @@ addLocationDataFn, locationDataToString, throwSyntaxError} = require -

    Functions required by parser -

    +

    Functions required by parser

    -
    exports.extend = extend
    -exports.addLocationDataFn = addLocationDataFn
    +
    exports.extend = extend
    +exports.addLocationDataFn = addLocationDataFn
    @@ -165,15 +166,14 @@ exports.addLocationDataFn = addLocationDataFn
    -

    Constant functions for nodes that don't need customization. -

    +

    Constant functions for nodes that don't need customization.

    -
    YES     = -> yes
    -NO      = -> no
    -THIS    = -> this
    -NEGATE  = -> @negated = not @negated; this
    +
    YES     = -> yes
    +NO      = -> no
    +THIS    = -> this
    +NEGATE  = -> @negated = not @negated; this
    @@ -181,13 +181,26 @@ exports.addLocationDataFn = addLocationDataFn
  • -
    +
    -

    CodeFragment

    +

    CodeFragment

    +

    The various nodes defined below all compile to a collection of CodeFragment objects. +A CodeFragments is a block of generated code, and the location in the source file where the code +came from. CodeFragments can be assembled together into working code just by catting together +all the CodeFragments' code snippets, in order.

    +
    exports.CodeFragment = class CodeFragment
    +  constructor: (parent, code) ->
    +    @code = "#{code}"
    +    @locationData = parent?.locationData
    +    @type = parent?.constructor?.name or 'unknown'
    +
    +  toString:   ->
    +    "#{@code}#{if @locationData then ": " + locationDataToString(@locationData) else ''}"
    +
  • @@ -197,22 +210,12 @@ exports.addLocationDataFn = addLocationDataFn
    -

    The various nodes defined below all compile to a collection of CodeFragment objects. -A CodeFragments is a block of generated code, and the location in the source file where the code -came from. CodeFragments can be assembled together into working code just by catting together -all the CodeFragments' code snippets, in order. -

    +

    Convert an array of CodeFragments into a string.

    -
    exports.CodeFragment = class CodeFragment
    -  constructor: (parent, code) ->
    -    @code = "#{code}"
    -    @locationData = parent?.locationData
    -    @type = parent?.constructor?.name or 'unknown'
    -
    -  toString:   ->
    -    "#{@code}#{if @locationData then ": " + locationDataToString(@locationData) else ''}"
    +
    fragmentsToText = (fragments) ->
    +  (fragment.code for fragment in fragments).join('')
    @@ -223,13 +226,23 @@ all the CodeFragments' code snippets, in order.
    -

    Convert an array of CodeFragments into a string. -

    +

    Base

    +

    The Base is the abstract base class for all nodes in the syntax tree. +Each subclass implements the compileNode method, which performs the +code generation for that node. To compile a node to JavaScript, +call compile on it, which wraps compileNode in some generic extra smarts, +to know when the generated code needs to be wrapped up in a closure. +An options hash is passed and cloned throughout, containing information about +the environment from higher in the tree (such as if a returned value is +being requested by the surrounding function), information about the current +scope, and indentation level.

    -
    fragmentsToText = (fragments) ->
    -  (fragment.code for fragment in fragments).join('')
    +
    exports.Base = class Base
    +
    +  compile: (o, lvl) ->
    +    fragmentsToText @compileToFragments o, lvl
    @@ -237,13 +250,28 @@ all the CodeFragments' code snippets, in order.
  • -
    +
    -

    Base

    +

    Common logic for determining whether to wrap this node in a closure before +compiling it, or to compile directly. We need to wrap if this node is a +statement, and it's not a pureStatement, and we're not at +the top level of a block (which would be unnecessary), and we haven't +already been asked to return the result (because statements know how to +return results).

    +
      compileToFragments: (o, lvl) ->
    +    o        = extend {}, o
    +    o.level  = lvl if lvl
    +    node     = @unfoldSoak(o) or this
    +    node.tab = o.indent
    +    if o.level is LEVEL_TOP or not node.isStatement(o)
    +      node.compileNode o
    +    else
    +      node.compileClosure o
    +
  • @@ -253,23 +281,26 @@ all the CodeFragments' code snippets, in order.
    -

    The Base is the abstract base class for all nodes in the syntax tree. -Each subclass implements the compileNode method, which performs the -code generation for that node. To compile a node to JavaScript, -call compile on it, which wraps compileNode in some generic extra smarts, -to know when the generated code needs to be wrapped up in a closure. -An options hash is passed and cloned throughout, containing information about -the environment from higher in the tree (such as if a returned value is -being requested by the surrounding function), information about the current -scope, and indentation level. -

    +

    Statements converted into expressions via closure-wrapping share a scope +object with their parent closure, to preserve the expected lexical scope.

    -
    exports.Base = class Base
    -
    -  compile: (o, lvl) ->
    -    fragmentsToText @compileToFragments o, lvl
    +
      compileClosure: (o) ->
    +    if jumpNode = @jumps()
    +      jumpNode.error 'cannot use a pure statement in an expression'
    +    o.sharedScope = yes
    +    func = new Code [], Block.wrap [this]
    +    args = []
    +    if (argumentsNode = @contains isLiteralArguments) or @contains isLiteralThis
    +      args = [new Literal 'this']
    +      if argumentsNode
    +        meth = 'apply'
    +        args.push new Literal 'arguments'
    +      else
    +        meth = 'call'
    +      func = new Value func, [new Access new Literal meth]
    +    (new Call func, args).compileNode o
    @@ -280,25 +311,26 @@ scope, and indentation level.
    -

    Common logic for determining whether to wrap this node in a closure before -compiling it, or to compile directly. We need to wrap if this node is a -statement, and it's not a pureStatement, and we're not at -the top level of a block (which would be unnecessary), and we haven't -already been asked to return the result (because statements know how to -return results). -

    +

    If the code generation wishes to use the result of a complex expression +in multiple places, ensure that the expression is only ever evaluated once, +by assigning it to a temporary variable. Pass a level to precompile.

    +

    If level is passed, then returns [val, ref], where val is the compiled value, and ref +is the compiled reference. If level is not passed, this returns [val, ref] where +the two values are raw nodes which have not been compiled.

    -
      compileToFragments: (o, lvl) ->
    -    o        = extend {}, o
    -    o.level  = lvl if lvl
    -    node     = @unfoldSoak(o) or this
    -    node.tab = o.indent
    -    if o.level is LEVEL_TOP or not node.isStatement(o)
    -      node.compileNode o
    -    else
    -      node.compileClosure o
    +
      cache: (o, level, reused) ->
    +    unless @isComplex()
    +      ref = if level then @compileToFragments o, level else this
    +      [ref, ref]
    +    else
    +      ref = new Literal reused or o.scope.freeVariable 'ref'
    +      sub = new Assign ref, this
    +      if level then [sub.compileToFragments(o, level), [@makeCode(ref.value)]] else [sub, ref]
    +
    +  cacheToCodeFragments: (cacheValues) ->
    +    [fragmentsToText(cacheValues[0]), fragmentsToText(cacheValues[1])]
    @@ -309,17 +341,18 @@ return results).
    -

    Statements converted into expressions via closure-wrapping share a scope -object with their parent closure, to preserve the expected lexical scope. -

    +

    Construct a node that returns the current node's result. +Note that this is overridden for smarter behavior for +many statement nodes (e.g. If, For)...

    -
      compileClosure: (o) ->
    -    if jumpNode = @jumps()
    -      jumpNode.error 'cannot use a pure statement in an expression'
    -    o.sharedScope = yes
    -    Closure.wrap(this).compileNode o
    +
      makeReturn: (res) ->
    +    me = @unwrapAll()
    +    if res
    +      new Call new Literal("#{res}.push"), [me]
    +    else
    +      new Return me
    @@ -330,29 +363,20 @@ object with their parent closure, to preserve the expected lexical scope.
    -

    If the code generation wishes to use the result of a complex expression -in multiple places, ensure that the expression is only ever evaluated once, -by assigning it to a temporary variable. Pass a level to precompile. - -

    -

    If level is passed, then returns [val, ref], where val is the compiled value, and ref -is the compiled reference. If level is not passed, this returns [val, ref] where -the two values are raw nodes which have not been compiled. -

    +

    Does this node, or any of its children, contain a node of a certain kind? +Recursively traverses down the children nodes and returns the first one +that verifies pred. Otherwise return undefined. contains does not cross +scope boundaries.

    -
      cache: (o, level, reused) ->
    -    unless @isComplex()
    -      ref = if level then @compileToFragments o, level else this
    -      [ref, ref]
    -    else
    -      ref = new Literal reused or o.scope.freeVariable 'ref'
    -      sub = new Assign ref, this
    -      if level then [sub.compileToFragments(o, level), [@makeCode(ref.value)]] else [sub, ref]
    -
    -  cacheToCodeFragments: (cacheValues) ->
    -    [fragmentsToText(cacheValues[0]), fragmentsToText(cacheValues[1])]
    +
      contains: (pred) ->
    +    node = undefined
    +    @traverseChildren no, (n) ->
    +      if pred n
    +        node = n
    +        return no
    +    node
    @@ -363,19 +387,14 @@ the two values are raw nodes which have not been compiled.
    -

    Construct a node that returns the current node's result. -Note that this is overridden for smarter behavior for -many statement nodes (e.g. If, For)... -

    +

    Pull out the last non-comment node of a node list.

    -
      makeReturn: (res) ->
    -    me = @unwrapAll()
    -    if res
    -      new Call new Literal("#{res}.push"), [me]
    -    else
    -      new Return me
    +
      lastNonComment: (list) ->
    +    i = list.length
    +    return list[i] while i-- when list[i] not instanceof Comment
    +    null
    @@ -386,21 +405,16 @@ many statement nodes (e.g. If, For)...
    -

    Does this node, or any of its children, contain a node of a certain kind? -Recursively traverses down the children nodes and returns the first one -that verifies pred. Otherwise return undefined. contains does not cross -scope boundaries. -

    +

    toString representation of the node, for inspecting the parse tree. +This is what coffee --nodes prints out.

    -
      contains: (pred) ->
    -    node = undefined
    -    @traverseChildren no, (n) ->
    -      if pred n
    -        node = n
    -        return no
    -    node
    +
      toString: (idt = '', name = @constructor.name) ->
    +    tree = '\n' + idt + name
    +    tree += '?' if @soak
    +    @eachChild (node) -> tree += node.toString idt + TAB
    +    tree
    @@ -411,15 +425,29 @@ scope boundaries.
    -

    Pull out the last non-comment node of a node list. -

    +

    Passes each child to a function, breaking when the function returns false.

    -
      lastNonComment: (list) ->
    -    i = list.length
    -    return list[i] while i-- when list[i] not instanceof Comment
    -    null
    +
      eachChild: (func) ->
    +    return this unless @children
    +    for attr in @children when @[attr]
    +      for child in flatten [@[attr]]
    +        return this if func(child) is false
    +    this
    +
    +  traverseChildren: (crossScope, func) ->
    +    @eachChild (child) ->
    +      recur = func(child)
    +      child.traverseChildren(crossScope, func) unless recur is no
    +
    +  invert: ->
    +    new Op '!', this
    +
    +  unwrapAll: ->
    +    node = this
    +    continue until node is node = node.unwrap()
    +    node
    @@ -430,68 +458,12 @@ scope boundaries.
    -

    toString representation of the node, for inspecting the parse tree. -This is what coffee --nodes prints out. -

    - - - -
      toString: (idt = '', name = @constructor.name) ->
    -    tree = '\n' + idt + name
    -    tree += '?' if @soak
    -    @eachChild (node) -> tree += node.toString idt + TAB
    -    tree
    - - - - -
  • -
    - -
    - -
    -

    Passes each child to a function, breaking when the function returns false. -

    - -
    - -
      eachChild: (func) ->
    -    return this unless @children
    -    for attr in @children when @[attr]
    -      for child in flatten [@[attr]]
    -        return this if func(child) is false
    -    this
    -
    -  traverseChildren: (crossScope, func) ->
    -    @eachChild (child) ->
    -      recur = func(child)
    -      child.traverseChildren(crossScope, func) unless recur is no
    -
    -  invert: ->
    -    new Op '!', this
    -
    -  unwrapAll: ->
    -    node = this
    -    continue until node is node = node.unwrap()
    -    node
    - -
  • - - -
  • -
    - -
    - -

    Default implementations of the common node properties and methods. Nodes -will override these with custom logic, if needed. -

    +will override these with custom logic, if needed.

    -
      children: []
    +            
      children: []
     
       isStatement     : NO
       jumps           : NO
    @@ -505,18 +477,60 @@ will override these with custom logic, if needed.
             
  • +
  • +
    + +
    + +
    +

    Is this node used to assign a certain variable?

    + +
    + +
      assigns: NO
    + +
  • + + +
  • +
    + +
    + +
    +

    For this node and all descendents, set the location data to locationData +if the location data is not already set.

    + +
    + +
      updateLocationDataIfMissing: (locationData) ->
    +    return this if @locationData
    +    @locationData = locationData
    +
    +    @eachChild (child) ->
    +      child.updateLocationDataIfMissing locationData
    + +
  • + +
  • -

    Is this node used to assign a certain variable? -

    +

    Throw a SyntaxError associated with this node's location.

    -
      assigns: NO
    +
      error: (message) ->
    +    throwSyntaxError message, @locationData
    +
    +  makeCode: (code) ->
    +    new CodeFragment this, code
    +
    +  wrapInBraces: (fragments) ->
    +    [].concat @makeCode('('), fragments, @makeCode(')')
  • @@ -527,17 +541,18 @@ will override these with custom logic, if needed.
    -

    For this node and all descendents, set the location data to locationData -if the location data is not already set. -

    +

    fragmentsList is an array of arrays of fragments. Each array in fragmentsList will be +concatonated together, with joinStr added in between each, to produce a final flat array +of fragments.

    -
      updateLocationDataIfMissing: (locationData) ->
    -    @locationData or= locationData
    -
    -    @eachChild (child) ->
    -      child.updateLocationDataIfMissing locationData
    +
      joinFragmentArrays: (fragmentsList, joinStr) ->
    +    answer = []
    +    for fragments,i in fragmentsList
    +      if i then answer.push @makeCode joinStr
    +      answer = answer.concat fragments
    +    answer
    @@ -548,19 +563,18 @@ if the location data is not already set.
    -

    Throw a SyntaxError associated with this node's location. -

    +

    Block

    +

    The block is the list of expressions that forms the body of an +indented block of code -- the implementation of a function, a clause in an +if, switch, or try, and so on...

    -
      error: (message) ->
    -    throwSyntaxError message, @locationData
    +            
    exports.Block = class Block extends Base
    +  constructor: (nodes) ->
    +    @expressions = compact flatten nodes or []
     
    -  makeCode: (code) ->
    -    new CodeFragment this, code
    -
    -  wrapInBraces: (fragments) ->
    -    [].concat @makeCode('('), fragments, @makeCode(')')
    + children: ['expressions']
    @@ -571,19 +585,13 @@ if the location data is not already set.
    -

    fragmentsList is an array of arrays of fragments. Each array in fragmentsList will be -concatonated together, with joinStr added in between each, to produce a final flat array -of fragments. -

    +

    Tack an expression on to the end of this expression list.

    -
      joinFragmentArrays: (fragmentsList, joinStr) ->
    -    answer = []
    -    for fragments,i in fragmentsList
    -      if i then answer.push @makeCode joinStr
    -      answer = answer.concat fragments
    -    answer
    +
      push: (node) ->
    +    @expressions.push node
    +    this
    @@ -591,13 +599,16 @@ of fragments.
  • -
    +
    -

    Block

    +

    Remove and return the last expression of this expression list.

    +
      pop: ->
    +    @expressions.pop()
    +
  • @@ -607,18 +618,13 @@ of fragments.
    -

    The block is the list of expressions that forms the body of an -indented block of code -- the implementation of a function, a clause in an -if, switch, or try, and so on... -

    +

    Add an expression at the beginning of this expression list.

    -
    exports.Block = class Block extends Base
    -  constructor: (nodes) ->
    -    @expressions = compact flatten nodes or []
    -
    -  children: ['expressions']
    +
      unshift: (node) ->
    +    @expressions.unshift node
    +    this
    @@ -629,14 +635,13 @@ indented block of code -- the implementation of a function, a clause in an
    -

    Tack an expression on to the end of this expression list. -

    +

    If this Block consists of just a single node, unwrap it by pulling +it back out.

    -
      push: (node) ->
    -    @expressions.push node
    -    this
    +
      unwrap: ->
    +    if @expressions.length is 1 then @expressions[0] else this
    @@ -647,13 +652,21 @@ indented block of code -- the implementation of a function, a clause in an
    -

    Remove and return the last expression of this expression list. -

    +

    Is this an empty block of code?

    -
      pop: ->
    -    @expressions.pop()
    +
      isEmpty: ->
    +    not @expressions.length
    +
    +  isStatement: (o) ->
    +    for exp in @expressions when exp.isStatement o
    +      return yes
    +    no
    +
    +  jumps: (o) ->
    +    for exp in @expressions
    +      return jumpNode if jumpNode = exp.jumps o
    @@ -664,14 +677,20 @@ indented block of code -- the implementation of a function, a clause in an
    -

    Add an expression at the beginning of this expression list. -

    +

    A Block node does not return its entire body, rather it +ensures that the final expression is returned.

    -
      unshift: (node) ->
    -    @expressions.unshift node
    -    this
    +
      makeReturn: (res) ->
    +    len = @expressions.length
    +    while len--
    +      expr = @expressions[len]
    +      if expr not instanceof Comment
    +        @expressions[len] = expr.makeReturn res
    +        @expressions.splice(len, 1) if expr instanceof Return and not expr.expression
    +        break
    +    this
    @@ -682,14 +701,12 @@ indented block of code -- the implementation of a function, a clause in an
    -

    If this Block consists of just a single node, unwrap it by pulling -it back out. -

    +

    A Block is the only node that can serve as the root.

    -
      unwrap: ->
    -    if @expressions.length is 1 then @expressions[0] else this
    +
      compileToFragments: (o = {}, level) ->
    +    if o.scope then super o, level else @compileRoot o
    @@ -700,22 +717,22 @@ it back out.
    -

    Is this an empty block of code? -

    +

    Compile all expressions within the Block body. If we need to +return the result, and it's an expression, simply return it. If it's a +statement, ask the statement to do so.

    -
      isEmpty: ->
    -    not @expressions.length
    +            
      compileNode: (o) ->
    +    @tab  = o.indent
    +    top   = o.level is LEVEL_TOP
    +    compiledNodes = []
     
    -  isStatement: (o) ->
    -    for exp in @expressions when exp.isStatement o
    -      return yes
    -    no
    +    for node, index in @expressions
     
    -  jumps: (o) ->
    -    for exp in @expressions
    -      return exp if exp.jumps o
    + node = node.unwrapAll() + node = (node.unfoldSoak(o) or node) + if node instanceof Block
    @@ -726,21 +743,32 @@ it back out.
    -

    A Block node does not return its entire body, rather it -ensures that the final expression is returned. -

    +

    This is a nested block. We don't do anything special here like enclose +it in a new scope; we just compile the statements in this block along with +our own

    -
      makeReturn: (res) ->
    -    len = @expressions.length
    -    while len--
    -      expr = @expressions[len]
    -      if expr not instanceof Comment
    -        @expressions[len] = expr.makeReturn res
    -        @expressions.splice(len, 1) if expr instanceof Return and not expr.expression
    -        break
    -    this
    +
            compiledNodes.push node.compileNode o
    +      else if top
    +        node.front = true
    +        fragments = node.compileToFragments o
    +        unless node.isStatement o
    +          fragments.unshift @makeCode "#{@tab}"
    +          fragments.push @makeCode ";"
    +        compiledNodes.push fragments
    +      else
    +        compiledNodes.push node.compileToFragments o, LEVEL_LIST
    +    if top
    +      if @spaced
    +        return [].concat @joinFragmentArrays(compiledNodes, '\n\n'), @makeCode("\n")
    +      else
    +        return @joinFragmentArrays(compiledNodes, '\n')
    +    if compiledNodes.length
    +      answer = @joinFragmentArrays(compiledNodes, ', ')
    +    else
    +      answer = [@makeCode "void 0"]
    +    if compiledNodes.length > 1 and o.level >= LEVEL_LIST then @wrapInBraces answer else answer
    @@ -751,13 +779,18 @@ ensures that the final expression is returned.
    -

    A Block is the only node that can serve as the root. -

    +

    If we happen to be the top-level Block, wrap everything in +a safety closure, unless requested not to. +It would be better not to generate them in the first place, but for now, +clean up obvious double-parentheses.

    -
      compileToFragments: (o = {}, level) ->
    -    if o.scope then super o, level else @compileRoot o
    +
      compileRoot: (o) ->
    +    o.indent  = if o.bare then '' else TAB
    +    o.level   = LEVEL_TOP
    +    @spaced   = yes
    +    o.scope   = new Scope null, this, null
    @@ -768,23 +801,26 @@ ensures that the final expression is returned.
    -

    Compile all expressions within the Block body. If we need to -return the result, and it's an expression, simply return it. If it's a -statement, ask the statement to do so. -

    +

    Mark given local variables in the root scope as parameters so they don't +end up being declared on this block.

    -
      compileNode: (o) ->
    -    @tab  = o.indent
    -    top   = o.level is LEVEL_TOP
    -    compiledNodes = []
    -
    -    for node, index in @expressions
    -
    -      node = node.unwrapAll()
    -      node = (node.unfoldSoak(o) or node)
    -      if node instanceof Block
    +
        o.scope.parameter name for name in o.locals or []
    +    prelude   = []
    +    unless o.bare
    +      preludeExps = for exp, i in @expressions
    +        break unless exp.unwrap() instanceof Comment
    +        exp
    +      rest = @expressions[preludeExps.length...]
    +      @expressions = preludeExps
    +      if preludeExps.length
    +        prelude = @compileNode merge(o, indent: '')
    +        prelude.push @makeCode "\n"
    +      @expressions = rest
    +    fragments = @compileWithDeclarations o
    +    return fragments if o.bare
    +    [].concat prelude, @makeCode("(function() {\n"), fragments, @makeCode("\n}).call(this);\n")
    @@ -795,33 +831,40 @@ statement, ask the statement to do so.
    -

    This is a nested block. We don't do anything special here like enclose -it in a new scope; we just compile the statements in this block along with -our own -

    +

    Compile the expressions body for the contents of a function, with +declarations of all inner variables pushed up to the top.

    -
            compiledNodes.push node.compileNode o
    -      else if top
    -        node.front = true
    -        fragments = node.compileToFragments o
    -        unless node.isStatement o
    -          fragments.unshift @makeCode "#{@tab}"
    -          fragments.push @makeCode ";"
    -        compiledNodes.push fragments
    -      else
    -        compiledNodes.push node.compileToFragments o, LEVEL_LIST
    -    if top
    -      if @spaced
    -        return [].concat @joinFragmentArrays(compiledNodes, '\n\n'), @makeCode("\n")
    -      else
    -        return @joinFragmentArrays(compiledNodes, '\n')
    -    if compiledNodes.length
    -      answer = @joinFragmentArrays(compiledNodes, ', ')
    -    else
    -      answer = [@makeCode "void 0"]
    -    if compiledNodes.length > 1 and o.level >= LEVEL_LIST then @wrapInBraces answer else answer
    +
      compileWithDeclarations: (o) ->
    +    fragments = []
    +    post = []
    +    for exp, i in @expressions
    +      exp = exp.unwrap()
    +      break unless exp instanceof Comment or exp instanceof Literal
    +    o = merge(o, level: LEVEL_TOP)
    +    if i
    +      rest = @expressions.splice i, 9e9
    +      [spaced,    @spaced] = [@spaced, no]
    +      [fragments, @spaced] = [@compileNode(o), spaced]
    +      @expressions = rest
    +    post = @compileNode o
    +    {scope} = o
    +    if scope.expressions is this
    +      declars = o.scope.hasDeclarations()
    +      assigns = scope.hasAssignments
    +      if declars or assigns
    +        fragments.push @makeCode '\n' if i
    +        fragments.push @makeCode "#{@tab}var "
    +        if declars
    +          fragments.push @makeCode scope.declaredVariables().join(', ')
    +        if assigns
    +          fragments.push @makeCode ",\n#{@tab + TAB}" if declars
    +          fragments.push @makeCode scope.assignedVariables().join(",\n#{@tab + TAB}")
    +        fragments.push @makeCode ";\n#{if @spaced then '\n' else ''}"
    +      else if fragments.length and post.length
    +        fragments.push @makeCode "\n"
    +    fragments.concat post
    @@ -832,19 +875,14 @@ our own
    -

    If we happen to be the top-level Block, wrap everything in -a safety closure, unless requested not to. -It would be better not to generate them in the first place, but for now, -clean up obvious double-parentheses. -

    +

    Wrap up the given nodes as a Block, unless it already happens +to be one.

    -
      compileRoot: (o) ->
    -    o.indent  = if o.bare then '' else TAB
    -    o.level   = LEVEL_TOP
    -    @spaced   = yes
    -    o.scope   = new Scope null, this, null
    +
      @wrap: (nodes) ->
    +    return nodes[0] if nodes.length is 1 and nodes[0] instanceof Block
    +    new Block nodes
    @@ -855,27 +893,63 @@ clean up obvious double-parentheses.
    -

    Mark given local variables in the root scope as parameters so they don't -end up being declared on this block. -

    +

    Literal

    +

    Literals are static values that can be passed through directly into +JavaScript without translation, such as: strings, numbers, +true, false, null...

    -
        o.scope.parameter name for name in o.locals or []
    -    prelude   = []
    -    unless o.bare
    -      preludeExps = for exp, i in @expressions
    -        break unless exp.unwrap() instanceof Comment
    -        exp
    -      rest = @expressions[preludeExps.length...]
    -      @expressions = preludeExps
    -      if preludeExps.length
    -        prelude = @compileNode merge(o, indent: '')
    -        prelude.push @makeCode "\n"
    -      @expressions = rest
    -    fragments = @compileWithDeclarations o
    -    return fragments if o.bare
    -    [].concat prelude, @makeCode("(function() {\n"), fragments, @makeCode("\n}).call(this);\n")
    +
    exports.Literal = class Literal extends Base
    +  constructor: (@value) ->
    +
    +  makeReturn: ->
    +    if @isStatement() then this else super
    +
    +  isAssignable: ->
    +    IDENTIFIER.test @value
    +
    +  isStatement: ->
    +    @value in ['break', 'continue', 'debugger']
    +
    +  isComplex: NO
    +
    +  assigns: (name) ->
    +    name is @value
    +
    +  jumps: (o) ->
    +    return this if @value is 'break' and not (o?.loop or o?.block)
    +    return this if @value is 'continue' and not o?.loop
    +
    +  compileNode: (o) ->
    +    code = if @value is 'this'
    +      if o.scope.method?.bound then o.scope.method.context else @value
    +    else if @value.reserved
    +      "\"#{@value}\""
    +    else
    +      @value
    +    answer = if @isStatement() then "#{@tab}#{code};" else code
    +    [@makeCode answer]
    +
    +  toString: ->
    +    ' "' + @value + '"'
    +
    +class exports.Undefined extends Base
    +  isAssignable: NO
    +  isComplex: NO
    +  compileNode: (o) ->
    +    [@makeCode if o.level >= LEVEL_ACCESS then '(void 0)' else 'void 0']
    +
    +class exports.Null extends Base
    +  isAssignable: NO
    +  isComplex: NO
    +  compileNode: -> [@makeCode "null"]
    +
    +class exports.Bool extends Base
    +  isAssignable: NO
    +  isComplex: NO
    +  compileNode: -> [@makeCode @val]
    +  constructor: (@val) ->
    @@ -886,41 +960,28 @@ end up being declared on this block.
    -

    Compile the expressions body for the contents of a function, with -declarations of all inner variables pushed up to the top. -

    +

    Return

    +

    A return is a pureStatement -- wrapping it in a closure wouldn't +make sense.

    -
      compileWithDeclarations: (o) ->
    -    fragments = []
    -    post = []
    -    for exp, i in @expressions
    -      exp = exp.unwrap()
    -      break unless exp instanceof Comment or exp instanceof Literal
    -    o = merge(o, level: LEVEL_TOP)
    -    if i
    -      rest = @expressions.splice i, 9e9
    -      [spaced,    @spaced] = [@spaced, no]
    -      [fragments, @spaced] = [@compileNode(o), spaced]
    -      @expressions = rest
    -    post = @compileNode o
    -    {scope} = o
    -    if scope.expressions is this
    -      declars = o.scope.hasDeclarations()
    -      assigns = scope.hasAssignments
    -      if declars or assigns
    -        fragments.push @makeCode '\n' if i
    -        fragments.push @makeCode "#{@tab}var "
    -        if declars
    -          fragments.push @makeCode scope.declaredVariables().join(', ')
    -        if assigns
    -          fragments.push @makeCode ",\n#{@tab + TAB}" if declars
    -          fragments.push @makeCode scope.assignedVariables().join(",\n#{@tab + TAB}")
    -        fragments.push @makeCode ";\n#{if @spaced then '\n' else ''}"
    -      else if fragments.length and post.length
    -        fragments.push @makeCode "\n"
    -    fragments.concat post
    +
    exports.Return = class Return extends Base
    +  constructor: (expr) ->
    +    @expression = expr if expr and not expr.unwrap().isUndefined
    +
    +  children: ['expression']
    +
    +  isStatement:     YES
    +  makeReturn:      THIS
    +  jumps:           THIS
    +
    +  compileToFragments: (o, level) ->
    +    expr = @expression?.makeReturn()
    +    if expr and expr not instanceof Return then expr.compileToFragments o, level else super o, level
    +
    +  compileNode: (o) ->
    +    answer = []
    @@ -931,15 +992,15 @@ declarations of all inner variables pushed up to the top.
    -

    Wrap up the given nodes as a Block, unless it already happens -to be one. -

    +

    TODO: If we call expression.compile() here twice, we'll sometimes get back different results!

    -
      @wrap: (nodes) ->
    -    return nodes[0] if nodes.length is 1 and nodes[0] instanceof Block
    -    new Block nodes
    +
        answer.push @makeCode @tab + "return#{if @expression then " " else ""}"
    +    if @expression
    +      answer = answer.concat @expression.compileToFragments o, LEVEL_PAREN
    +    answer.push @makeCode ";"
    +    return answer
    @@ -947,13 +1008,25 @@ to be one.
  • -
    +
    -

    Literal

    +

    Value

    +

    A value, variable or literal or parenthesized, indexed or dotted into, +or vanilla.

    +
    exports.Value = class Value extends Base
    +  constructor: (base, props, tag) ->
    +    return base if not props and base instanceof Value
    +    @base       = base
    +    @properties = props or []
    +    @[tag]      = true if tag
    +    return this
    +
    +  children: ['base', 'properties']
    +
  • @@ -963,63 +1036,19 @@ to be one.
    -

    Literals are static values that can be passed through directly into -JavaScript without translation, such as: strings, numbers, -true, false, null... -

    +

    Add a property (or properties ) Access to the list.

    -
    exports.Literal = class Literal extends Base
    -  constructor: (@value) ->
    +            
      add: (props) ->
    +    @properties = @properties.concat props
    +    this
     
    -  makeReturn: ->
    -    if @isStatement() then this else super
    +  hasProperties: ->
    +    !!@properties.length
     
    -  isAssignable: ->
    -    IDENTIFIER.test @value
    -
    -  isStatement: ->
    -    @value in ['break', 'continue', 'debugger']
    -
    -  isComplex: NO
    -
    -  assigns: (name) ->
    -    name is @value
    -
    -  jumps: (o) ->
    -    return this if @value is 'break' and not (o?.loop or o?.block)
    -    return this if @value is 'continue' and not o?.loop
    -
    -  compileNode: (o) ->
    -    code = if @value is 'this'
    -      if o.scope.method?.bound then o.scope.method.context else @value
    -    else if @value.reserved
    -      "\"#{@value}\""
    -    else
    -      @value
    -    answer = if @isStatement() then "#{@tab}#{code};" else code
    -    [@makeCode answer]
    -
    -  toString: ->
    -    ' "' + @value + '"'
    -
    -class exports.Undefined extends Base
    -  isAssignable: NO
    -  isComplex: NO
    -  compileNode: (o) ->
    -    [@makeCode if o.level >= LEVEL_ACCESS then '(void 0)' else 'void 0']
    -
    -class exports.Null extends Base
    -  isAssignable: NO
    -  isComplex: NO
    -  compileNode: -> [@makeCode "null"]
    -
    -class exports.Bool extends Base
    -  isAssignable: NO
    -  isComplex: NO
    -  compileNode: -> [@makeCode @val]
    -  constructor: (@val) ->
    + bareLiteral: (type) -> + not @properties.length and @base instanceof type
    @@ -1027,13 +1056,43 @@ JavaScript without translation, such as: strings, numbers,
  • -
    +
    -

    Return

    +

    Some boolean checks for the benefit of other nodes.

    +
      isArray        : -> @bareLiteral(Arr)
    +  isRange        : -> @bareLiteral(Range)
    +  isComplex      : -> @hasProperties() or @base.isComplex()
    +  isAssignable   : -> @hasProperties() or @base.isAssignable()
    +  isSimpleNumber : -> @bareLiteral(Literal) and SIMPLENUM.test @base.value
    +  isString       : -> @bareLiteral(Literal) and IS_STRING.test @base.value
    +  isRegex        : -> @bareLiteral(Literal) and IS_REGEX.test @base.value
    +  isAtomic       : ->
    +    for node in @properties.concat @base
    +      return no if node.soak or node instanceof Call
    +    yes
    +
    +  isNotCallable  : -> @isSimpleNumber() or @isString() or @isRegex() or
    +                      @isArray() or @isRange() or @isSplice() or @isObject()
    +
    +  isStatement : (o)    -> not @properties.length and @base.isStatement o
    +  assigns     : (name) -> not @properties.length and @base.assigns name
    +  jumps       : (o)    -> not @properties.length and @base.jumps o
    +
    +  isObject: (onlyGenerated) ->
    +    return no if @properties.length
    +    (@base instanceof Obj) and (not onlyGenerated or @base.generated)
    +
    +  isSplice: ->
    +    last(@properties) instanceof Slice
    +
    +  looksStatic: (className) ->
    +    @base.value is className and @properties.length and
    +      @properties[0].name?.value isnt 'prototype'
    +
  • @@ -1043,28 +1102,13 @@ JavaScript without translation, such as: strings, numbers,
    -

    A return is a pureStatement -- wrapping it in a closure wouldn't -make sense. -

    +

    The value can be unwrapped as its inner node, if there are no attached +properties.

    -
    exports.Return = class Return extends Base
    -  constructor: (expr) ->
    -    @expression = expr if expr and not expr.unwrap().isUndefined
    -
    -  children: ['expression']
    -
    -  isStatement:     YES
    -  makeReturn:      THIS
    -  jumps:           THIS
    -
    -  compileToFragments: (o, level) ->
    -    expr = @expression?.makeReturn()
    -    if expr and expr not instanceof Return then expr.compileToFragments o, level else super o, level
    -
    -  compileNode: (o) ->
    -    answer = []
    +
      unwrap: ->
    +    if @properties.length then this else @base
    @@ -1075,16 +1119,26 @@ make sense.
    -

    TODO: If we call expression.compile() here twice, we'll sometimes get back different results! -

    +

    A reference has base part (this value) and name part. +We cache them separately for compiling complex expressions. +a()[b()] ?= c -> (_base = a())[_name = b()] ? _base[_name] = c

    -
        answer.push @makeCode @tab + "return#{if @expression then " " else ""}"
    -    if @expression
    -      answer = answer.concat @expression.compileToFragments o, LEVEL_PAREN
    -    answer.push @makeCode ";"
    -    return answer
    +
      cacheReference: (o) ->
    +    name = last @properties
    +    if @properties.length < 2 and not @base.isComplex() and not name?.isComplex()
    +      return [this, this]  # `a` `a.b`
    +    base = new Value @base, @properties[...-1]
    +    if base.isComplex()  # `a().b`
    +      bref = new Literal o.scope.freeVariable 'base'
    +      base = new Value new Parens new Assign bref, base
    +    return [base, bref] unless name  # `a()`
    +    if name.isComplex()  # `a[b()]`
    +      nref = new Literal o.scope.freeVariable 'name'
    +      name = new Index new Assign nref, name.index
    +      nref = new Index nref
    +    [base.add(name), new Value(bref or base.base, [nref or name])]
    @@ -1092,13 +1146,26 @@ make sense.
  • -
    +
    -

    Value

    +

    We compile a value to JavaScript by compiling and joining each property. +Things get much more interesting if the chain of properties has soak +operators ?. interspersed. Then we have to take care not to accidentally +evaluate anything twice when building the soak chain.

    +
      compileNode: (o) ->
    +    @base.front = @front
    +    props = @properties
    +    fragments = @base.compileToFragments o, (if props.length then LEVEL_ACCESS else null)
    +    if (@base instanceof Parens or props.length) and SIMPLENUM.test fragmentsToText fragments
    +      fragments.push @makeCode '.'
    +    for prop in props
    +      fragments.push (prop.compileToFragments o)...
    +    fragments
    +
  • @@ -1108,21 +1175,25 @@ make sense.
    -

    A value, variable or literal or parenthesized, indexed or dotted into, -or vanilla. -

    +

    Unfold a soak into an If: a?.b -> a.b if a?

    -
    exports.Value = class Value extends Base
    -  constructor: (base, props, tag) ->
    -    return base if not props and base instanceof Value
    -    @base       = base
    -    @properties = props or []
    -    @[tag]      = true if tag
    -    return this
    -
    -  children: ['base', 'properties']
    +
      unfoldSoak: (o) ->
    +    @unfoldedSoak ?= do =>
    +      if ifn = @base.unfoldSoak o
    +        ifn.body.properties.push @properties...
    +        return ifn
    +      for prop, i in @properties when prop.soak
    +        prop.soak = off
    +        fst = new Value @base, @properties[...i]
    +        snd = new Value @base, @properties[i..]
    +        if fst.isComplex()
    +          ref = new Literal o.scope.freeVariable 'ref'
    +          fst = new Parens new Assign ref, fst
    +          snd.base = ref
    +        return new If new Existence(fst), snd, soak: on
    +      no
    @@ -1133,17 +1204,23 @@ or vanilla.
    -

    Add a property (or properties ) Access to the list. -

    +

    Comment

    +

    CoffeeScript passes through block comments as JavaScript block comments +at the same position.

    -
      add: (props) ->
    -    @properties = @properties.concat props
    -    this
    +            
    exports.Comment = class Comment extends Base
    +  constructor: (@comment) ->
     
    -  hasProperties: ->
    -    !!@properties.length
    + isStatement: YES + makeReturn: THIS + + compileNode: (o, level) -> + comment = @comment.replace /^(\s*)#/gm, "$1 *" + code = "/*#{multident comment, @tab}#{if '\n' in comment then "\n#{@tab}" else ''} */" + code = o.indent + code if (level or o.level) is LEVEL_TOP + [@makeCode("\n"), @makeCode(code)]
    @@ -1154,31 +1231,21 @@ or vanilla.
    -

    Some boolean checks for the benefit of other nodes. -

    +

    Call

    +

    Node for a function invocation. Takes care of converting super() calls into +calls against the prototype's function of the same name.

    -
      isArray        : -> not @properties.length and @base instanceof Arr
    -  isComplex      : -> @hasProperties() or @base.isComplex()
    -  isAssignable   : -> @hasProperties() or @base.isAssignable()
    -  isSimpleNumber : -> @base instanceof Literal and SIMPLENUM.test @base.value
    -  isString       : -> @base instanceof Literal and IS_STRING.test @base.value
    -  isAtomic       : ->
    -    for node in @properties.concat @base
    -      return no if node.soak or node instanceof Call
    -    yes
    +            
    exports.Call = class Call extends Base
    +  constructor: (variable, @args = [], @soak) ->
    +    @isNew    = false
    +    @isSuper  = variable is 'super'
    +    @variable = if @isSuper then null else variable
    +    if variable instanceof Value and variable.isNotCallable()
    +      variable.error "literal is not a function"
     
    -  isStatement : (o)    -> not @properties.length and @base.isStatement o
    -  assigns     : (name) -> not @properties.length and @base.assigns name
    -  jumps       : (o)    -> not @properties.length and @base.jumps o
    -
    -  isObject: (onlyGenerated) ->
    -    return no if @properties.length
    -    (@base instanceof Obj) and (not onlyGenerated or @base.generated)
    -
    -  isSplice: ->
    -    last(@properties) instanceof Slice
    + children: ['variable', 'args']
    @@ -1189,14 +1256,17 @@ or vanilla.
    -

    The value can be unwrapped as its inner node, if there are no attached -properties. -

    +

    Tag this invocation as creating a new instance.

    -
      unwrap: ->
    -    if @properties.length then this else @base
    +
      newInstance: ->
    +    base = @variable?.base or @variable
    +    if base instanceof Call and not base.isNew
    +      base.newInstance()
    +    else
    +      @isNew = true
    +    this
    @@ -1207,27 +1277,22 @@ properties.
    -

    A reference has base part (this value) and name part. -We cache them separately for compiling complex expressions. -a()[b()] ?= c -> (_base = a())[_name = b()] ? _base[_name] = c -

    +

    Grab the reference to the superclass's implementation of the current +method.

    -
      cacheReference: (o) ->
    -    name = last @properties
    -    if @properties.length < 2 and not @base.isComplex() and not name?.isComplex()
    -      return [this, this]  # `a` `a.b`
    -    base = new Value @base, @properties[...-1]
    -    if base.isComplex()  # `a().b`
    -      bref = new Literal o.scope.freeVariable 'base'
    -      base = new Value new Parens new Assign bref, base
    -    return [base, bref] unless name  # `a()`
    -    if name.isComplex()  # `a[b()]`
    -      nref = new Literal o.scope.freeVariable 'name'
    -      name = new Index new Assign nref, name.index
    -      nref = new Index nref
    -    [base.add(name), new Value(bref or base.base, [nref or name])]
    +
      superReference: (o) ->
    +    method = o.scope.namedMethod()
    +    if method?.klass
    +      accesses = [new Access(new Literal '__super__')]
    +      accesses.push new Access new Literal 'constructor' if method.static
    +      accesses.push new Access new Literal method.name
    +      (new Value (new Literal method.klass), accesses).compile o
    +    else if method?.ctor
    +      "#{method.name}.__super__.constructor"
    +    else
    +      @error 'cannot call super outside of an instance method.'
    @@ -1238,23 +1303,13 @@ We cache them separately for compiling complex expressions.
    -

    We compile a value to JavaScript by compiling and joining each property. -Things get much more interesting if the chain of properties has soak -operators ?. interspersed. Then we have to take care not to accidentally -evaluate anything twice when building the soak chain. -

    +

    The appropriate this value for a super call.

    -
      compileNode: (o) ->
    -    @base.front = @front
    -    props = @properties
    -    fragments = @base.compileToFragments o, (if props.length then LEVEL_ACCESS else null)
    -    if (@base instanceof Parens or props.length) and SIMPLENUM.test fragmentsToText fragments
    -      fragments.push @makeCode '.'
    -    for prop in props
    -      fragments.push (prop.compileToFragments o)...
    -    fragments
    +
      superThis : (o) ->
    +    method = o.scope.method
    +    (method and not method.klass and method.context) or "this"
    @@ -1265,26 +1320,40 @@ evaluate anything twice when building the soak chain.
    -

    Unfold a soak into an If: a?.b -> a.b if a? -

    +

    Soaked chained invocations unfold into if/else ternary structures.

    -
      unfoldSoak: (o) ->
    -    @unfoldedSoak ?= do =>
    -      if ifn = @base.unfoldSoak o
    -        ifn.body.properties.push @properties...
    -        return ifn
    -      for prop, i in @properties when prop.soak
    -        prop.soak = off
    -        fst = new Value @base, @properties[...i]
    -        snd = new Value @base, @properties[i..]
    -        if fst.isComplex()
    -          ref = new Literal o.scope.freeVariable 'ref'
    -          fst = new Parens new Assign ref, fst
    -          snd.base = ref
    -        return new If new Existence(fst), snd, soak: on
    -      no
    +
      unfoldSoak: (o) ->
    +    if @soak
    +      if @variable
    +        return ifn if ifn = unfoldSoak o, this, 'variable'
    +        [left, rite] = new Value(@variable).cacheReference o
    +      else
    +        left = new Literal @superReference o
    +        rite = new Value left
    +      rite = new Call rite, @args
    +      rite.isNew = @isNew
    +      left = new Literal "typeof #{ left.compile o } === \"function\""
    +      return new If left, new Value(rite), soak: yes
    +    call = this
    +    list = []
    +    loop
    +      if call.variable instanceof Call
    +        list.push call
    +        call = call.variable
    +        continue
    +      break unless call.variable instanceof Value
    +      list.push call
    +      break unless (call = call.variable.base) instanceof Call
    +    for call in list.reverse()
    +      if ifn
    +        if call.variable instanceof Call
    +          call.variable = ifn
    +        else
    +          call.variable.base = ifn
    +      ifn = unfoldSoak o, call, 'variable'
    +    ifn
    @@ -1292,13 +1361,36 @@ evaluate anything twice when building the soak chain.
  • -
    +
    -

    Comment

    +

    Compile a vanilla function call.

    +
      compileNode: (o) ->
    +    @variable?.front = @front
    +    compiledArray = Splat.compileSplattedArray o, @args, true
    +    if compiledArray.length
    +      return @compileSplat o, compiledArray
    +    compiledArgs = []
    +    for arg, argIndex in @args
    +      if argIndex then compiledArgs.push @makeCode ", "
    +      compiledArgs.push (arg.compileToFragments o, LEVEL_LIST)...
    +
    +    fragments = []
    +    if @isSuper
    +      preface = @superReference(o) + ".call(#{@superThis(o)}"
    +      if compiledArgs.length then preface += ", "
    +      fragments.push @makeCode preface
    +    else
    +      if @isNew then fragments.push @makeCode 'new '
    +      fragments.push @variable.compileToFragments(o, LEVEL_ACCESS)...
    +      fragments.push @makeCode "("
    +    fragments.push compiledArgs...
    +    fragments.push @makeCode ")"
    +    fragments
    +
  • @@ -1308,22 +1400,48 @@ evaluate anything twice when building the soak chain.
    -

    CoffeeScript passes through block comments as JavaScript block comments -at the same position. -

    +

    If you call a function with a splat, it's converted into a JavaScript +.apply() call to allow an array of arguments to be passed. +If it's a constructor, then things get real tricky. We have to inject an +inner constructor in order to be able to pass the varargs.

    +

    splatArgs is an array of CodeFragments to put into the 'apply'.

    -
    exports.Comment = class Comment extends Base
    -  constructor: (@comment) ->
    +            
      compileSplat: (o, splatArgs) ->
    +    if @isSuper
    +      return [].concat @makeCode("#{ @superReference o }.apply(#{@superThis(o)}, "),
    +        splatArgs, @makeCode(")")
     
    -  isStatement:     YES
    -  makeReturn:      THIS
    +    if @isNew
    +      idt = @tab + TAB
    +      return [].concat @makeCode("""
    +        (function(func, args, ctor) {
    +        #{idt}ctor.prototype = func.prototype;
    +        #{idt}var child = new ctor, result = func.apply(child, args);
    +        #{idt}return Object(result) === result ? result : child;
    +        #{@tab}})("""),
    +        (@variable.compileToFragments o, LEVEL_LIST),
    +        @makeCode(", "), splatArgs, @makeCode(", function(){})")
     
    -  compileNode: (o, level) ->
    -    code = "/*#{multident @comment, @tab}#{if '\n' in @comment then "\n#{@tab}" else ''}*/\n"
    -    code = o.indent + code if (level or o.level) is LEVEL_TOP
    -    [@makeCode code]
    + answer = [] + base = new Value @variable + if (name = base.properties.pop()) and base.isComplex() + ref = o.scope.freeVariable 'ref' + answer = answer.concat @makeCode("(#{ref} = "), + (base.compileToFragments o, LEVEL_LIST), + @makeCode(")"), + name.compileToFragments(o) + else + fun = base.compileToFragments o, LEVEL_ACCESS + fun = @wrapInBraces fun if SIMPLENUM.test fragmentsToText fun + if name + ref = fragmentsToText fun + fun.push (name.compileToFragments o)... + else + ref = 'null' + answer = answer.concat fun + answer = answer.concat @makeCode(".apply(#{ref}, "), splatArgs, @makeCode(")")
    @@ -1331,13 +1449,21 @@ at the same position.
  • -
    +
    -

    Call

    +

    Extends

    +

    Node to extend an object's prototype with an ancestor object. +After goog.inherits from the +Closure Library.

    +
    exports.Extends = class Extends extends Base
    +  constructor: (@child, @parent) ->
    +
    +  children: ['child', 'parent']
    +
  • @@ -1347,19 +1473,12 @@ at the same position.
    -

    Node for a function invocation. Takes care of converting super() calls into -calls against the prototype's function of the same name. -

    +

    Hooks one constructor into another's prototype chain.

    -
    exports.Call = class Call extends Base
    -  constructor: (variable, @args = [], @soak) ->
    -    @isNew    = false
    -    @isSuper  = variable is 'super'
    -    @variable = if @isSuper then null else variable
    -
    -  children: ['variable', 'args']
    +
      compileToFragments: (o) ->
    +    new Call(new Value(new Literal utility 'extends'), [@child, @parent]).compileToFragments o
    @@ -1370,18 +1489,29 @@ calls against the prototype's function of the same name.
    -

    Tag this invocation as creating a new instance. -

    +

    Access

    +

    A . access into a property of a value, or the :: shorthand for +an access into the object's prototype.

    -
      newInstance: ->
    -    base = @variable?.base or @variable
    -    if base instanceof Call and not base.isNew
    -      base.newInstance()
    -    else
    -      @isNew = true
    -    this
    +
    exports.Access = class Access extends Base
    +  constructor: (@name, tag) ->
    +    @name.asKey = yes
    +    @soak  = tag is 'soak'
    +
    +  children: ['name']
    +
    +  compileToFragments: (o) ->
    +    name = @name.compileToFragments o
    +    if IDENTIFIER.test fragmentsToText name
    +      name.unshift @makeCode "."
    +    else
    +      name.unshift @makeCode "["
    +      name.push @makeCode "]"
    +    name
    +
    +  isComplex: NO
    @@ -1392,23 +1522,21 @@ calls against the prototype's function of the same name.
    -

    Grab the reference to the superclass's implementation of the current -method. -

    +

    Index

    +

    A [ ... ] indexed access into an array or object.

    -
      superReference: (o) ->
    -    method = o.scope.namedMethod()
    -    if method?.klass
    -      accesses = [new Access(new Literal '__super__')]
    -      accesses.push new Access new Literal 'constructor' if method.static
    -      accesses.push new Access new Literal method.name
    -      (new Value (new Literal method.klass), accesses).compile o
    -    else if method?.ctor
    -      "#{method.name}.__super__.constructor"
    -    else
    -      @error 'cannot call super outside of an instance method.'
    +
    exports.Index = class Index extends Base
    +  constructor: (@index) ->
    +
    +  children: ['index']
    +
    +  compileToFragments: (o) ->
    +    [].concat @makeCode("["), @index.compileToFragments(o, LEVEL_PAREN), @makeCode("]")
    +
    +  isComplex: ->
    +    @index.isComplex()
    @@ -1419,14 +1547,20 @@ method.
    -

    The appropriate this value for a super call. -

    +

    Range

    +

    A range literal. Ranges can be used to extract portions (slices) of arrays, +to specify a range for comprehensions, or as a value, to be expanded into the +corresponding array of integers at runtime.

    -
      superThis : (o) ->
    -    method = o.scope.method
    -    (method and not method.klass and method.context) or "this"
    +
    exports.Range = class Range extends Base
    +
    +  children: ['from', 'to']
    +
    +  constructor: (@from, @to, tag) ->
    +    @exclusive = tag is 'exclusive'
    +    @equals = if @exclusive then '' else '='
    @@ -1437,41 +1571,18 @@ method.
    -

    Soaked chained invocations unfold into if/else ternary structures. -

    +

    Compiles the range's source variables -- where it starts and where it ends. +But only if they need to be cached to avoid double evaluation.

    -
      unfoldSoak: (o) ->
    -    if @soak
    -      if @variable
    -        return ifn if ifn = unfoldSoak o, this, 'variable'
    -        [left, rite] = new Value(@variable).cacheReference o
    -      else
    -        left = new Literal @superReference o
    -        rite = new Value left
    -      rite = new Call rite, @args
    -      rite.isNew = @isNew
    -      left = new Literal "typeof #{ left.compile o } === \"function\""
    -      return new If left, new Value(rite), soak: yes
    -    call = this
    -    list = []
    -    loop
    -      if call.variable instanceof Call
    -        list.push call
    -        call = call.variable
    -        continue
    -      break unless call.variable instanceof Value
    -      list.push call
    -      break unless (call = call.variable.base) instanceof Call
    -    for call in list.reverse()
    -      if ifn
    -        if call.variable instanceof Call
    -          call.variable = ifn
    -        else
    -          call.variable.base = ifn
    -      ifn = unfoldSoak o, call, 'variable'
    -    ifn
    +
      compileVariables: (o) ->
    +    o = merge o, top: true
    +    [@fromC, @fromVar]  =  @cacheToCodeFragments @from.cache o, LEVEL_LIST
    +    [@toC, @toVar]      =  @cacheToCodeFragments @to.cache o, LEVEL_LIST
    +    [@step, @stepVar]   =  @cacheToCodeFragments step.cache o, LEVEL_LIST if step = del o, 'step'
    +    [@fromNum, @toNum]  = [@fromVar.match(NUMBER), @toVar.match(NUMBER)]
    +    @stepNum            = @stepVar.match(NUMBER) if @stepVar
    @@ -1482,33 +1593,14 @@ method.
    -

    Compile a vanilla function call. -

    +

    When compiled normally, the range returns the contents of the for loop +needed to iterate over the values in the range. Used by comprehensions.

    -
      compileNode: (o) ->
    -    @variable?.front = @front
    -    compiledArray = Splat.compileSplattedArray o, @args, true
    -    if compiledArray.length
    -      return @compileSplat o, compiledArray
    -    compiledArgs = []
    -    for arg, argIndex in @args
    -      if argIndex then compiledArgs.push @makeCode ", "
    -      compiledArgs.push (arg.compileToFragments o, LEVEL_LIST)...
    -
    -    fragments = []
    -    if @isSuper
    -      preface = @superReference(o) + ".call(#{@superThis(o)}"
    -      if compiledArgs.length then preface += ", "
    -      fragments.push @makeCode preface
    -    else
    -      if @isNew then fragments.push @makeCode 'new '
    -      fragments.push @variable.compileToFragments(o, LEVEL_ACCESS)...
    -      fragments.push @makeCode "("
    -    fragments.push compiledArgs...
    -    fragments.push @makeCode ")"
    -    fragments
    +
      compileNode: (o) ->
    +    @compileVariables o unless @fromVar
    +    return @compileArray(o) unless o.index
    @@ -1519,51 +1611,18 @@ method.
    -

    If you call a function with a splat, it's converted into a JavaScript -.apply() call to allow an array of arguments to be passed. -If it's a constructor, then things get real tricky. We have to inject an -inner constructor in order to be able to pass the varargs. - -

    -

    splatArgs is an array of CodeFragments to put into the 'apply'. -

    +

    Set up endpoints.

    -
      compileSplat: (o, splatArgs) ->
    -    if @isSuper
    -      return [].concat @makeCode("#{ @superReference o }.apply(#{@superThis(o)}, "),
    -        splatArgs, @makeCode(")")
    -
    -    if @isNew
    -      idt = @tab + TAB
    -      return [].concat @makeCode("""
    -        (function(func, args, ctor) {
    -        #{idt}ctor.prototype = func.prototype;
    -        #{idt}var child = new ctor, result = func.apply(child, args);
    -        #{idt}return Object(result) === result ? result : child;
    -        #{@tab}})("""),
    -        (@variable.compileToFragments o, LEVEL_LIST),
    -        @makeCode(", "), splatArgs, @makeCode(", function(){})")
    -
    -    answer = []
    -    base = new Value @variable
    -    if (name = base.properties.pop()) and base.isComplex()
    -      ref = o.scope.freeVariable 'ref'
    -      answer = answer.concat @makeCode("(#{ref} = "),
    -        (base.compileToFragments o, LEVEL_LIST),
    -        @makeCode(")"),
    -        name.compileToFragments(o)
    -    else
    -      fun = base.compileToFragments o, LEVEL_ACCESS
    -      fun = @wrapInBraces fun if SIMPLENUM.test fragmentsToText fun
    -      if name
    -        ref = fragmentsToText fun
    -        fun.push (name.compileToFragments o)...
    -      else
    -        ref = 'null'
    -      answer = answer.concat fun
    -    answer = answer.concat @makeCode(".apply(#{ref}, "), splatArgs, @makeCode(")")
    +
        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
    +    [lt, gt] = ["#{idx} <#{@equals}", "#{idx} >#{@equals}"]
    @@ -1571,13 +1630,22 @@ inner constructor in order to be able to pass the varargs.
  • -
    +
    -

    Extends

    +

    Generate the condition.

    +
        condPart = if @stepNum
    +      if parseNum(@stepNum[0]) > 0 then "#{lt} #{@toVar}" else "#{gt} #{@toVar}"
    +    else if known
    +      [from, to] = [parseNum(@fromNum[0]), parseNum(@toNum[0])]
    +      if from <= to then "#{lt} #{to}" else "#{gt} #{to}"
    +    else
    +      cond = if @stepVar then "#{@stepVar} > 0" else "#{@fromVar} <= #{@toVar}"
    +      "#{cond} ? #{lt} #{@toVar} : #{gt} #{@toVar}"
    +
  • @@ -1587,17 +1655,25 @@ inner constructor in order to be able to pass the varargs.
    -

    Node to extend an object's prototype with an ancestor object. -After goog.inherits from the -Closure Library. -

    +

    Generate the step.

    -
    exports.Extends = class Extends extends Base
    -  constructor: (@child, @parent) ->
    +            
        stepPart = if @stepVar
    +      "#{idx} += #{@stepVar}"
    +    else if known
    +      if namedIndex
    +        if from <= to then "++#{idx}" else "--#{idx}"
    +      else
    +        if from <= to then "#{idx}++" else "#{idx}--"
    +    else
    +      if namedIndex
    +        "#{cond} ? ++#{idx} : --#{idx}"
    +      else
    +        "#{cond} ? #{idx}++ : #{idx}--"
     
    -  children: ['child', 'parent']
    + varPart = "#{idxName} = #{varPart}" if namedIndex + stepPart = "#{idxName} = #{stepPart}" if namedIndex
    @@ -1608,13 +1684,11 @@ After goog.inherits from the
    -

    Hooks one constructor into another's prototype chain. -

    +

    The final loop body.

    -
      compileToFragments: (o) ->
    -    new Call(new Value(new Literal utility 'extends'), [@child, @parent]).compileToFragments o
    +
        [@makeCode "#{varPart}; #{condPart}; #{stepPart}"]
    @@ -1622,13 +1696,34 @@ After goog.inherits from the
  • -
    +
    -

    Access

    +

    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
    +      return [@makeCode "[#{ range.join(', ') }]"]
    +    idt    = @tab + TAB
    +    i      = o.scope.freeVariable 'i'
    +    result = o.scope.freeVariable 'results'
    +    pre    = "\n#{idt}#{result} = [];"
    +    if @fromNum and @toNum
    +      o.index = i
    +      body    = fragmentsToText @compileNode o
    +    else
    +      vars    = "#{i} = #{@fromC}" + if @toC isnt @toVar then ", #{@toC}" else ''
    +      cond    = "#{@fromVar} <= #{@toVar}"
    +      body    = "var #{vars}; #{cond} ? #{i} <#{@equals} #{@toVar} : #{i} >#{@equals} #{@toVar}; #{cond} ? #{i}++ : #{i}--"
    +    post   = "{ #{result}.push(#{i}); }\n#{idt}return #{result};\n#{o.indent}"
    +    hasArgs = (node) -> node?.contains isLiteralArguments
    +    args   = ', arguments' if hasArgs(@from) or hasArgs(@to)
    +    [@makeCode "(function() {#{pre}\n#{idt}for (#{body})#{post}}).apply(this#{args ? ''})"]
    +
  • @@ -1638,29 +1733,19 @@ After goog.inherits from the
    -

    A . access into a property of a value, or the :: shorthand for -an access into the object's prototype. -

    +

    Slice

    +

    An array slice literal. Unlike JavaScript's Array#slice, the second parameter +specifies the index of the end of the slice, just as the first parameter +is the index of the beginning.

    -
    exports.Access = class Access extends Base
    -  constructor: (@name, tag) ->
    -    @name.asKey = yes
    -    @soak  = tag is 'soak'
    +            
    exports.Slice = class Slice extends Base
     
    -  children: ['name']
    +  children: ['range']
     
    -  compileToFragments: (o) ->
    -    name = @name.compileToFragments o
    -    if IDENTIFIER.test fragmentsToText name
    -      name.unshift @makeCode "."
    -    else
    -      name.unshift @makeCode "["
    -      name.push @makeCode "]"
    -    name
    -
    -  isComplex: NO
    + constructor: (@range) -> + super()
    @@ -1668,13 +1753,19 @@ an access into the object's prototype.
  • -
    +
    -

    Index

    +

    We have to be careful when trying to slice through the end of the array, +9e9 is used because not all implementations respect undefined or 1/0. +9e9 should be safe because 9e9 > 2**32, the max array length.

    +
      compileNode: (o) ->
    +    {to, from} = @range
    +    fromCompiled = from and from.compileToFragments(o, LEVEL_PAREN) or [@makeCode '0']
    +
  • @@ -1684,21 +1775,22 @@ an access into the object's prototype.
    -

    A [ ... ] indexed access into an array or object. -

    +

    TODO: jwalton - move this into the 'if'?

    -
    exports.Index = class Index extends Base
    -  constructor: (@index) ->
    -
    -  children: ['index']
    -
    -  compileToFragments: (o) ->
    -    [].concat @makeCode("["), @index.compileToFragments(o, LEVEL_PAREN), @makeCode("]")
    -
    -  isComplex: ->
    -    @index.isComplex()
    +
        if to
    +      compiled     = to.compileToFragments o, LEVEL_PAREN
    +      compiledText = fragmentsToText compiled
    +      if not (not @range.exclusive and +compiledText is -1)
    +        toStr = ', ' + if @range.exclusive
    +          compiledText
    +        else if SIMPLENUM.test compiledText
    +          "#{+compiledText + 1}"
    +        else
    +          compiled = to.compileToFragments o, LEVEL_ACCESS
    +          "+#{fragmentsToText compiled} + 1 || 9e9"
    +    [@makeCode ".slice(#{ fragmentsToText fromCompiled }#{ toStr or '' })"]
    @@ -1706,13 +1798,56 @@ an access into the object's prototype.
  • -
    +
    -

    Range

    +

    Obj

    +

    An object literal, nothing fancy.

    +
    exports.Obj = class Obj extends Base
    +  constructor: (props, @generated = false) ->
    +    @objects = @properties = props or []
    +
    +  children: ['properties']
    +
    +  compileNode: (o) ->
    +    props = @properties
    +    return [@makeCode(if @front then '({})' else '{}')] unless props.length
    +    if @generated
    +      for node in props when node instanceof Value
    +        node.error 'cannot have an implicit value in an implicit object'
    +    idt         = o.indent += TAB
    +    lastNoncom  = @lastNonComment @properties
    +    answer = []
    +    for prop, i in props
    +      join = if i is props.length - 1
    +        ''
    +      else if prop is lastNoncom or prop instanceof Comment
    +        '\n'
    +      else
    +        ',\n'
    +      indent = if prop instanceof Comment then '' else idt
    +      if prop instanceof Assign and prop.variable instanceof Value and prop.variable.hasProperties()
    +        prop.variable.error 'Invalid object key'
    +      if prop instanceof Value and prop.this
    +        prop = new Assign prop.properties[0].name, prop, 'object'
    +      if prop not instanceof Comment
    +        if prop not instanceof Assign
    +          prop = new Assign prop, prop, 'object'
    +        (prop.variable.base or prop.variable).asKey = yes
    +      if indent then answer.push @makeCode indent
    +      answer.push prop.compileToFragments(o, LEVEL_TOP)...
    +      if join then answer.push @makeCode join
    +    answer.unshift @makeCode "{#{ props.length and '\n' }"
    +    answer.push @makeCode "#{ props.length and '\n' + @tab }}"
    +    if @front then @wrapInBraces answer else answer
    +
    +  assigns: (name) ->
    +    for prop in @properties when prop.assigns name then return yes
    +    no
    +
  • @@ -1722,20 +1857,40 @@ an access into the object's prototype.
    -

    A range literal. Ranges can be used to extract portions (slices) of arrays, -to specify a range for comprehensions, or as a value, to be expanded into the -corresponding array of integers at runtime. -

    +

    Arr

    +

    An array literal.

    -
    exports.Range = class Range extends Base
    +            
    exports.Arr = class Arr extends Base
    +  constructor: (objs) ->
    +    @objects = objs or []
     
    -  children: ['from', 'to']
    +  children: ['objects']
     
    -  constructor: (@from, @to, tag) ->
    -    @exclusive = tag is 'exclusive'
    -    @equals = if @exclusive then '' else '='
    + compileNode: (o) -> + return [@makeCode '[]'] unless @objects.length + o.indent += TAB + answer = Splat.compileSplattedArray o, @objects + return answer if answer.length + + answer = [] + compiledObjs = (obj.compileToFragments o, LEVEL_LIST for obj in @objects) + for fragments, index in compiledObjs + if index + answer.push @makeCode ", " + answer.push fragments... + if fragmentsToText(answer).indexOf('\n') >= 0 + answer.unshift @makeCode "[\n#{o.indent}" + answer.push @makeCode "\n#{@tab}]" + else + answer.unshift @makeCode "[" + answer.push @makeCode "]" + answer + + assigns: (name) -> + for obj in @objects when obj.assigns name then return yes + no
    @@ -1746,19 +1901,19 @@ corresponding array of integers at runtime.
    -

    Compiles the range's source variables -- where it starts and where it ends. -But only if they need to be cached to avoid double evaluation. -

    +

    Class

    +

    The CoffeeScript class definition. +Initialize a Class with its name, an optional superclass, and a +list of prototype property assignments.

    -
      compileVariables: (o) ->
    -    o = merge o, top: true
    -    [@fromC, @fromVar]  =  @cacheToCodeFragments @from.cache o, LEVEL_LIST
    -    [@toC, @toVar]      =  @cacheToCodeFragments @to.cache o, LEVEL_LIST
    -    [@step, @stepVar]   =  @cacheToCodeFragments step.cache o, LEVEL_LIST if step = del o, 'step'
    -    [@fromNum, @toNum]  = [@fromVar.match(SIMPLENUM), @toVar.match(SIMPLENUM)]
    -    @stepNum            = @stepVar.match(SIMPLENUM) if @stepVar
    +
    exports.Class = class Class extends Base
    +  constructor: (@variable, @parent, @body = new Block) ->
    +    @boundFuncs = []
    +    @body.classBody = yes
    +
    +  children: ['variable', 'parent', 'body']
    @@ -1769,15 +1924,19 @@ But only if they need to be cached to avoid double evaluation.
    -

    When compiled normally, the range returns the contents of the for loop -needed to iterate over the values in the range. Used by comprehensions. -

    +

    Figure out the appropriate name for the constructor function of this class.

    -
      compileNode: (o) ->
    -    @compileVariables o unless @fromVar
    -    return @compileArray(o) unless o.index
    +
      determineName: ->
    +    return null unless @variable
    +    decl = if tail = last @variable.properties
    +      tail instanceof Access and tail.name.value
    +    else
    +      @variable.base.value
    +    if decl in STRICT_PROSCRIBED
    +      @variable.error "class variable name may not be #{decl}"
    +    decl and= IDENTIFIER.test(decl) and decl
    @@ -1788,19 +1947,19 @@ needed to iterate over the values in the range. Used by comprehensions.
    -

    Set up endpoints. -

    +

    For all this-references and bound functions in the class definition, +this is the Class being constructed.

    -
        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
    -    [lt, gt] = ["#{idx} <#{@equals}", "#{idx} >#{@equals}"]
    +
      setContext: (name) ->
    +    @body.traverseChildren false, (node) ->
    +      return false if node.classBody
    +      if node instanceof Literal and node.value is 'this'
    +        node.value    = name
    +      else if node instanceof Code
    +        node.klass    = name
    +        node.context  = name if node.bound
    @@ -1811,19 +1970,16 @@ needed to iterate over the values in the range. Used by comprehensions.
    -

    Generate the condition. -

    +

    Ensure that all functions bound to the instance are proxied in the +constructor.

    -
        condPart = if @stepNum
    -      if +@stepNum > 0 then "#{lt} #{@toVar}" else "#{gt} #{@toVar}"
    -    else if known
    -      [from, to] = [+@fromNum, +@toNum]
    -      if from <= to then "#{lt} #{to}" else "#{gt} #{to}"
    -    else
    -      cond = if @stepVar then "#{@stepVar} > 0" else "#{@fromVar} <= #{@toVar}"
    -      "#{cond} ? #{lt} #{@toVar} : #{gt} #{@toVar}"
    +
      addBoundFunctions: (o) ->
    +    for bvar in @boundFuncs
    +      lhs = (new Value (new Literal "this"), [new Access bvar]).compile o
    +      @ctor.body.unshift new Literal "#{lhs} = #{utility 'bind'}(#{lhs}, this)"
    +    return
    @@ -1834,26 +1990,38 @@ needed to iterate over the values in the range. Used by comprehensions.
    -

    Generate the step. -

    +

    Merge the properties from a top-level object as prototypal properties +on the class.

    -
        stepPart = if @stepVar
    -      "#{idx} += #{@stepVar}"
    -    else if known
    -      if namedIndex
    -        if from <= to then "++#{idx}" else "--#{idx}"
    -      else
    -        if from <= to then "#{idx}++" else "#{idx}--"
    -    else
    -      if namedIndex
    -        "#{cond} ? ++#{idx} : --#{idx}"
    -      else
    -        "#{cond} ? #{idx}++ : #{idx}--"
    -
    -    varPart  = "#{idxName} = #{varPart}" if namedIndex
    -    stepPart = "#{idxName} = #{stepPart}" if namedIndex
    +
      addProperties: (node, name, o) ->
    +    props = node.base.properties[..]
    +    exprs = while assign = props.shift()
    +      if assign instanceof Assign
    +        base = assign.variable.base
    +        delete assign.context
    +        func = assign.value
    +        if base.value is 'constructor'
    +          if @ctor
    +            assign.error 'cannot define more than one constructor in a class'
    +          if func.bound
    +            assign.error 'cannot define a constructor as a bound function'
    +          if func instanceof Code
    +            assign = @ctor = func
    +          else
    +            @externalCtor = o.classScope.freeVariable 'class'
    +            assign = new Assign new Literal(@externalCtor), func
    +        else
    +          if assign.variable.this
    +            func.static = yes
    +          else
    +            assign.variable = new Value(new Literal(name), [(new Access new Literal 'prototype'), new Access base])
    +            if func instanceof Code and func.bound
    +              @boundFuncs.push base
    +              func.bound = no
    +      assign
    +    compact exprs
    @@ -1864,12 +2032,24 @@ needed to iterate over the values in the range. Used by comprehensions.
    -

    The final loop body. -

    +

    Walk the body of the class, looking for prototype properties to be converted +and tagging static assignments.

    -
        [@makeCode "#{varPart}; #{condPart}; #{stepPart}"]
    +
      walkBody: (name, o) ->
    +    @traverseChildren false, (child) =>
    +      cont = true
    +      return false if child instanceof Class
    +      if child instanceof Block
    +        for node, i in exps = child.expressions
    +          if node instanceof Assign and node.variable.looksStatic name
    +            node.value.static = yes
    +          else if node instanceof Value and node.isObject(true)
    +            cont = false
    +            exps[i] = @addProperties node, name, o
    +        child.expressions = exps = flatten exps
    +      cont and child not instanceof Class
    @@ -1880,31 +2060,18 @@ needed to iterate over the values in the range. Used by comprehensions.
    -

    When used as a value, expand the range into the equivalent array. -

    +

    use strict (and other directives) must be the first expression statement(s) +of a function body. This method ensures the prologue is correctly positioned +above the constructor.

    -
      compileArray: (o) ->
    -    if @fromNum and @toNum and Math.abs(@fromNum - @toNum) <= 20
    -      range = [+@fromNum..+@toNum]
    -      range.pop() if @exclusive
    -      return [@makeCode "[#{ range.join(', ') }]"]
    -    idt    = @tab + TAB
    -    i      = o.scope.freeVariable 'i'
    -    result = o.scope.freeVariable 'results'
    -    pre    = "\n#{idt}#{result} = [];"
    -    if @fromNum and @toNum
    -      o.index = i
    -      body    = fragmentsToText @compileNode o
    -    else
    -      vars    = "#{i} = #{@fromC}" + if @toC isnt @toVar then ", #{@toC}" else ''
    -      cond    = "#{@fromVar} <= #{@toVar}"
    -      body    = "var #{vars}; #{cond} ? #{i} <#{@equals} #{@toVar} : #{i} >#{@equals} #{@toVar}; #{cond} ? #{i}++ : #{i}--"
    -    post   = "{ #{result}.push(#{i}); }\n#{idt}return #{result};\n#{o.indent}"
    -    hasArgs = (node) -> node?.contains (n) -> n instanceof Literal and n.value is 'arguments' and not n.asKey
    -    args   = ', arguments' if hasArgs(@from) or hasArgs(@to)
    -    [@makeCode "(function() {#{pre}\n#{idt}for (#{body})#{post}}).apply(this#{args ? ''})"]
    +
      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
    @@ -1912,13 +2079,27 @@ needed to iterate over the values in the range. Used by comprehensions.
  • -
    +
    -

    Slice

    +

    Make sure that a constructor is defined for the class, and properly +configured.

    +
      ensureConstructor: (name) ->
    +    if not @ctor
    +      @ctor = new Code
    +      if @externalCtor
    +        @ctor.body.push new Literal "#{@externalCtor}.apply(this, arguments)"
    +      else if @parent
    +        @ctor.body.push new Literal "#{name}.__super__.constructor.apply(this, arguments)"
    +      @ctor.body.makeReturn()
    +      @body.expressions.unshift @ctor
    +    @ctor.ctor = @ctor.name = name
    +    @ctor.klass = null
    +    @ctor.noReturn = yes
    +
  • @@ -1928,19 +2109,44 @@ needed to iterate over the values in the range. Used by comprehensions.
    -

    An array slice literal. Unlike JavaScript's Array#slice, the second parameter -specifies the index of the end of the slice, just as the first parameter -is the index of the beginning. -

    +

    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.

    -
    exports.Slice = class Slice extends Base
    +            
      compileNode: (o) ->
    +    if jumpNode = @body.jumps()
    +      jumpNode.error 'Class bodies cannot contain pure statements'
    +    if argumentsNode = @body.contains isLiteralArguments
    +      argumentsNode.error "Class bodies shouldn't reference arguments"
     
    -  children: ['range']
    +    name  = @determineName() or '_Class'
    +    name  = "_#{name}" if name.reserved
    +    lname = new Literal name
    +    func  = new Code [], Block.wrap [@body]
    +    args  = []
    +    o.classScope = func.makeScope o.scope
     
    -  constructor: (@range) ->
    -    super()
    + @hoistDirectivePrologue() + @setContext name + @walkBody name, o + @ensureConstructor name + @addBoundFunctions o + @body.spaced = yes + @body.expressions.push lname + + if @parent + superClass = new Literal o.classScope.freeVariable 'super', no + @body.expressions.unshift new Extends lname, superClass + func.params.push new Param superClass + args.push @parent + + @body.expressions.unshift @directives... + + klass = new Parens new Call func, args + klass = new Assign @variable, klass if @variable + klass.compileToFragments o
    @@ -1951,16 +2157,30 @@ is the index of the beginning.
    -

    We have to be careful when trying to slice through the end of the array, -9e9 is used because not all implementations respect undefined or 1/0. -9e9 should be safe because 9e9 > 2**32, the max array length. -

    +

    Assign

    +

    The Assign is used to assign a local variable to value, or to set the +property of an object -- including within object literals.

    -
      compileNode: (o) ->
    -    {to, from} = @range
    -    fromCompiled = from and from.compileToFragments(o, LEVEL_PAREN) or [@makeCode '0']
    +
    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'
    +      @variable.error "variable name may not be \"#{name}\""
    +
    +  children: ['variable', 'value']
    +
    +  isStatement: (o) ->
    +    o?.level is LEVEL_TOP and @context? and "?" in @context
    +
    +  assigns: (name) ->
    +    @[if @context is 'object' then 'value' else 'variable'].assigns name
    +
    +  unfoldSoak: (o) ->
    +    unfoldSoak o, this, 'variable'
    @@ -1971,23 +2191,37 @@ is the index of the beginning.
    -

    TODO: jwalton - move this into the 'if'? -

    +

    Compile an assignment, delegating to compilePatternMatch or +compileSplice if appropriate. Keep track of the name of the base object +we've been assigned to, for correct internal references. If the variable +has not been seen yet within the current scope, declare it.

    -
        if to
    -      compiled     = to.compileToFragments o, LEVEL_PAREN
    -      compiledText = fragmentsToText compiled
    -      if not (not @range.exclusive and +compiledText is -1)
    -        toStr = ', ' + if @range.exclusive
    -          compiledText
    -        else if SIMPLENUM.test compiledText
    -          "#{+compiledText + 1}"
    -        else
    -          compiled = to.compileToFragments o, LEVEL_ACCESS
    -          "+#{fragmentsToText compiled} + 1 || 9e9"
    -    [@makeCode ".slice(#{ fragmentsToText fromCompiled }#{ toStr or '' })"]
    +
      compileNode: (o) ->
    +    if isValue = @variable instanceof Value
    +      return @compilePatternMatch o if @variable.isArray() or @variable.isObject()
    +      return @compileSplice       o if @variable.isSplice()
    +      return @compileConditional  o if @context in ['||=', '&&=', '?=']
    +      return @compileSpecialMath  o if @context in ['**=', '//=', '%%=']
    +    compiledName = @variable.compileToFragments o, LEVEL_LIST
    +    name = fragmentsToText compiledName
    +    unless @context
    +      varBase = @variable.unwrapAll()
    +      unless varBase.isAssignable()
    +        @variable.error "\"#{@variable.compile o}\" cannot be assigned"
    +      unless varBase.hasProperties?()
    +        if @param
    +          o.scope.add name, 'var'
    +        else
    +          o.scope.find name
    +    if @value instanceof Code and match = METHOD_DEF.exec name
    +      @value.klass = match[1] if match[2]
    +      @value.name  = match[3] ? match[4] ? match[5]
    +    val = @value.compileToFragments o, LEVEL_LIST
    +    return (compiledName.concat @makeCode(": "), val) if @context is 'object'
    +    answer = compiledName.concat @makeCode(" #{ @context or '=' } "), val
    +    if o.level <= LEVEL_LIST then answer else @wrapInBraces answer
    @@ -1995,13 +2229,26 @@ is the index of the beginning.
  • -
    +
    -

    Obj

    +

    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) ->
    +    top       = o.level is LEVEL_TOP
    +    {value}   = this
    +    {objects} = @variable.base
    +    unless olen = objects.length
    +      code = value.compileToFragments o
    +      return if o.level >= LEVEL_OP then @wrapInBraces code else code
    +    isObject = @variable.isObject()
    +    if top and olen is 1 and (obj = objects[0]) not instanceof Splat
    +
  • @@ -2011,52 +2258,27 @@ is the index of the beginning.
    -

    An object literal, nothing fancy. -

    +

    Unroll simplest cases: {v} = x -> v = x.v

    -
    exports.Obj = class Obj extends Base
    -  constructor: (props, @generated = false) ->
    -    @objects = @properties = props or []
    -
    -  children: ['properties']
    -
    -  compileNode: (o) ->
    -    props = @properties
    -    return [@makeCode(if @front then '({})' else '{}')] unless props.length
    -    if @generated
    -      for node in props when node instanceof Value
    -        node.error 'cannot have an implicit value in an implicit object'
    -    idt         = o.indent += TAB
    -    lastNoncom  = @lastNonComment @properties
    -    answer = []
    -    for prop, i in props
    -      join = if i is props.length - 1
    -        ''
    -      else if prop is lastNoncom or prop instanceof Comment
    -        '\n'
    -      else
    -        ',\n'
    -      indent = if prop instanceof Comment then '' else idt
    -      if prop instanceof Assign and prop.variable instanceof Value and prop.variable.hasProperties()
    -        prop.variable.error 'Invalid object key'
    -      if prop instanceof Value and prop.this
    -        prop = new Assign prop.properties[0].name, prop, 'object'
    -      if prop not instanceof Comment
    -        if prop not instanceof Assign
    -          prop = new Assign prop, prop, 'object'
    -        (prop.variable.base or prop.variable).asKey = yes
    -      if indent then answer.push @makeCode indent
    -      answer.push prop.compileToFragments(o, LEVEL_TOP)...
    -      if join then answer.push @makeCode join
    -    answer.unshift @makeCode "{#{ props.length and '\n' }"
    -    answer.push @makeCode "#{ props.length and '\n' + @tab }}"
    -    if @front then @wrapInBraces answer else answer
    -
    -  assigns: (name) ->
    -    for prop in @properties when prop.assigns name then return yes
    -    no
    +
          if obj instanceof Assign
    +        {variable: {base: idx}, value: obj} = obj
    +      else
    +        idx = if isObject
    +          if obj.this then obj.properties[0].name else obj
    +        else
    +          new Literal 0
    +      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 RESERVED
    +        obj.error "assignment to a reserved word: #{obj.compile o}"
    +      return new Assign(obj, value, null, param: @param).compileToFragments o, LEVEL_TOP
    +    vvar     = value.compileToFragments o, LEVEL_LIST
    +    vvarText = fragmentsToText vvar
    +    assigns  = []
    +    expandedIdx = false
    @@ -2064,13 +2286,19 @@ is the index of the beginning.
  • -
    +
    -

    Arr

    +

    Make vvar into a simple variable if it isn't already.

    +
        if not IDENTIFIER.test(vvarText) or @variable.assigns(vvarText)
    +      assigns.push [@makeCode("#{ ref = o.scope.freeVariable 'ref' } = "), vvar...]
    +      vvar = [@makeCode ref]
    +      vvarText = ref
    +    for obj, i in objects
    +
  • @@ -2080,40 +2308,13 @@ is the index of the beginning.
    -

    An array literal. -

    +

    A regular array pattern-match.

    -
    exports.Arr = class Arr extends Base
    -  constructor: (objs) ->
    -    @objects = objs or []
    -
    -  children: ['objects']
    -
    -  compileNode: (o) ->
    -    return [@makeCode '[]'] unless @objects.length
    -    o.indent += TAB
    -    answer = Splat.compileSplattedArray o, @objects
    -    return answer if answer.length
    -
    -    answer = []
    -    compiledObjs = (obj.compileToFragments o, LEVEL_LIST for obj in @objects)
    -    for fragments, index in compiledObjs
    -      if index
    -        answer.push @makeCode ", "
    -      answer.push fragments...
    -    if fragmentsToText(answer).indexOf('\n') >= 0
    -      answer.unshift @makeCode "[\n#{o.indent}"
    -      answer.push @makeCode "\n#{@tab}]"
    -    else
    -      answer.unshift @makeCode "["
    -      answer.push @makeCode "]"
    -    answer
    -
    -  assigns: (name) ->
    -    for obj in @objects when obj.assigns name then return yes
    -    no
    +
          idx = i
    +      if isObject
    +        if obj instanceof Assign
    @@ -2121,13 +2322,16 @@ is the index of the beginning.
  • -
    +
    -

    Class

    +

    A regular object pattern-match.

    +
              {variable: {base: idx}, value: obj} = obj
    +        else
    +
  • @@ -2137,19 +2341,51 @@ is the index of the beginning.
    -

    The CoffeeScript class definition. -Initialize a Class with its name, an optional superclass, and a -list of prototype property assignments. -

    +

    A shorthand {a, b, @c} = val pattern-match.

    -
    exports.Class = class Class extends Base
    -  constructor: (@variable, @parent, @body = new Block) ->
    -    @boundFuncs = []
    -    @body.classBody = yes
    -
    -  children: ['variable', 'parent', 'body']
    +
              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
    +      if not expandedIdx and obj instanceof Splat
    +        name = obj.name.unwrap().value
    +        obj = obj.unwrap()
    +        val = "#{olen} <= #{vvarText}.length ? #{ utility 'slice' }.call(#{vvarText}, #{i}"
    +        if rest = olen - i - 1
    +          ivar = o.scope.freeVariable 'i'
    +          val += ", #{ivar} = #{vvarText}.length - #{rest}) : (#{ivar} = #{i}, [])"
    +        else
    +          val += ") : []"
    +        val   = new Literal val
    +        expandedIdx = "#{ivar}++"
    +      else if not expandedIdx and obj instanceof Expansion
    +        if rest = olen - i - 1
    +          if rest is 1
    +            expandedIdx = "#{vvarText}.length - 1"
    +          else
    +            ivar = o.scope.freeVariable 'i'
    +            val = new Literal "#{ivar} = #{vvarText}.length - #{rest}"
    +            expandedIdx = "#{ivar}++"
    +            assigns.push val.compileToFragments o, LEVEL_LIST
    +        continue
    +      else
    +        name = obj.unwrap().value
    +        if obj instanceof Splat or obj instanceof Expansion
    +          obj.error "multiple splats/expansions are disallowed in an assignment"
    +        if typeof idx is 'number'
    +          idx = new Literal expandedIdx or idx
    +          acc = no
    +        else
    +          acc = isObject and IDENTIFIER.test idx.unwrap().value or 0
    +        val = new Value new Literal(vvarText), [new (if acc then Access else Index) idx]
    +      if name? and name in RESERVED
    +        obj.error "assignment to a reserved word: #{obj.compile o}"
    +      assigns.push new Assign(obj, val, null, param: @param, subpattern: yes).compileToFragments o, LEVEL_LIST
    +    assigns.push vvar unless top or @subpattern
    +    fragments = @joinFragmentArrays assigns, ', '
    +    if o.level < LEVEL_LIST then fragments else @wrapInBraces fragments
    @@ -2160,20 +2396,14 @@ list of prototype property assignments.
    -

    Figure out the appropriate name for the constructor function of this class. -

    +

    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.

    -
      determineName: ->
    -    return null unless @variable
    -    decl = if tail = last @variable.properties
    -      tail instanceof Access and tail.name.value
    -    else
    -      @variable.base.value
    -    if decl in STRICT_PROSCRIBED
    -      @variable.error "class variable name may not be #{decl}"
    -    decl and= IDENTIFIER.test(decl) and decl
    +
      compileConditional: (o) ->
    +    [left, right] = @variable.cacheReference o
    @@ -2184,20 +2414,19 @@ list of prototype property assignments.
    -

    For all this-references and bound functions in the class definition, -this is the Class being constructed. -

    +

    Disallow conditional assignment of undefined variables.

    -
      setContext: (name) ->
    -    @body.traverseChildren false, (node) ->
    -      return false if node.classBody
    -      if node instanceof Literal and node.value is 'this'
    -        node.value    = name
    -      else if node instanceof Code
    -        node.klass    = name
    -        node.context  = name if node.bound
    +
        if not left.properties.length and left.base instanceof Literal and
    +           left.base.value != "this" and not o.scope.check left.base.value
    +      @variable.error "the variable \"#{left.base.value}\" can't be assigned with #{@context} because it has not been declared before"
    +    if "?" in @context
    +      o.isExistentialEquals = true
    +      new If(new Existence(left), right, type: 'if').addElse(new Assign(right, @value, '=')).compileToFragments o
    +    else
    +      fragments = new Op(@context[...-1], left, new Assign(right, @value, '=')).compileToFragments o
    +      if o.level <= LEVEL_LIST then fragments else @wrapInBraces fragments
    @@ -2208,17 +2437,14 @@ list of prototype property assignments.
    -

    Ensure that all functions bound to the instance are proxied in the -constructor. -

    +

    Convert special math assignment operators like a **= b to the equivalent +extended form a = a ** b and then compiles that.

    -
      addBoundFunctions: (o) ->
    -    for bvar in @boundFuncs
    -      lhs = (new Value (new Literal "this"), [new Access bvar]).compile o
    -      @ctor.body.unshift new Literal "#{lhs} = #{utility 'bind'}(#{lhs}, this)"
    -    return
    +
      compileSpecialMath: (o) ->
    +    [left, right] = @variable.cacheReference o
    +    new Assign(left, new Op(@context[...-1], right, @value)).compileToFragments o
    @@ -2229,41 +2455,31 @@ constructor.
    -

    Merge the properties from a top-level object as prototypal properties -on the class. -

    +

    Compile the assignment from an array splice literal, using JavaScript's +Array#splice method.

    -
      addProperties: (node, name, o) ->
    -    props = node.base.properties[..]
    -    exprs = while assign = props.shift()
    -      if assign instanceof Assign
    -        base = assign.variable.base
    -        delete assign.context
    -        func = assign.value
    -        if base.value is 'constructor'
    -          if @ctor
    -            assign.error 'cannot define more than one constructor in a class'
    -          if func.bound
    -            assign.error 'cannot define a constructor as a bound function'
    -          if func instanceof Code
    -            assign = @ctor = func
    -          else
    -            @externalCtor = o.scope.freeVariable 'class'
    -            assign = new Assign new Literal(@externalCtor), func
    -        else
    -          if assign.variable.this
    -            func.static = yes
    -            if func.bound
    -              func.context = name
    -          else
    -            assign.variable = new Value(new Literal(name), [(new Access new Literal 'prototype'), new Access base])
    -            if func instanceof Code and func.bound
    -              @boundFuncs.push base
    -              func.bound = no
    -      assign
    -    compact exprs
    +
      compileSplice: (o) ->
    +    {range: {from, to, exclusive}} = @variable.properties.pop()
    +    name = @variable.compile o
    +    if from
    +      [fromDecl, fromRef] = @cacheToCodeFragments from.cache o, LEVEL_OP
    +    else
    +      fromDecl = fromRef = '0'
    +    if to
    +      if from instanceof Value and from.isSimpleNumber() and
    +         to instanceof Value and to.isSimpleNumber()
    +        to = to.compile(o) - fromRef
    +        to += 1 unless exclusive
    +      else
    +        to = to.compile(o, LEVEL_ACCESS) + ' - ' + fromRef
    +        to += ' + 1' unless exclusive
    +    else
    +      to = "9e9"
    +    [valDef, valRef] = @value.cache o, LEVEL_LIST
    +    answer = [].concat @makeCode("[].splice.apply(#{name}, [#{fromDecl}, #{to}].concat("), valDef, @makeCode(")), "), valRef
    +    if o.level > LEVEL_TOP then @wrapInBraces answer else answer
    @@ -2274,22 +2490,26 @@ on the class.
    -

    Walk the body of the class, looking for prototype properties to be converted. -

    +

    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.

    -
      walkBody: (name, o) ->
    -    @traverseChildren false, (child) =>
    -      cont = true
    -      return false if child instanceof Class
    -      if child instanceof Block
    -        for node, i in exps = child.expressions
    -          if node instanceof Value and node.isObject(true)
    -            cont = false
    -            exps[i] = @addProperties node, name, o
    -        child.expressions = exps = flatten exps
    -      cont and child not instanceof Class
    +
    exports.Code = class Code extends Base
    +  constructor: (params, body, tag) ->
    +    @params  = params or []
    +    @body    = body or new Block
    +    @bound   = tag is 'boundfunc'
    +
    +  children: ['params', 'body']
    +
    +  isStatement: -> !!@ctor
    +
    +  jumps: NO
    +
    +  makeScope: (parentScope) -> new Scope parentScope, @body, this
    @@ -2300,19 +2520,18 @@ on the class.
    -

    use strict (and other directives) must be the first expression statement(s) -of a function body. This method ensures the prologue is correctly positioned -above the constructor. -

    +

    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 arguments object. If the function is bound with the => +arrow, generates a wrapper that saves the current value of this through +a closure.

    -
      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
    +
      compileNode: (o) ->
    +
    +    if @bound and o.scope.method?.bound
    +      @context = o.scope.method.context
    @@ -2323,31 +2542,72 @@ above the constructor.
    -

    Make sure that a constructor is defined for the class, and properly -configured. -

    +

    Handle bound functions early.

    -
      ensureConstructor: (name, o) ->
    -    missing = not @ctor
    -    @ctor or= new Code
    -    @ctor.ctor = @ctor.name = name
    -    @ctor.klass = null
    -    @ctor.noReturn = yes
    -    if missing
    -      superCall = new Literal "#{name}.__super__.constructor.apply(this, arguments)" if @parent
    -      superCall = new Literal "#{@externalCtor}.apply(this, arguments)" if @externalCtor
    -      if superCall
    -        ref = new Literal o.scope.freeVariable 'ref'
    -        @ctor.body.unshift new Assign ref, superCall
    -      @addBoundFunctions o
    -      if superCall
    -        @ctor.body.push ref
    -        @ctor.body.makeReturn()
    -      @body.expressions.unshift @ctor
    -    else
    -      @addBoundFunctions o
    +
        if @bound and not @context
    +      @context = '_this'
    +      wrapper = new Code [new Param new Literal @context], new Block [this]
    +      boundfunc = new Call(wrapper, [new Literal 'this'])
    +      boundfunc.updateLocationDataIfMissing @locationData
    +      return boundfunc.compileNode(o)
    +
    +    o.scope         = del(o, 'classScope') or @makeScope o.scope
    +    o.scope.shared  = del(o, 'sharedScope')
    +    o.indent        += TAB
    +    delete o.bare
    +    delete o.isExistentialEquals
    +    params = []
    +    exprs  = []
    +    for param in @params when param not instanceof Expansion
    +      o.scope.parameter param.asReference o
    +    for param in @params when param.splat or param instanceof Expansion
    +      for {name: p} in @params when param not instanceof Expansion
    +        if p.this then p = p.properties[0].name
    +        if p.value then o.scope.add p.value, 'var', yes
    +      splats = new Assign new Value(new Arr(p.asReference o for p in @params)),
    +                          new Value new Literal 'arguments'
    +      break
    +    for param in @params
    +      if param.isComplex()
    +        val = ref = param.asReference o
    +        val = new Op '?', ref, param.value if param.value
    +        exprs.push new Assign new Value(param.name), val, '=', param: yes
    +      else
    +        ref = param
    +        if param.value
    +          lit = new Literal ref.name.value + ' == null'
    +          val = new Assign new Value(param.name), param.value, '='
    +          exprs.push new If lit, val
    +      params.push ref unless splats
    +    wasEmpty = @body.isEmpty()
    +    exprs.unshift splats if splats
    +    @body.expressions.unshift exprs... if exprs.length
    +    for p, i in params
    +      params[i] = p.compileToFragments o
    +      o.scope.parameter fragmentsToText params[i]
    +    uniqs = []
    +    @eachParamName (name, node) ->
    +      node.error "multiple parameters named '#{name}'" if name in uniqs
    +      uniqs.push name
    +    @body.makeReturn() unless wasEmpty or @noReturn
    +    code  = 'function'
    +    code  += ' ' + @name if @ctor
    +    code  += '('
    +    answer = [@makeCode(code)]
    +    for p, i in params
    +      if i then answer.push @makeCode ", "
    +      answer.push p...
    +    answer.push @makeCode ') {'
    +    answer = answer.concat(@makeCode("\n"), @body.compileWithDeclarations(o), @makeCode("\n#{@tab}")) unless @body.isEmpty()
    +    answer.push @makeCode '}'
    +
    +    return [@makeCode(@tab), answer...] if @ctor
    +    if @front or (o.level >= LEVEL_ACCESS) then @wrapInBraces answer else answer
    +
    +  eachParamName: (iterator) ->
    +    param.eachName iterator for param in @params
    @@ -2358,40 +2618,13 @@ configured.
    -

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

    +

    Short-circuit traverseChildren method to prevent it from crossing scope boundaries +unless crossScope is true.

    -
      compileNode: (o) ->
    -    decl  = @determineName()
    -    name  = decl or '_Class'
    -    name = "_#{name}" if name.reserved
    -    lname = new Literal name
    -
    -    @hoistDirectivePrologue()
    -    @setContext name
    -    @walkBody name, o
    -    @ensureConstructor name, o
    -    @body.spaced = yes
    -    @body.expressions.unshift @ctor unless @ctor instanceof Code
    -    @body.expressions.push lname
    -    @body.expressions.unshift @directives...
    -
    -    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
    -      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.compileToFragments o
    +
      traverseChildren: (crossScope, func) ->
    +    super(crossScope, func) if crossScope
    @@ -2399,13 +2632,43 @@ constructor, property assignments, and inheritance getting built out below.
  • -
    +
    -

    Assign

    +

    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
    +      @name.error "parameter name \"#{name}\" is not allowed"
    +
    +  children: ['name', 'value']
    +
    +  compileToFragments: (o) ->
    +    @name.compileToFragments o, LEVEL_LIST
    +
    +  asReference: (o) ->
    +    return @reference if @reference
    +    node = @name
    +    if node.this
    +      node = node.properties[0].name
    +      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
    +    node = new Splat node if @splat
    +    node.updateLocationDataIfMissing @locationData
    +    @reference = node
    +
    +  isComplex: ->
    +    @name.isComplex()
    +
  • @@ -2415,30 +2678,19 @@ constructor, property assignments, and inheritance getting built out below.
    -

    The Assign is used to assign a local variable to value, or to set the -property of an object -- including within object literals. -

    +

    Iterates the name or names of a Param. +In a sense, a destructured parameter represents multiple JS parameters. This +method allows to iterate them all. +The iterator function will be called as iterator(name, node) where +name is the name of the parameter and node is the AST node corresponding +to that name.

    -
    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'
    -      @variable.error "variable name may not be \"#{name}\""
    -
    -  children: ['variable', 'value']
    -
    -  isStatement: (o) ->
    -    o?.level is LEVEL_TOP and @context? and "?" in @context
    -
    -  assigns: (name) ->
    -    @[if @context is 'object' then 'value' else 'variable'].assigns name
    -
    -  unfoldSoak: (o) ->
    -    unfoldSoak o, this, 'variable'
    +
      eachName: (iterator, name = @name)->
    +    atParam = (obj) ->
    +      node = obj.properties[0].name
    +      iterator node.value, node unless node.value.reserved
    @@ -2449,37 +2701,13 @@ property of an object -- including within object literals.
    -

    Compile an assignment, delegating to compilePatternMatch or -compileSplice if appropriate. Keep track of the name of the base object -we've been assigned to, for correct internal references. If the variable -has not been seen yet within the current scope, declare it. -

    +
      +
    • simple literals foo
    • +
    -
      compileNode: (o) ->
    -    if isValue = @variable instanceof Value
    -      return @compilePatternMatch o if @variable.isArray() or @variable.isObject()
    -      return @compileSplice       o if @variable.isSplice()
    -      return @compileConditional  o if @context in ['||=', '&&=', '?=']
    -    compiledName = @variable.compileToFragments o, LEVEL_LIST
    -    name = fragmentsToText compiledName
    -    unless @context
    -      varBase = @variable.unwrapAll()
    -      unless varBase.isAssignable()
    -        @variable.error "\"#{@variable.compile o}\" cannot be assigned"
    -      unless varBase.hasProperties?()
    -        if @param
    -          o.scope.add name, 'var'
    -        else
    -          o.scope.find name
    -    if @value instanceof Code and match = METHOD_DEF.exec name
    -      @value.klass = match[1] if match[1]
    -      @value.name  = match[2] ? match[3] ? match[4] ? match[5]
    -    val = @value.compileToFragments o, LEVEL_LIST
    -    return (compiledName.concat @makeCode(": "), val) if @context is 'object'
    -    answer = compiledName.concat @makeCode(" #{ @context or '=' } "), val
    -    if o.level <= LEVEL_LIST then answer else @wrapInBraces answer
    +
        return iterator name.value, name if name instanceof Literal
    @@ -2490,23 +2718,14 @@ has not been seen yet within the current scope, declare it.
    -

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

    +
      +
    • at-params @foo
    • +
    -
      compilePatternMatch: (o) ->
    -    top       = o.level is LEVEL_TOP
    -    {value}   = this
    -    {objects} = @variable.base
    -    unless olen = objects.length
    -      code = value.compileToFragments o
    -      return if o.level >= LEVEL_OP then @wrapInBraces code else code
    -    isObject = @variable.isObject()
    -    if top and olen is 1 and (obj = objects[0]) not instanceof Splat
    +
        return atParam name if name instanceof Value
    +    for obj in name.objects
    @@ -2517,28 +2736,14 @@ for details.
    -

    Unroll simplest cases: {v} = x -> v = x.v -

    +
      +
    • assignments within destructured parameters {foo:bar}
    • +
    -
          if obj instanceof Assign
    -        {variable: {base: idx}, value: obj} = obj
    -      else
    -        idx = if isObject
    -          if obj.this then obj.properties[0].name else obj
    -        else
    -          new Literal 0
    -      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 RESERVED
    -        obj.error "assignment to a reserved word: #{obj.compile o}"
    -      return new Assign(obj, value, null, param: @param).compileToFragments o, LEVEL_TOP
    -    vvar    = value.compileToFragments o, LEVEL_LIST
    -    vvarText = fragmentsToText vvar
    -    assigns = []
    -    splat   = false
    +
          if obj instanceof Assign
    +        @eachName iterator, obj.value.unwrap()
    @@ -2549,16 +2754,16 @@ for details.
    -

    Make vvar into a simple variable if it isn't already. -

    +
      +
    • splats within destructured parameters [xs...]
    • +
    -
        if not IDENTIFIER.test(vvarText) or @variable.assigns(vvarText)
    -      assigns.push [@makeCode("#{ ref = o.scope.freeVariable 'ref' } = "), vvar...]
    -      vvar = [@makeCode ref]
    -      vvarText = ref
    -    for obj, i in objects
    +
          else if obj instanceof Splat
    +        node = obj.name.unwrap()
    +        iterator node.value, node
    +      else if obj instanceof Value
    @@ -2569,14 +2774,14 @@ for details.
    -

    A regular array pattern-match. -

    +
      +
    • destructured parameters within destructured parameters [{a}]
    • +
    -
          idx = i
    -      if isObject
    -        if obj instanceof Assign
    +
            if obj.isArray() or obj.isObject()
    +          @eachName iterator, obj.base
    @@ -2587,13 +2792,14 @@ for details.
    -

    A regular object pattern-match. -

    +
      +
    • at-params within destructured parameters {@foo}
    • +
    -
              {variable: {base: idx}, value: obj} = obj
    -        else
    +
            else if obj.this
    +          atParam obj
    @@ -2604,42 +2810,16 @@ for details.
    -

    A shorthand {a, b, @c} = val pattern-match. -

    +
      +
    • simple destructured parameters {foo}
    • +
    -
              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
    -      if not splat and obj instanceof Splat
    -        name = obj.name.unwrap().value
    -        obj = obj.unwrap()
    -        val = "#{olen} <= #{vvarText}.length ? #{ utility 'slice' }.call(#{vvarText}, #{i}"
    -        if rest = olen - i - 1
    -          ivar = o.scope.freeVariable 'i'
    -          val += ", #{ivar} = #{vvarText}.length - #{rest}) : (#{ivar} = #{i}, [])"
    -        else
    -          val += ") : []"
    -        val   = new Literal val
    -        splat = "#{ivar}++"
    -      else
    -        name = obj.unwrap().value
    -        if obj instanceof Splat
    -          obj.error "multiple splats are disallowed in an assignment"
    -        if typeof idx is 'number'
    -          idx = new Literal splat or idx
    -          acc = no
    -        else
    -          acc = isObject and IDENTIFIER.test idx.unwrap().value or 0
    -        val = new Value new Literal(vvarText), [new (if acc then Access else Index) idx]
    -      if name? and name in RESERVED
    -        obj.error "assignment to a reserved word: #{obj.compile o}"
    -      assigns.push new Assign(obj, val, null, param: @param, subpattern: yes).compileToFragments o, LEVEL_LIST
    -    assigns.push vvar unless top or @subpattern
    -    fragments = @joinFragmentArrays assigns, ', '
    -    if o.level < LEVEL_LIST then fragments else @wrapInBraces fragments
    +
            else iterator obj.base.value, obj.base
    +      else if obj not instanceof Expansion
    +        obj.error "illegal parameter #{obj.compile()}"
    +    return
    @@ -2650,15 +2830,28 @@ for details.
    -

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

    +

    Splat

    +

    A splat, either as a parameter to a function, an argument to a call, +or as part of a destructuring assignment.

    -
      compileConditional: (o) ->
    -    [left, right] = @variable.cacheReference o
    +
    exports.Splat = class Splat extends Base
    +
    +  children: ['name']
    +
    +  isAssignable: YES
    +
    +  constructor: (name) ->
    +    @name = if name.compile then name else new Literal name
    +
    +  assigns: (name) ->
    +    @name.assigns name
    +
    +  compileToFragments: (o) ->
    +    @name.compileToFragments o
    +
    +  unwrap: -> @name
    @@ -2669,16 +2862,34 @@ more than once.
    -

    Disallow conditional assignment of undefined variables. -

    +

    Utility function that converts an arbitrary number of elements, mixed with +splats, to a proper array.

    -
        if not left.properties.length and left.base instanceof Literal and
    -           left.base.value != "this" and not o.scope.check left.base.value
    -      @variable.error "the variable \"#{left.base.value}\" can't be assigned with #{@context} because it has not been declared before"
    -    if "?" in @context then o.isExistentialEquals = true
    -    new Op(@context[...-1], left, new Assign(right, @value, '=')).compileToFragments o
    +
      @compileSplattedArray: (o, list, apply) ->
    +    index = -1
    +    continue while (node = list[++index]) and node not instanceof Splat
    +    return [] if index >= list.length
    +    if list.length is 1
    +      node = list[0]
    +      fragments = node.compileToFragments o, LEVEL_LIST
    +      return fragments if apply
    +      return [].concat node.makeCode("#{ utility 'slice' }.call("), fragments, node.makeCode(")")
    +    args = list[index..]
    +    for node, i in args
    +      compiledNode = node.compileToFragments o, LEVEL_LIST
    +      args[i] = if node instanceof Splat
    +      then [].concat node.makeCode("#{ utility 'slice' }.call("), compiledNode, node.makeCode(")")
    +      else [].concat node.makeCode("["), compiledNode, node.makeCode("]")
    +    if index is 0
    +      node = list[0]
    +      concatPart = (node.joinFragmentArrays args[1..], ', ')
    +      return args[0].concat node.makeCode(".concat("), concatPart, node.makeCode(")")
    +    base = (node.compileToFragments o, LEVEL_LIST for node in list[...index])
    +    base = list[0].joinFragmentArrays base, ', '
    +    concatPart = list[index].joinFragmentArrays args, ', '
    +    [].concat list[0].makeCode("["), base, list[index].makeCode("].concat("), concatPart, (last list).makeCode(")")
    @@ -2689,31 +2900,23 @@ more than once.
    -

    Compile the assignment from an array splice literal, using JavaScript's -Array#splice method. -

    +

    Expansion

    +

    Used to skip values inside an array destructuring (pattern matching) or +parameter list.

    -
      compileSplice: (o) ->
    -    {range: {from, to, exclusive}} = @variable.properties.pop()
    -    name = @variable.compile o
    -    if from
    -      [fromDecl, fromRef] = @cacheToCodeFragments from.cache o, LEVEL_OP
    -    else
    -      fromDecl = fromRef = '0'
    -    if to
    -      if from?.isSimpleNumber() and to.isSimpleNumber()
    -        to = +to.compile(o) - +fromRef
    -        to += 1 unless exclusive
    -      else
    -        to = to.compile(o, LEVEL_ACCESS) + ' - ' + fromRef
    -        to += ' + 1' unless exclusive
    -    else
    -      to = "9e9"
    -    [valDef, valRef] = @value.cache o, LEVEL_LIST
    -    answer = [].concat @makeCode("[].splice.apply(#{name}, [#{fromDecl}, #{to}].concat("), valDef, @makeCode(")), "), valRef
    -    if o.level > LEVEL_TOP then @wrapInBraces answer else answer
    +
    exports.Expansion = class Expansion extends Base
    +
    +  isComplex: NO
    +
    +  compileNode: (o) ->
    +    @error 'Expansion must be used inside a destructuring assignment or parameter list'
    +
    +  asReference: (o) ->
    +    this
    +
    +  eachName: (iterator) ->
    @@ -2721,13 +2924,42 @@ more than once.
  • -
    +
    -

    Code

    +

    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) ->
    +    @condition = if options?.invert then condition.invert() else condition
    +    @guard     = options?.guard
    +
    +  children: ['condition', 'guard', 'body']
    +
    +  isStatement: YES
    +
    +  makeReturn: (res) ->
    +    if res
    +      super
    +    else
    +      @returns = not @jumps loop: yes
    +      this
    +
    +  addBody: (@body) ->
    +    this
    +
    +  jumps: ->
    +    {expressions} = @body
    +    return no unless expressions.length
    +    for node in expressions
    +      return jumpNode if jumpNode = node.jumps loop: yes
    +    no
    +
  • @@ -2737,25 +2969,33 @@ more than once.
    -

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

    +

    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.

    -
    exports.Code = class Code extends Base
    -  constructor: (params, body, tag) ->
    -    @params  = params or []
    -    @body    = body or new Block
    -    @bound   = tag is 'boundfunc'
    -    @context = '_this' if @bound
    -
    -  children: ['params', 'body']
    -
    -  isStatement: -> !!@ctor
    -
    -  jumps: NO
    +
      compileNode: (o) ->
    +    o.indent += TAB
    +    set      = ''
    +    {body}   = this
    +    if body.isEmpty()
    +      body = @makeCode ''
    +    else
    +      if @returns
    +        body.makeReturn rvar = o.scope.freeVariable 'results'
    +        set  = "#{@tab}#{rvar} = [];\n"
    +      if @guard
    +        if body.expressions.length > 1
    +          body.expressions.unshift new If (new Parens @guard).invert(), new Literal "continue"
    +        else
    +          body = Block.wrap [new If @guard, body] if @guard
    +      body = [].concat @makeCode("\n"), (body.compileToFragments o, LEVEL_TOP), @makeCode("\n#{@tab}")
    +    answer = [].concat @makeCode(set + @tab + "while ("), @condition.compileToFragments(o, LEVEL_PAREN),
    +      @makeCode(") {"), body, @makeCode("}")
    +    if @returns
    +      answer.push @makeCode "\n#{@tab}return #{rvar};"
    +    answer
    @@ -2766,77 +3006,25 @@ has no children -- they're within the inner scope.
    -

    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 arguments object. If the function is bound with the => -arrow, generates a wrapper that saves the current value of this through -a closure. -

    +

    Op

    +

    Simple Arithmetic and logical operations. Performs some conversion from +CoffeeScript operations into their JavaScript equivalents.

    -
      compileNode: (o) ->
    -    o.scope         = new Scope o.scope, @body, this
    -    o.scope.shared  = del(o, 'sharedScope')
    -    o.indent        += TAB
    -    delete o.bare
    -    delete o.isExistentialEquals
    -    params = []
    -    exprs  = []
    -    @eachParamName (name) -> # this step must be performed before the others
    -      unless o.scope.check name then o.scope.parameter name
    -    for param in @params when param.splat
    -      for {name: p} in @params
    -        if p.this then p = p.properties[0].name
    -        if p.value then o.scope.add p.value, 'var', yes
    -      splats = new Assign new Value(new Arr(p.asReference o for p in @params)),
    -                          new Value new Literal 'arguments'
    -      break
    -    for param in @params
    -      if param.isComplex()
    -        val = ref = param.asReference o
    -        val = new Op '?', ref, param.value if param.value
    -        exprs.push new Assign new Value(param.name), val, '=', param: yes
    -      else
    -        ref = param
    -        if param.value
    -          lit = new Literal ref.name.value + ' == null'
    -          val = new Assign new Value(param.name), param.value, '='
    -          exprs.push new If lit, val
    -      params.push ref unless splats
    -    wasEmpty = @body.isEmpty()
    -    exprs.unshift splats if splats
    -    @body.expressions.unshift exprs... if exprs.length
    -    for p, i in params
    -      params[i] = p.compileToFragments o
    -      o.scope.parameter fragmentsToText params[i]
    -    uniqs = []
    -    @eachParamName (name, node) ->
    -      node.error "multiple parameters named '#{name}'" if name in uniqs
    -      uniqs.push name
    -    @body.makeReturn() unless wasEmpty or @noReturn
    -    if @bound
    -      if o.scope.parent.method?.bound
    -        @bound = @context = o.scope.parent.method.context
    -      else if not @static
    -        o.scope.parent.assign '_this', 'this'
    -    idt   = o.indent
    -    code  = 'function'
    -    code  += ' ' + @name if @ctor
    -    code  += '('
    -    answer = [@makeCode(code)]
    -    for p, i in params
    -      if i then answer.push @makeCode ", "
    -      answer.push p...
    -    answer.push @makeCode ') {'
    -    answer = answer.concat(@makeCode("\n"), @body.compileWithDeclarations(o), @makeCode("\n#{@tab}")) unless @body.isEmpty()
    -    answer.push @makeCode '}'
    -
    -    return [@makeCode(@tab), answer...] if @ctor
    -    if @front or (o.level >= LEVEL_ACCESS) then @wrapInBraces answer else answer
    -
    -  eachParamName: (iterator) ->
    -    param.eachName iterator for param in @params
    +
    exports.Op = class Op extends Base
    +  constructor: (op, first, second, flip ) ->
    +    return new In first, second if op is 'in'
    +    if op is 'do'
    +      return @generateDo first
    +    if op is 'new'
    +      return first.newInstance() if first instanceof Call and not first.do and not first.isNew
    +      first = new Parens first   if first instanceof Code and first.bound or first.do
    +    @operator = CONVERSIONS[op] or op
    +    @first    = first
    +    @second   = second
    +    @flip     = !!flip
    +    return this
    @@ -2847,14 +3035,14 @@ a closure.
    -

    Short-circuit traverseChildren method to prevent it from crossing scope boundaries -unless crossScope is true. -

    +

    The map of conversions from CoffeeScript to JavaScript symbols.

    -
      traverseChildren: (crossScope, func) ->
    -    super(crossScope, func) if crossScope
    +
      CONVERSIONS =
    +    '==': '==='
    +    '!=': '!=='
    +    'of': 'in'
    @@ -2862,13 +3050,27 @@ unless crossScope is true.
  • -
    +
    -

    Param

    +

    The map of invertible operators.

    +
      INVERSIONS =
    +    '!==': '==='
    +    '===': '!=='
    +
    +  children: ['first', 'second']
    +
    +  isSimpleNumber: NO
    +
    +  isUnary: ->
    +    not @second
    +
    +  isComplex: ->
    +    not (@isUnary() and @operator in ['+', '-']) or @first.isComplex()
    +
  • @@ -2878,38 +3080,62 @@ unless crossScope is true.
    -

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

    +

    Am I capable of +Python-style comparison chaining?

    -
    exports.Param = class Param extends Base
    -  constructor: (@name, @value, @splat) ->
    -    if (name = @name.unwrapAll().value) in STRICT_PROSCRIBED
    -      @name.error "parameter name \"#{name}\" is not allowed"
    +            
      isChainable: ->
    +    @operator in ['<', '>', '>=', '<=', '===', '!==']
     
    -  children: ['name', 'value']
    +  invert: ->
    +    if @isChainable() and @first.isChainable()
    +      allInvertable = yes
    +      curr = this
    +      while curr and curr.operator
    +        allInvertable and= (curr.operator of INVERSIONS)
    +        curr = curr.first
    +      return new Parens(this).invert() unless allInvertable
    +      curr = this
    +      while curr and curr.operator
    +        curr.invert = !curr.invert
    +        curr.operator = INVERSIONS[curr.operator]
    +        curr = curr.first
    +      this
    +    else if op = INVERSIONS[@operator]
    +      @operator = op
    +      if @first.unwrap() instanceof Op
    +        @first.invert()
    +      this
    +    else if @second
    +      new Parens(this).invert()
    +    else if @operator is '!' and (fst = @first.unwrap()) instanceof Op and
    +                                  fst.operator in ['!', 'in', 'instanceof']
    +      fst
    +    else
    +      new Op '!', this
     
    -  compileToFragments: (o) ->
    -    @name.compileToFragments o, LEVEL_LIST
    +  unfoldSoak: (o) ->
    +    @operator in ['++', '--', 'delete'] and unfoldSoak o, this, 'first'
     
    -  asReference: (o) ->
    -    return @reference if @reference
    -    node = @name
    -    if node.this
    -      node = node.properties[0].name
    -      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
    -    node = new Splat node if @splat
    -    @reference = node
    +  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
     
    -  isComplex: ->
    -    @name.isComplex()
    + compileNode: (o) -> + isChain = @isChainable() and @first.isChainable()
    @@ -2920,20 +3146,28 @@ as well as be a splat, gathering up a group of parameters into an array.
    -

    Iterates the name or names of a Param. -In a sense, a destructured parameter represents multiple JS parameters. This -method allows to iterate them all. -The iterator function will be called as iterator(name, node) where -name is the name of the parameter and node is the AST node corresponding -to that name. -

    +

    In chains, there's no need to wrap bare obj literals in parens, +as the chained expression is wrapped.

    -
      eachName: (iterator, name = @name)->
    -    atParam = (obj) ->
    -      node = obj.properties[0].name
    -      iterator node.value, node unless node.value.reserved
    +
        @first.front = @front unless isChain
    +    if @operator is 'delete' and o.scope.check(@first.unwrapAll().value)
    +      @error 'delete operand may not be argument or var'
    +    if @operator in ['--', '++'] and @first.unwrapAll().value in STRICT_PROSCRIBED
    +      @error "cannot increment/decrement \"#{@first.unwrapAll().value}\""
    +    return @compileUnary     o if @isUnary()
    +    return @compileChain     o if isChain
    +    switch @operator
    +      when '?'  then @compileExistence o
    +      when '**' then @compilePower o
    +      when '//' then @compileFloorDivision o
    +      when '%%' then @compileModulo o
    +      else
    +        lhs = @first.compileToFragments o, LEVEL_OP
    +        rhs = @second.compileToFragments o, LEVEL_OP
    +        answer = [].concat lhs, @makeCode(" #{@operator} "), rhs
    +        if o.level <= LEVEL_OP then answer else @wrapInBraces answer
    @@ -2944,13 +3178,19 @@ to that name.
    -
      -
    • simple literals foo
    • -
    - +

    Mimic Python's chained comparisons when multiple comparison operators are +used sequentially. For example:

    +
    bin/coffee -e 'console.log 50 < 65 > 10'
    +true
    +
    -
        return iterator name.value, name if name instanceof Literal
    +
      compileChain: (o) ->
    +    [@first.second, shared] = @first.second.cache o
    +    fst = @first.compileToFragments o, LEVEL_OP
    +    fragments = fst.concat @makeCode(" #{if @invert then '&&' else '||'} "),
    +      (shared.compileToFragments o), @makeCode(" #{@operator} "), (@second.compileToFragments o, LEVEL_OP)
    +    @wrapInBraces fragments
    @@ -2961,14 +3201,18 @@ to that name.
    -
      -
    • at-params @foo
    • -
    +

    Keep reference to the left expression, unless this an existential assignment

    -
        return atParam name if name instanceof Value
    -    for obj in name.objects
    +
      compileExistence: (o) ->
    +    if @first.isComplex()
    +      ref = new Literal o.scope.freeVariable 'ref'
    +      fst = new Parens new Assign ref, @first
    +    else
    +      fst = @first
    +      ref = fst
    +    new If(new Existence(fst), ref, type: 'if').addElse(@second).compileToFragments o
    @@ -2979,14 +3223,29 @@ to that name.
    -
      -
    • assignments within destructured parameters {foo:bar}
    • -
    +

    Compile a unary Op.

    -
          if obj instanceof Assign
    -        @eachName iterator, obj.value.unwrap()
    +
      compileUnary: (o) ->
    +    parts = []
    +    op = @operator
    +    parts.push [@makeCode op]
    +    if op is '!' and @first instanceof Existence
    +      @first.negated = not @first.negated
    +      return @first.compileToFragments o
    +    if o.level >= LEVEL_ACCESS
    +      return (new Parens this).compileToFragments o
    +    plusMinus = op in ['+', '-']
    +    parts.push [@makeCode(' ')] if op in ['new', 'typeof', 'delete'] or
    +                      plusMinus and @first instanceof Op and @first.operator is op
    +    if (plusMinus and @first instanceof Op) or (op is 'new' and @first.isStatement o)
    +      @first = new Parens @first
    +    parts.push @first.compileToFragments o, LEVEL_OP
    +    parts.reverse() if @flip
    +    @joinFragmentArrays parts, ''
    +
    +  compilePower: (o) ->
    @@ -2997,16 +3256,24 @@ to that name.
    -
      -
    • splats within destructured parameters [xs...]
    • -
    +

    Make a Math.pow call

    -
          else if obj instanceof Splat
    -        node = obj.name.unwrap()
    -        iterator node.value, node
    -      else if obj instanceof Value
    +
        pow = new Value new Literal('Math'), [new Access new Literal 'pow']
    +    new Call(pow, [@first, @second]).compileToFragments o
    +
    +  compileFloorDivision: (o) ->
    +    floor = new Value new Literal('Math'), [new Access new Literal 'floor']
    +    div = new Op '/', @first, @second
    +    new Call(floor, [div]).compileToFragments o
    +
    +  compileModulo: (o) ->
    +    mod = new Value new Literal utility 'modulo'
    +    new Call(mod, [@first, @second]).compileToFragments o
    +
    +  toString: (idt) ->
    +    super idt, @constructor.name + ' ' + @operator
    @@ -3017,14 +3284,22 @@ to that name.
    -
      -
    • destructured parameters within destructured parameters [{a}]
    • -
    +

    In

    -
            if obj.isArray() or obj.isObject()
    -          @eachName iterator, obj.base
    +
    exports.In = class In extends Base
    +  constructor: (@object, @array) ->
    +
    +  children: ['object', 'array']
    +
    +  invert: NEGATE
    +
    +  compileNode: (o) ->
    +    if @array instanceof Value and @array.isArray() and @array.base.objects.length
    +      for obj in @array.base.objects when obj instanceof Splat
    +        hasSplat = yes
    +        break
    @@ -3035,14 +3310,32 @@ to that name.
    -
      -
    • at-params within destructured parameters {@foo}
    • -
    +

    compileOrTest only if we have an array literal with no splats

    -
            else if obj.this
    -          atParam obj
    +
          return @compileOrTest o unless hasSplat
    +    @compileLoopTest o
    +
    +  compileOrTest: (o) ->
    +    [sub, ref] = @object.cache o, LEVEL_OP
    +    [cmp, cnj] = if @negated then [' !== ', ' && '] else [' === ', ' || ']
    +    tests = []
    +    for item, i in @array.base.objects
    +      if i then tests.push @makeCode cnj
    +      tests = tests.concat (if i then ref else sub), @makeCode(cmp), item.compileToFragments(o, LEVEL_ACCESS)
    +    if o.level < LEVEL_OP then tests else @wrapInBraces tests
    +
    +  compileLoopTest: (o) ->
    +    [sub, ref] = @object.cache o, LEVEL_LIST
    +    fragments = [].concat @makeCode(utility('indexOf') + ".call("), @array.compileToFragments(o, LEVEL_LIST),
    +      @makeCode(", "), ref, @makeCode(") " + if @negated then '< 0' else '>= 0')
    +    return fragments if fragmentsToText(sub) is fragmentsToText(ref)
    +    fragments = sub.concat @makeCode(', '), fragments
    +    if o.level < LEVEL_LIST then fragments else @wrapInBraces fragments
    +
    +  toString: (idt) ->
    +    super idt, @constructor.name + if @negated then '!' else ''
    @@ -3053,16 +3346,24 @@ to that name.
    -
      -
    • simple destructured parameters {foo}
    • -
    +

    Try

    +

    A classic try/catch/finally block.

    -
            else iterator obj.base.value, obj.base
    -      else
    -        obj.error "illegal parameter #{obj.compile()}"
    -    return
    +
    exports.Try = class Try extends Base
    +  constructor: (@attempt, @errorVariable, @recovery, @ensure) ->
    +
    +  children: ['attempt', 'recovery', 'ensure']
    +
    +  isStatement: YES
    +
    +  jumps: (o) -> @attempt.jumps(o) or @recovery?.jumps(o)
    +
    +  makeReturn: (res) ->
    +    @attempt  = @attempt .makeReturn res if @attempt
    +    @recovery = @recovery.makeReturn res if @recovery
    +    this
    @@ -3070,13 +3371,35 @@ to that name.
  • -
    +
    -

    Splat

    +

    Compilation is more or less as you would expect -- the finally clause +is optional, the catch is not.

    +
      compileNode: (o) ->
    +    o.indent  += TAB
    +    tryPart   = @attempt.compileToFragments o, LEVEL_TOP
    +
    +    catchPart = if @recovery
    +      placeholder = new Literal '_error'
    +      @recovery.unshift new Assign @errorVariable, placeholder if @errorVariable
    +      [].concat @makeCode(" catch ("), placeholder.compileToFragments(o), @makeCode(") {\n"),
    +        @recovery.compileToFragments(o, LEVEL_TOP), @makeCode("\n#{@tab}}")
    +    else unless @ensure or @recovery
    +      [@makeCode(' catch (_error) {}')]
    +    else
    +      []
    +
    +    ensurePart = if @ensure then ([].concat @makeCode(" finally {\n"), @ensure.compileToFragments(o, LEVEL_TOP),
    +      @makeCode("\n#{@tab}}")) else []
    +
    +    [].concat @makeCode("#{@tab}try {\n"),
    +      tryPart,
    +      @makeCode("\n#{@tab}}"), catchPart, ensurePart
    +
  • @@ -3086,28 +3409,18 @@ to that name.
    -

    A splat, either as a parameter to a function, an argument to a call, -or as part of a destructuring assignment. -

    +

    Throw

    +

    Simple node to throw an exception.

    -
    exports.Splat = class Splat extends Base
    +            
    exports.Throw = class Throw extends Base
    +  constructor: (@expression) ->
     
    -  children: ['name']
    +  children: ['expression']
     
    -  isAssignable: YES
    -
    -  constructor: (name) ->
    -    @name = if name.compile then name else new Literal name
    -
    -  assigns: (name) ->
    -    @name.assigns name
    -
    -  compileToFragments: (o) ->
    -    @name.compileToFragments o
    -
    -  unwrap: -> @name
    + isStatement: YES + jumps: NO
    @@ -3118,35 +3431,14 @@ or as part of a destructuring assignment.
    -

    Utility function that converts an arbitrary number of elements, mixed with -splats, to a proper array. -

    +

    A Throw is already a return, of sorts...

    -
      @compileSplattedArray: (o, list, apply) ->
    -    index = -1
    -    continue while (node = list[++index]) and node not instanceof Splat
    -    return [] if index >= list.length
    -    if list.length is 1
    -      node = list[0]
    -      fragments = node.compileToFragments o, LEVEL_LIST
    -      return fragments if apply
    -      return [].concat node.makeCode("#{ utility 'slice' }.call("), fragments, node.makeCode(")")
    -    args = list[index..]
    -    for node, i in args
    -      compiledNode = node.compileToFragments o, LEVEL_LIST
    -      args[i] = if node instanceof Splat
    -      then [].concat node.makeCode("#{ utility 'slice' }.call("), compiledNode, node.makeCode(")")
    -      else [].concat node.makeCode("["), compiledNode, node.makeCode("]")
    -    if index is 0
    -      node = list[0]
    -      concatPart = (node.joinFragmentArrays args[1..], ', ')
    -      return args[0].concat node.makeCode(".concat("), concatPart, node.makeCode(")")
    -    base = (node.compileToFragments o, LEVEL_LIST for node in list[...index])
    -    base = list[0].joinFragmentArrays base, ', '
    -    concatPart = list[index].joinFragmentArrays args, ', '
    -    [].concat list[0].makeCode("["), base, list[index].makeCode("].concat("), concatPart, (last list).makeCode(")")
    +
      makeReturn: THIS
    +
    +  compileNode: (o) ->
    +    [].concat @makeCode(@tab + "throw "), @expression.compileToFragments(o), @makeCode(";")
    @@ -3154,13 +3446,31 @@ splats, to a proper array.
  • -
    +
    -

    While

    +

    Existence

    +

    Checks a variable for existence -- not null and not undefined. This is +similar to .nil? in Ruby, and avoids having to consult a JavaScript truth +table.

    +
    exports.Existence = class Existence extends Base
    +  constructor: (@expression) ->
    +
    +  children: ['expression']
    +
    +  invert: NEGATE
    +
    +  compileNode: (o) ->
    +    @expression.front = @front
    +    code = @expression.compile o, LEVEL_OP
    +    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
    +
  • @@ -3170,38 +3480,12 @@ splats, to a proper array.
    -

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

    +

    do not use strict equality here; it will break existing code

    -
    exports.While = class While extends Base
    -  constructor: (condition, options) ->
    -    @condition = if options?.invert then condition.invert() else condition
    -    @guard     = options?.guard
    -
    -  children: ['condition', 'guard', 'body']
    -
    -  isStatement: YES
    -
    -  makeReturn: (res) ->
    -    if res
    -      super
    -    else
    -      @returns = not @jumps loop: yes
    -      this
    -
    -  addBody: (@body) ->
    -    this
    -
    -  jumps: ->
    -    {expressions} = @body
    -    return no unless expressions.length
    -    for node in expressions
    -      return node if node.jumps loop: yes
    -    no
    +
          code = "#{code} #{if @negated then '==' else '!='} null"
    +    [@makeCode(if o.level <= LEVEL_COND then code else "(#{code})")]
    @@ -3212,34 +3496,31 @@ flexibility or more speed than a comprehension can provide.
    -

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

    +

    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.

    +

    Parentheses are a good way to force any statement to become an expression.

    -
      compileNode: (o) ->
    -    o.indent += TAB
    -    set      = ''
    -    {body}   = this
    -    if body.isEmpty()
    -      body = @makeCode ''
    -    else
    -      if @returns
    -        body.makeReturn rvar = o.scope.freeVariable 'results'
    -        set  = "#{@tab}#{rvar} = [];\n"
    -      if @guard
    -        if body.expressions.length > 1
    -          body.expressions.unshift new If (new Parens @guard).invert(), new Literal "continue"
    -        else
    -          body = Block.wrap [new If @guard, body] if @guard
    -      body = [].concat @makeCode("\n"), (body.compileToFragments o, LEVEL_TOP), @makeCode("\n#{@tab}")
    -    answer = [].concat @makeCode(set + @tab + "while ("), @condition.compileToFragments(o, LEVEL_PAREN),
    -      @makeCode(") {"), body, @makeCode("}")
    -    if @returns
    -      answer.push @makeCode "\n#{@tab}return #{rvar};"
    -    answer
    +
    exports.Parens = class Parens extends Base
    +  constructor: (@body) ->
    +
    +  children: ['body']
    +
    +  unwrap    : -> @body
    +  isComplex : -> @body.isComplex()
    +
    +  compileNode: (o) ->
    +    expr = @body.unwrap()
    +    if expr instanceof Value and expr.isAtomic()
    +      expr.front = @front
    +      return expr.compileToFragments o
    +    fragments = expr.compileToFragments 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 fragments else @wrapInBraces fragments
    @@ -3247,13 +3528,36 @@ return an array containing the computed result of each iteration.
  • -
    +
    -

    Op

    +

    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.

    +

    Unlike Python array comprehensions, they can be multi-line, and you can pass +the current index of the loop as a second parameter. Unlike Ruby blocks, +you can map and filter in a single pass.

    +
    exports.For = class For extends While
    +  constructor: (body, source) ->
    +    {@source, @guard, @step, @name, @index} = source
    +    @body    = Block.wrap [body]
    +    @own     = !!source.own
    +    @object  = !!source.object
    +    [@name, @index] = [@index, @name] if @object
    +    @index.error 'index cannot be a pattern matching expression' if @index instanceof Value
    +    @range   = @source instanceof Value and @source.base instanceof Range and not @source.properties.length
    +    @pattern = @name instanceof Value
    +    @index.error 'indexes do not apply to range loops' if @range and @index
    +    @name.error 'cannot pattern match over range loops' if @range and @pattern
    +    @name.error 'cannot use own with for-in' if @own and not @object
    +    @returns = false
    +
    +  children: ['body', 'source', 'guard', 'step']
    +
  • @@ -3263,25 +3567,105 @@ return an array containing the computed result of each iteration.
    -

    Simple Arithmetic and logical operations. Performs some conversion from -CoffeeScript operations into their JavaScript equivalents. -

    +

    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.

    -
    exports.Op = class Op extends Base
    -  constructor: (op, first, second, flip ) ->
    -    return new In first, second if op is 'in'
    -    if op is 'do'
    -      return @generateDo first
    -    if op is 'new'
    -      return first.newInstance() if first instanceof Call and not first.do and not first.isNew
    -      first = new Parens first   if first instanceof Code and first.bound or first.do
    -    @operator = CONVERSIONS[op] or op
    -    @first    = first
    -    @second   = second
    -    @flip     = !!flip
    -    return this
    +
      compileNode: (o) ->
    +    body      = Block.wrap [@body]
    +    lastJumps = last(body.expressions)?.jumps()
    +    @returns  = no if lastJumps and lastJumps instanceof Return
    +    source    = if @range then @source.base else @source
    +    scope     = o.scope
    +    name      = @name  and (@name.compile o, LEVEL_LIST) if not @pattern
    +    index     = @index and (@index.compile o, LEVEL_LIST)
    +    scope.find(name)  if name and not @pattern
    +    scope.find(index) if index
    +    rvar      = scope.freeVariable 'results' if @returns
    +    ivar      = (@object and index) or scope.freeVariable 'i'
    +    kvar      = (@range and name) or index or ivar
    +    kvarAssign = if kvar isnt ivar then "#{kvar} = " else ""
    +    if @step and not @range
    +      [step, stepVar] = @cacheToCodeFragments @step.cache o, LEVEL_LIST
    +      stepNum = stepVar.match NUMBER
    +    name      = ivar if @pattern
    +    varPart   = ''
    +    guardPart = ''
    +    defPart   = ''
    +    idt1      = @tab + TAB
    +    if @range
    +      forPartFragments = source.compileToFragments 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}[#{kvar}]"
    +      if not @object
    +        defPart += "#{@tab}#{step};\n" if step isnt stepVar
    +        lvar = scope.freeVariable 'len' unless @step and stepNum and down = (parseNum(stepNum[0]) < 0)
    +        declare = "#{kvarAssign}#{ivar} = 0, #{lvar} = #{svar}.length"
    +        declareDown = "#{kvarAssign}#{ivar} = #{svar}.length - 1"
    +        compare = "#{ivar} < #{lvar}"
    +        compareDown = "#{ivar} >= 0"
    +        if @step
    +          if stepNum
    +            if down
    +              compare = compareDown
    +              declare = declareDown
    +          else
    +            compare = "#{stepVar} > 0 ? #{compare} : #{compareDown}"
    +            declare = "(#{stepVar} > 0 ? (#{declare}) : #{declareDown})"
    +          increment = "#{ivar} += #{stepVar}"
    +        else
    +          increment = "#{if kvar isnt ivar then "++#{ivar}" else "#{ivar}++"}"
    +        forPartFragments  = [@makeCode("#{declare}; #{compare}; #{kvarAssign}#{increment}")]
    +    if @returns
    +      resultPart   = "#{@tab}#{rvar} = [];\n"
    +      returnResult = "\n#{@tab}return #{rvar};"
    +      body.makeReturn rvar
    +    if @guard
    +      if body.expressions.length > 1
    +        body.expressions.unshift new If (new Parens @guard).invert(), new Literal "continue"
    +      else
    +        body = Block.wrap [new If @guard, body] if @guard
    +    if @pattern
    +      body.expressions.unshift new Assign @name, new Literal "#{svar}[#{kvar}]"
    +    defPartFragments = [].concat @makeCode(defPart), @pluckDirectCall(o, body)
    +    varPart = "\n#{idt1}#{namePart};" if namePart
    +    if @object
    +      forPartFragments   = [@makeCode("#{kvar} in #{svar}")]
    +      guardPart = "\n#{idt1}if (!#{utility 'hasProp'}.call(#{svar}, #{kvar})) continue;" if @own
    +    bodyFragments = body.compileToFragments merge(o, indent: idt1), LEVEL_TOP
    +    if bodyFragments and (bodyFragments.length > 0)
    +      bodyFragments = [].concat @makeCode("\n"), bodyFragments, @makeCode("\n")
    +    [].concat defPartFragments, @makeCode("#{resultPart or ''}#{@tab}for ("),
    +      forPartFragments, @makeCode(") {#{guardPart}#{varPart}"), bodyFragments,
    +      @makeCode("#{@tab}}#{returnResult or ''}")
    +
    +  pluckDirectCall: (o, body) ->
    +    defs = []
    +    for expr, idx in body.expressions
    +      expr = expr.unwrapAll()
    +      continue unless expr instanceof Call
    +      val = expr.variable?.unwrapAll()
    +      continue unless (val instanceof Code) or
    +                      (val instanceof Value and
    +                      val.base?.unwrapAll() instanceof Code and
    +                      val.properties.length is 1 and
    +                      val.properties[0].name?.value in ['call', 'apply'])
    +      fn    = val.base?.unwrapAll() or val
    +      ref   = new Literal o.scope.freeVariable 'fn'
    +      base  = new Value ref
    +      if val.base
    +        [val.base, base] = [base, val]
    +      body.expressions[idx] = new Call base, expr.args
    +      defs = defs.concat @makeCode(@tab), (new Assign(ref, fn).compileToFragments(o, LEVEL_TOP)), @makeCode(';\n')
    +    defs
    @@ -3292,15 +3676,48 @@ CoffeeScript operations into their JavaScript equivalents.
    -

    The map of conversions from CoffeeScript to JavaScript symbols. -

    +

    Switch

    +

    A JavaScript switch statement. Converts into a returnable expression on-demand.

    -
      CONVERSIONS =
    -    '==': '==='
    -    '!=': '!=='
    -    'of': 'in'
    +
    exports.Switch = class Switch extends Base
    +  constructor: (@subject, @cases, @otherwise) ->
    +
    +  children: ['subject', 'cases', 'otherwise']
    +
    +  isStatement: YES
    +
    +  jumps: (o = {block: yes}) ->
    +    for [conds, block] in @cases
    +      return jumpNode if jumpNode = block.jumps o
    +    @otherwise?.jumps o
    +
    +  makeReturn: (res) ->
    +    pair[1].makeReturn res for pair in @cases
    +    @otherwise or= new Block [new Literal 'void 0'] if res
    +    @otherwise?.makeReturn res
    +    this
    +
    +  compileNode: (o) ->
    +    idt1 = o.indent + TAB
    +    idt2 = o.indent = idt1 + TAB
    +    fragments = [].concat @makeCode(@tab + "switch ("),
    +      (if @subject then @subject.compileToFragments(o, LEVEL_PAREN) else @makeCode "false"),
    +      @makeCode(") {\n")
    +    for [conditions, block], i in @cases
    +      for cond in flatten [conditions]
    +        cond  = cond.invert() unless @subject
    +        fragments = fragments.concat @makeCode(idt1 + "case "), cond.compileToFragments(o, LEVEL_PAREN), @makeCode(":\n")
    +      fragments = fragments.concat body, @makeCode('\n') if (body = block.compileToFragments o, LEVEL_TOP).length > 0
    +      break if i is @cases.length - 1 and not @otherwise
    +      expr = @lastNonComment block.expressions
    +      continue if expr instanceof Return or (expr instanceof Literal and expr.jumps() and expr.value isnt 'debugger')
    +      fragments.push cond.makeCode(idt2 + 'break;\n')
    +    if @otherwise and @otherwise.expressions.length
    +      fragments.push @makeCode(idt1 + "default:\n"), (@otherwise.compileToFragments o, LEVEL_TOP)..., @makeCode("\n")
    +    fragments.push @makeCode @tab + '}'
    +    fragments
    @@ -3311,24 +3728,25 @@ CoffeeScript operations into their JavaScript equivalents.
    -

    The map of invertible operators. -

    +

    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, +because ternaries are already proper expressions, and don't need conversion.

    -
      INVERSIONS =
    -    '!==': '==='
    -    '===': '!=='
    +            
    exports.If = class If extends Base
    +  constructor: (condition, @body, options = {}) ->
    +    @condition = if options.type is 'unless' then condition.invert() else condition
    +    @elseBody  = null
    +    @isChain   = false
    +    {@soak}    = options
     
    -  children: ['first', 'second']
    +  children: ['condition', 'body', 'elseBody']
     
    -  isSimpleNumber: NO
    -
    -  isUnary: ->
    -    not @second
    -
    -  isComplex: ->
    -    not (@isUnary() and @operator in ['+', '-']) or @first.isComplex()
    + bodyNode: -> @body?.unwrap() + elseBodyNode: -> @elseBody?.unwrap()
    @@ -3339,63 +3757,18 @@ CoffeeScript operations into their JavaScript equivalents.
    -

    Am I capable of -Python-style comparison chaining? -

    +

    Rewrite a chain of Ifs to add a default case as the final else.

    -
      isChainable: ->
    -    @operator in ['<', '>', '>=', '<=', '===', '!==']
    -
    -  invert: ->
    -    if @isChainable() and @first.isChainable()
    -      allInvertable = yes
    -      curr = this
    -      while curr and curr.operator
    -        allInvertable and= (curr.operator of INVERSIONS)
    -        curr = curr.first
    -      return new Parens(this).invert() unless allInvertable
    -      curr = this
    -      while curr and curr.operator
    -        curr.invert = !curr.invert
    -        curr.operator = INVERSIONS[curr.operator]
    -        curr = curr.first
    -      this
    -    else if op = INVERSIONS[@operator]
    -      @operator = op
    -      if @first.unwrap() instanceof Op
    -        @first.invert()
    -      this
    -    else if @second
    -      new Parens(this).invert()
    -    else if @operator is '!' and (fst = @first.unwrap()) instanceof Op and
    -                                  fst.operator in ['!', 'in', 'instanceof']
    -      fst
    -    else
    -      new Op '!', this
    -
    -  unfoldSoak: (o) ->
    -    @operator in ['++', '--', 'delete'] and unfoldSoak o, this, 'first'
    -
    -  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()
    +
      addElse: (elseBody) ->
    +    if @isChain
    +      @elseBodyNode().addElse elseBody
    +    else
    +      @isChain  = elseBody instanceof If
    +      @elseBody = @ensureBlock elseBody
    +      @elseBody.updateLocationDataIfMissing elseBody.locationData
    +    this
    @@ -3406,23 +3779,28 @@ CoffeeScript operations into their JavaScript equivalents.
    -

    In chains, there's no need to wrap bare obj literals in parens, -as the chained expression is wrapped. -

    +

    The If only compiles into a statement if either of its bodies needs +to be a statement. Otherwise a conditional operator is safe.

    -
        @first.front = @front unless isChain
    -    if @operator is 'delete' and o.scope.check(@first.unwrapAll().value)
    -      @error 'delete operand may not be argument or var'
    -    if @operator in ['--', '++'] and @first.unwrapAll().value in STRICT_PROSCRIBED
    -      @error "cannot increment/decrement \"#{@first.unwrapAll().value}\""
    -    return @compileUnary     o if @isUnary()
    -    return @compileChain     o if isChain
    -    return @compileExistence o if @operator is '?'
    -    answer = [].concat @first.compileToFragments(o, LEVEL_OP), @makeCode(' ' + @operator + ' '),
    -            @second.compileToFragments(o, LEVEL_OP)
    -    if o.level <= LEVEL_OP then answer else @wrapInBraces answer
    +
      isStatement: (o) ->
    +    o?.level is LEVEL_TOP or
    +      @bodyNode().isStatement(o) or @elseBodyNode()?.isStatement(o)
    +
    +  jumps: (o) -> @body.jumps(o) or @elseBody?.jumps(o)
    +
    +  compileNode: (o) ->
    +    if @isStatement o then @compileStatement o else @compileExpression o
    +
    +  makeReturn: (res) ->
    +    @elseBody  or= new Block [new Literal 'void 0'] if res
    +    @body     and= new Block [@body.makeReturn res]
    +    @elseBody and= new Block [@elseBody.makeReturn res]
    +    this
    +
    +  ensureBlock: (node) ->
    +    if node instanceof Block then node else new Block [node]
    @@ -3433,21 +3811,31 @@ as the chained expression is wrapped.
    -

    Mimic Python's chained comparisons when multiple comparison operators are -used sequentially. For example: - -

    -
    bin/coffee -e 'console.log 50 < 65 > 10'
    -true
    +

    Compile the If as a regular if-else statement. Flattened chains +force inner else bodies into statement form.

    -
      compileChain: (o) ->
    -    [@first.second, shared] = @first.second.cache o
    -    fst = @first.compileToFragments o, LEVEL_OP
    -    fragments = fst.concat @makeCode(" #{if @invert then '&&' else '||'} "),
    -      (shared.compileToFragments o), @makeCode(" #{@operator} "), (@second.compileToFragments o, LEVEL_OP)
    -    @wrapInBraces fragments
    +
      compileStatement: (o) ->
    +    child    = del o, 'chainChild'
    +    exeq     = del o, 'isExistentialEquals'
    +
    +    if exeq
    +      return new If(@condition.invert(), @elseBodyNode(), type: 'if').compileToFragments o
    +
    +    indent   = o.indent + TAB
    +    cond     = @condition.compileToFragments o, LEVEL_PAREN
    +    body     = @ensureBlock(@body).compileToFragments merge o, {indent}
    +    ifPart   = [].concat @makeCode("if ("), cond, @makeCode(") {\n"), body, @makeCode("\n#{@tab}}")
    +    ifPart.unshift @makeCode @tab unless child
    +    return ifPart unless @elseBody
    +    answer = ifPart.concat @makeCode(' else ')
    +    if @isChain
    +      o.chainChild = yes
    +      answer = answer.concat @elseBody.unwrap().compileToFragments o, LEVEL_TOP
    +    else
    +      answer = answer.concat @makeCode("{\n"), @elseBody.compileToFragments(merge(o, {indent}), LEVEL_TOP), @makeCode("\n#{@tab}}")
    +    answer
    @@ -3458,19 +3846,19 @@ true
    -

    Keep reference to the left expression, unless this an existential assignment -

    +

    Compile the If as a conditional operator.

    -
      compileExistence: (o) ->
    -    if !o.isExistentialEquals and @first.isComplex()
    -      ref = new Literal o.scope.freeVariable 'ref'
    -      fst = new Parens new Assign ref, @first
    -    else
    -      fst = @first
    -      ref = fst
    -    new If(new Existence(fst), ref, type: 'if').addElse(@second).compileToFragments o
    +
      compileExpression: (o) ->
    +    cond = @condition.compileToFragments o, LEVEL_COND
    +    body = @bodyNode().compileToFragments o, LEVEL_LIST
    +    alt  = if @elseBodyNode() then @elseBodyNode().compileToFragments(o, LEVEL_LIST) else [@makeCode('void 0')]
    +    fragments = cond.concat @makeCode(" ? "), body, @makeCode(" : "), alt
    +    if o.level >= LEVEL_COND then @wrapInBraces fragments else fragments
    +
    +  unfoldSoak: ->
    +    @soak and this
    @@ -3481,57 +3869,23 @@ true
    -

    Compile a unary Op. -

    +

    Constants

    -
      compileUnary: (o) ->
    -    parts = []
    -    op = @operator
    -    parts.push [@makeCode op]
    -    if op is '!' and @first instanceof Existence
    -      @first.negated = not @first.negated
    -      return @first.compileToFragments o
    -    if o.level >= LEVEL_ACCESS
    -      return (new Parens this).compileToFragments o
    -    plusMinus = op in ['+', '-']
    -    parts.push [@makeCode(' ')] if op in ['new', 'typeof', 'delete'] or
    -                      plusMinus and @first instanceof Op and @first.operator is op
    -    if (plusMinus and @first instanceof Op) or (op is 'new' and @first.isStatement o)
    -      @first = new Parens @first
    -    parts.push @first.compileToFragments o, LEVEL_OP
    -    parts.reverse() if @flip
    -    @joinFragmentArrays parts, ''
    -
    -  toString: (idt) ->
    -    super idt, @constructor.name + ' ' + @operator
    -
  • -
    +
    -

    In

    - +
    -
    exports.In = class In extends Base
    -  constructor: (@object, @array) ->
    -
    -  children: ['object', 'array']
    -
    -  invert: NEGATE
    -
    -  compileNode: (o) ->
    -    if @array instanceof Value and @array.isArray()
    -      for obj in @array.base.objects when obj instanceof Splat
    -        hasSplat = yes
    -        break
    +
    UTILITIES =
  • @@ -3542,34 +3896,25 @@ true
    -

    compileOrTest only if we have an array literal with no splats -

    +

    Correctly set up a prototype chain for inheritance, including a reference +to the superclass for super() calls, and copies of any static properties.

    -
          return @compileOrTest o unless hasSplat
    -    @compileLoopTest o
    -
    -  compileOrTest: (o) ->
    -    return [@makeCode("#{!!@negated}")] if @array.base.objects.length is 0
    -    [sub, ref] = @object.cache o, LEVEL_OP
    -    [cmp, cnj] = if @negated then [' !== ', ' && '] else [' === ', ' || ']
    -    tests = []
    -    for item, i in @array.base.objects
    -      if i then tests.push @makeCode cnj
    -      tests = tests.concat (if i then ref else sub), @makeCode(cmp), item.compileToFragments(o, LEVEL_ACCESS)
    -    if o.level < LEVEL_OP then tests else @wrapInBraces tests
    -
    -  compileLoopTest: (o) ->
    -    [sub, ref] = @object.cache o, LEVEL_LIST
    -    fragments = [].concat @makeCode(utility('indexOf') + ".call("), @array.compileToFragments(o, LEVEL_LIST),
    -      @makeCode(", "), ref, @makeCode(") " + if @negated then '< 0' else '>= 0')
    -    return fragments if fragmentsToText(sub) is fragmentsToText(ref)
    -    fragments = sub.concat @makeCode(', '), fragments
    -    if o.level < LEVEL_LIST then fragments else @wrapInBraces fragments
    -
    -  toString: (idt) ->
    -    super idt, @constructor.name + if @negated then '!' else ''
    +
      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;
    +    }
    +  "
    @@ -3577,13 +3922,21 @@ true
  • -
    +
    -

    Try

    +

    Create a function bound to the current value of "this".

    +
      bind: -> '
    +    function(fn, me){
    +      return function(){
    +        return fn.apply(me, arguments);
    +      };
    +    }
    +  '
    +
  • @@ -3593,24 +3946,22 @@ true
    -

    A classic try/catch/finally block. -

    +

    Discover if an item is in an array.

    -
    exports.Try = class Try extends Base
    -  constructor: (@attempt, @errorVariable, @recovery, @ensure) ->
    +            
      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;
    +    }
    +  "
     
    -  children: ['attempt', 'recovery', 'ensure']
    -
    -  isStatement: YES
    -
    -  jumps: (o) -> @attempt.jumps(o) or @recovery?.jumps(o)
    -
    -  makeReturn: (res) ->
    -    @attempt  = @attempt .makeReturn res if @attempt
    -    @recovery = @recovery.makeReturn res if @recovery
    -    this
    + modulo: -> """ + function(a, b) { return (a % b + +b) % b; } + """
    @@ -3621,32 +3972,12 @@ true
    -

    Compilation is more or less as you would expect -- the finally clause -is optional, the catch is not. -

    +

    Shortcuts to speed up the lookup time for native functions.

    -
      compileNode: (o) ->
    -    o.indent  += TAB
    -    tryPart   = @attempt.compileToFragments o, LEVEL_TOP
    -
    -    catchPart = if @recovery
    -      placeholder = new Literal '_error'
    -      @recovery.unshift new Assign @errorVariable, placeholder if @errorVariable
    -      [].concat @makeCode(" catch ("), placeholder.compileToFragments(o), @makeCode(") {\n"),
    -        @recovery.compileToFragments(o, LEVEL_TOP), @makeCode("\n#{@tab}}")
    -    else unless @ensure or @recovery
    -      [@makeCode(' catch (_error) {}')]
    -    else
    -      []
    -
    -    ensurePart = if @ensure then ([].concat @makeCode(" finally {\n"), @ensure.compileToFragments(o, LEVEL_TOP),
    -      @makeCode("\n#{@tab}}")) else []
    -
    -    [].concat @makeCode("#{@tab}try {\n"),
    -      tryPart,
    -      @makeCode("\n#{@tab}}"), catchPart, ensurePart
    +
      hasProp: -> '{}.hasOwnProperty'
    +  slice  : -> '[].slice'
    @@ -3654,13 +3985,21 @@ is optional, the catch is not.
  • -
    +
    -

    Throw

    +

    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]
    +
  • @@ -3670,18 +4009,29 @@ is optional, the catch is not.
    -

    Simple node to throw an exception. -

    +

    Tabs are two spaces for pretty printing.

    -
    exports.Throw = class Throw extends Base
    -  constructor: (@expression) ->
    +            
    TAB = '  '
     
    -  children: ['expression']
    +IDENTIFIER_STR = "[$A-Za-z_\\x7f-\\uffff][$\\w\\x7f-\\uffff]*"
    +IDENTIFIER = /// ^ #{IDENTIFIER_STR} $ ///
    +SIMPLENUM  = /^[+-]?\d+$/
    +HEXNUM = /^[+-]?0x[\da-f]+/i
    +NUMBER    = ///^[+-]?(?:
    +  0x[\da-f]+ |              # hex
    +  \d*\.?\d+ (?:e[+-]?\d+)?  # decimal
    +)$///i
     
    -  isStatement: YES
    -  jumps:       NO
    +METHOD_DEF = /// ^ + (#{IDENTIFIER_STR}) + (\.prototype)? + (?: \.(#{IDENTIFIER_STR}) + | \[("(?:[^\\"\r\n]|\\.)*"|'(?:[^\\'\r\n]|\\.)*')\] + | \[(0x[\da-fA-F]+ | \d*\.?\d+ (?:[eE][+-]?\d+)?)\] + ) +$ ///
    @@ -3692,15 +4042,12 @@ is optional, the catch is not.
    -

    A Throw is already a return, of sorts... -

    +

    Is a literal value a string/regex?

    -
      makeReturn: THIS
    -
    -  compileNode: (o) ->
    -    [].concat @makeCode(@tab + "throw "), @expression.compileToFragments(o), @makeCode(";")
    +
    IS_STRING = /^['"]/
    +IS_REGEX = /^\//
    @@ -3708,10 +4055,10 @@ is optional, the catch is not.
  • -
    +
    -

    Existence

    +

    Helper Functions

    @@ -3724,27 +4071,18 @@ is optional, the catch is not.
    -

    Checks a variable for existence -- not null and not undefined. This is -similar to .nil? in Ruby, and avoids having to consult a JavaScript truth -table. -

    +

    Helper for ensuring that utility functions are assigned at the top level.

    -
    exports.Existence = class Existence extends Base
    -  constructor: (@expression) ->
    +            
    utility = (name) ->
    +  ref = "__#{name}"
    +  Scope.root.assign ref, UTILITIES[name]()
    +  ref
     
    -  children: ['expression']
    -
    -  invert: NEGATE
    -
    -  compileNode: (o) ->
    -    @expression.front = @front
    -    code = @expression.compile o, LEVEL_OP
    -    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
    +multident = (code, tab) -> + code = code.replace /\n/g, '$&' + tab + code.replace /\s+$/, ''
  • @@ -3755,13 +4093,26 @@ table.
    -

    do not use strict equality here; it will break existing code -

    +

    Parse a number (+- decimal/hexadecimal) +Examples: 0, -1, 1, 2e3, 2e-3, -0xfe, 0xfe

    -
          code = "#{code} #{if @negated then '==' else '!='} null"
    -    [@makeCode(if o.level <= LEVEL_COND then code else "(#{code})")]
    +
    parseNum = (x) ->
    +  if not x?
    +    0
    +  else if x.match HEXNUM
    +    parseInt x, 16
    +  else
    +    parseFloat x
    +
    +isLiteralArguments = (node) ->
    +  node instanceof Literal and node.value is 'arguments' and not node.asKey
    +
    +isLiteralThis = (node) ->
    +  (node instanceof Literal and node.value is 'this' and not node.asKey) or
    +    (node instanceof Code and node.bound) or
    +    (node instanceof Call and node.isSuper)
    @@ -3769,775 +4120,21 @@ table.
  • -
    +
    -

    Parens

    +

    Unfold a node's child if soak, then tuck the node under created If

    -
  • - - -
  • -
    - -
    - -
    -

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

    -

    Parentheses are a good way to force any statement to become an expression. -

    - -
    - -
    exports.Parens = class Parens extends Base
    -  constructor: (@body) ->
    -
    -  children: ['body']
    -
    -  unwrap    : -> @body
    -  isComplex : -> @body.isComplex()
    -
    -  compileNode: (o) ->
    -    expr = @body.unwrap()
    -    if expr instanceof Value and expr.isAtomic()
    -      expr.front = @front
    -      return expr.compileToFragments o
    -    fragments = expr.compileToFragments 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 fragments else @wrapInBraces fragments
    - -
  • - - -
  • -
    - -
    - -
    -

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

    -

    Unlike Python array comprehensions, they can be multi-line, and you can pass -the current index of the loop as a second parameter. Unlike Ruby blocks, -you can map and filter in a single pass. -

    - -
    - -
    exports.For = class For extends While
    -  constructor: (body, source) ->
    -    {@source, @guard, @step, @name, @index} = source
    -    @body    = Block.wrap [body]
    -    @own     = !!source.own
    -    @object  = !!source.object
    -    [@name, @index] = [@index, @name] if @object
    -    @index.error 'index cannot be a pattern matching expression' if @index instanceof Value
    -    @range   = @source instanceof Value and @source.base instanceof Range and not @source.properties.length
    -    @pattern = @name instanceof Value
    -    @index.error 'indexes do not apply to range loops' if @range and @index
    -    @name.error 'cannot pattern match over range loops' if @range and @pattern
    -    @index.error 'cannot use own with for-in' if @own and not @object
    -    @returns = false
    -
    -  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) ->
    -    body      = Block.wrap [@body]
    -    lastJumps = last(body.expressions)?.jumps()
    -    @returns  = no if lastJumps and lastJumps instanceof Return
    -    source    = if @range then @source.base else @source
    -    scope     = o.scope
    -    name      = @name  and (@name.compile o, LEVEL_LIST)
    -    index     = @index and (@index.compile o, LEVEL_LIST)
    -    scope.find(name)  if name and not @pattern
    -    scope.find(index) if index
    -    rvar      = scope.freeVariable 'results' if @returns
    -    ivar      = (@object and index) or scope.freeVariable 'i'
    -    kvar      = (@range and name) or index or ivar
    -    kvarAssign = if kvar isnt ivar then "#{kvar} = " else ""
    -    if @step and not @range
    -      [step, stepVar] = @cacheToCodeFragments @step.cache o, LEVEL_LIST
    -      stepNum = stepVar.match SIMPLENUM
    -    name      = ivar if @pattern
    -    varPart   = ''
    -    guardPart = ''
    -    defPart   = ''
    -    idt1      = @tab + TAB
    -    if @range
    -      forPartFragments = source.compileToFragments 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}[#{kvar}]"
    -      if not @object
    -        defPart += "#{@tab}#{step};\n" if step isnt stepVar
    -        lvar = scope.freeVariable 'len' unless @step and stepNum and down = (+stepNum < 0)
    -        declare = "#{kvarAssign}#{ivar} = 0, #{lvar} = #{svar}.length"
    -        declareDown = "#{kvarAssign}#{ivar} = #{svar}.length - 1"
    -        compare = "#{ivar} < #{lvar}"
    -        compareDown = "#{ivar} >= 0"
    -        if @step
    -          if stepNum
    -            if down
    -              compare = compareDown
    -              declare = declareDown
    -          else
    -            compare = "#{stepVar} > 0 ? #{compare} : #{compareDown}"
    -            declare = "(#{stepVar} > 0 ? (#{declare}) : #{declareDown})"
    -          increment = "#{ivar} += #{stepVar}"
    -        else
    -          increment = "#{if kvar isnt ivar then "++#{ivar}" else "#{ivar}++"}"
    -        forPartFragments  = [@makeCode("#{declare}; #{compare}; #{kvarAssign}#{increment}")]
    -    if @returns
    -      resultPart   = "#{@tab}#{rvar} = [];\n"
    -      returnResult = "\n#{@tab}return #{rvar};"
    -      body.makeReturn rvar
    -    if @guard
    -      if body.expressions.length > 1
    -        body.expressions.unshift new If (new Parens @guard).invert(), new Literal "continue"
    -      else
    -        body = Block.wrap [new If @guard, body] if @guard
    -    if @pattern
    -      body.expressions.unshift new Assign @name, new Literal "#{svar}[#{kvar}]"
    -    defPartFragments = [].concat @makeCode(defPart), @pluckDirectCall(o, body)
    -    varPart = "\n#{idt1}#{namePart};" if namePart
    -    if @object
    -      forPartFragments   = [@makeCode("#{kvar} in #{svar}")]
    -      guardPart = "\n#{idt1}if (!#{utility 'hasProp'}.call(#{svar}, #{kvar})) continue;" if @own
    -    bodyFragments = body.compileToFragments merge(o, indent: idt1), LEVEL_TOP
    -    if bodyFragments and (bodyFragments.length > 0)
    -      bodyFragments = [].concat @makeCode("\n"), bodyFragments, @makeCode("\n")
    -    [].concat defPartFragments, @makeCode("#{resultPart or ''}#{@tab}for ("),
    -      forPartFragments, @makeCode(") {#{guardPart}#{varPart}"), bodyFragments,
    -      @makeCode("#{@tab}}#{returnResult or ''}")
    -
    -  pluckDirectCall: (o, body) ->
    -    defs = []
    -    for expr, idx in body.expressions
    -      expr = expr.unwrapAll()
    -      continue unless expr instanceof Call
    -      val = expr.variable.unwrapAll()
    -      continue unless (val instanceof Code) or
    -                      (val instanceof Value and
    -                      val.base?.unwrapAll() instanceof Code and
    -                      val.properties.length is 1 and
    -                      val.properties[0].name?.value in ['call', 'apply'])
    -      fn    = val.base?.unwrapAll() or val
    -      ref   = new Literal o.scope.freeVariable 'fn'
    -      base  = new Value ref
    -      if val.base
    -        [val.base, base] = [base, val]
    -      body.expressions[idx] = new Call base, expr.args
    -      defs = defs.concat @makeCode(@tab), (new Assign(ref, fn).compileToFragments(o, LEVEL_TOP)), @makeCode(';\n')
    -    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']
    -
    -  isStatement: YES
    -
    -  jumps: (o = {block: yes}) ->
    -    for [conds, block] in @cases
    -      return block if block.jumps o
    -    @otherwise?.jumps o
    -
    -  makeReturn: (res) ->
    -    pair[1].makeReturn res for pair in @cases
    -    @otherwise or= new Block [new Literal 'void 0'] if res
    -    @otherwise?.makeReturn res
    -    this
    -
    -  compileNode: (o) ->
    -    idt1 = o.indent + TAB
    -    idt2 = o.indent = idt1 + TAB
    -    fragments = [].concat @makeCode(@tab + "switch ("),
    -      (if @subject then @subject.compileToFragments(o, LEVEL_PAREN) else @makeCode "false"),
    -      @makeCode(") {\n")
    -    for [conditions, block], i in @cases
    -      for cond in flatten [conditions]
    -        cond  = cond.invert() unless @subject
    -        fragments = fragments.concat @makeCode(idt1 + "case "), cond.compileToFragments(o, LEVEL_PAREN), @makeCode(":\n")
    -      fragments = fragments.concat body, @makeCode('\n') if (body = block.compileToFragments o, LEVEL_TOP).length > 0
    -      break if i is @cases.length - 1 and not @otherwise
    -      expr = @lastNonComment block.expressions
    -      continue if expr instanceof Return or (expr instanceof Literal and expr.jumps() and expr.value isnt 'debugger')
    -      fragments.push cond.makeCode(idt2 + 'break;\n')
    -    if @otherwise and @otherwise.expressions.length
    -      fragments.push @makeCode(idt1 + "default:\n"), (@otherwise.compileToFragments o, LEVEL_TOP)..., @makeCode("\n")
    -    fragments.push @makeCode @tab + '}'
    -    fragments
    - -
  • - - -
  • -
    - -
    - -
    -

    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, -because ternaries are already proper expressions, and don't need conversion. -

    - -
    - -
    exports.If = class If extends Base
    -  constructor: (condition, @body, options = {}) ->
    -    @condition = if options.type is 'unless' then condition.invert() else condition
    -    @elseBody  = null
    -    @isChain   = false
    -    {@soak}    = options
    -
    -  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) ->
    -    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 -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)
    -
    -  jumps: (o) -> @body.jumps(o) or @elseBody?.jumps(o)
    -
    -  compileNode: (o) ->
    -    if @isStatement o then @compileStatement o else @compileExpression o
    -
    -  makeReturn: (res) ->
    -    @elseBody  or= new Block [new Literal 'void 0'] if res
    -    @body     and= new Block [@body.makeReturn res]
    -    @elseBody and= new Block [@elseBody.makeReturn res]
    -    this
    -
    -  ensureBlock: (node) ->
    -    if node instanceof Block then node else new Block [node]
    - -
  • - - -
  • -
    - -
    - -
    -

    Compile the If as a regular if-else statement. Flattened chains -force inner else bodies into statement form. -

    - -
    - -
      compileStatement: (o) ->
    -    child    = del o, 'chainChild'
    -    exeq     = del o, 'isExistentialEquals'
    -
    -    if exeq
    -      return new If(@condition.invert(), @elseBodyNode(), type: 'if').compileToFragments o
    -
    -    indent   = o.indent + TAB
    -    cond     = @condition.compileToFragments o, LEVEL_PAREN
    -    body     = @ensureBlock(@body).compileToFragments merge o, {indent}
    -    ifPart   = [].concat @makeCode("if ("), cond, @makeCode(") {\n"), body, @makeCode("\n#{@tab}}")
    -    ifPart.unshift @makeCode @tab unless child
    -    return ifPart unless @elseBody
    -    answer = ifPart.concat @makeCode(' else ')
    -    if @isChain
    -      o.chainChild = yes
    -      answer = answer.concat @elseBody.unwrap().compileToFragments o, LEVEL_TOP
    -    else
    -      answer = answer.concat @makeCode("{\n"), @elseBody.compileToFragments(merge(o, {indent}), LEVEL_TOP), @makeCode("\n#{@tab}}")
    -    answer
    - -
  • - - -
  • -
    - -
    - -
    -

    Compile the If as a conditional operator. -

    - -
    - -
      compileExpression: (o) ->
    -    cond = @condition.compileToFragments o, LEVEL_COND
    -    body = @bodyNode().compileToFragments o, LEVEL_LIST
    -    alt  = if @elseBodyNode() then @elseBodyNode().compileToFragments(o, LEVEL_LIST) else [@makeCode('void 0')]
    -    fragments = cond.concat @makeCode(" ? "), body, @makeCode(" : "), alt
    -    if o.level >= LEVEL_COND then @wrapInBraces fragments else fragments
    -
    -  unfoldSoak: ->
    -    @soak and this
    - -
  • - - -
  • -
    - -
    - -
    -

    Faux-Nodes

    - -
    - -
  • - - -
  • -
    - -
    - -
    -

    Faux-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, -in which case, no dice. If the body mentions this or arguments, -then make sure that the closure wrapper preserves the original values. -

    - -
    - -
      wrap: (expressions, statement, noReturn) ->
    -    return expressions if expressions.jumps()
    -    func = new Code [], Block.wrap [expressions]
    -    args = []
    -    argumentsNode = expressions.contains @isLiteralArguments
    -    if argumentsNode and expressions.classBody
    -      argumentsNode.error "Class bodies shouldn't reference arguments"
    -    if argumentsNode or expressions.contains @isLiteralThis
    -      meth = new Literal if argumentsNode then 'apply' else 'call'
    -      args = [new Literal 'this']
    -      args.push new Literal 'arguments' if argumentsNode
    -      func = new Value func, [new Access meth]
    -    func.noReturn = noReturn
    -    call = new Call func, args
    -    if statement then Block.wrap [call] else call
    -
    -  isLiteralArguments: (node) ->
    -    node instanceof Literal and node.value is 'arguments' and not node.asKey
    -
    -  isLiteralThis: (node) ->
    -    (node instanceof Literal and node.value is 'this' and not node.asKey) or
    -      (node instanceof Code and node.bound) or
    -      (node instanceof Call and node.isSuper)
    - -
  • - - -
  • -
    - -
    - -
    -

    Unfold a node's child if soak, then tuck the node under created If -

    - -
    - -
    unfoldSoak = (o, parent, name) ->
    -  return unless ifn = parent[name].unfoldSoak o
    +            
    unfoldSoak = (o, parent, name) ->
    +  return unless ifn = parent[name].unfoldSoak o
       parent[name] = ifn.body
    -  ifn.body = new Value parent
    +  ifn.body = new Value parent
       ifn
  • - -
  • -
    - -
    - -
    -

    Constants

    - -
    - -
  • - - -
  • -
    - -
    - -
    - -
    - -
    -UTILITIES =
    - -
  • - - -
  • -
    - -
    - -
    -

    Correctly set up a prototype chain for inheritance, including a reference -to the superclass for super() calls, and copies of any static properties. -

    - -
    - -
      extends: -> """
    -    function(child, parent) { for (var key in parent) { if (#{utility 'hasProp'}.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }
    -  """
    - -
  • - - -
  • -
    - -
    - -
    -

    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: -> """
    -    [].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 = '  '
    -
    -IDENTIFIER_STR = "[$A-Za-z_\\x7f-\\uffff][$\\w\\x7f-\\uffff]*"
    -IDENTIFIER = /// ^ #{IDENTIFIER_STR} $ ///
    -SIMPLENUM  = /^[+-]?\d+$/
    -METHOD_DEF = ///
    -  ^
    -    (?:
    -      (#{IDENTIFIER_STR})
    -      \.prototype
    -      (?:
    -        \.(#{IDENTIFIER_STR})
    -      | \[("(?:[^\\"\r\n]|\\.)*"|'(?:[^\\'\r\n]|\\.)*')\]
    -      | \[(0x[\da-fA-F]+ | \d*\.?\d+ (?:[eE][+-]?\d+)?)\]
    -      )
    -    )
    -  |
    -    (#{IDENTIFIER_STR})
    -  $
    -///
    - -
  • - - -
  • -
    - -
    - -
    -

    Is a literal value a string? -

    - -
    - -
    IS_STRING = /^['"]/
    - -
  • - - -
  • -
    - -
    - -
    -

    Utility Functions

    - -
    - -
  • - - -
  • -
    - -
    - -
    - -
    - -
  • - - -
  • -
    - -
    - -
    -

    Helper for ensuring that utility functions are assigned at the top level. -

    - -
    - -
    utility = (name) ->
    -  ref = "__#{name}"
    -  Scope.root.assign ref, UTILITIES[name]()
    -  ref
    -
    -multident = (code, tab) ->
    -  code = code.replace /\n/g, '$&' + tab
    -  code.replace /\s+$/, ''
    - -
  • - diff --git a/documentation/docs/optparse.html b/documentation/docs/optparse.html index e96bb4de..d8ce31b8 100644 --- a/documentation/docs/optparse.html +++ b/documentation/docs/optparse.html @@ -69,6 +69,11 @@ + + register.coffee + + + repl.coffee @@ -111,7 +116,7 @@ -
    {repeat} = require './helpers'
    +
    {repeat} = require './helpers'
    @@ -123,18 +128,15 @@

    A simple OptionParser class to parse option flags from the command-line. -Use it like so: - -

    +Use it like so:

    parser  = new OptionParser switches, helpBanner
    -options = parser.parse process.argv
    -

    The first non-option is considered to be the start of the file (and file -option) list, and all subsequent arguments are left unparsed. -

    +options = parser.parse process.argv +

    The first non-option is considered to be the start of the file (and file +option) list, and all subsequent arguments are left unparsed.

    -
    exports.OptionParser = class OptionParser
    +
    exports.OptionParser = class OptionParser
    @@ -145,17 +147,14 @@ option) list, and all subsequent arguments are left unparsed.
    -

    Initialize with a list of valid options, in the form: - -

    -
    [short-flag, long-flag, description]
    -

    Along with an an optional banner for the usage help. -

    +

    Initialize with a list of valid options, in the form:

    +
    [short-flag, long-flag, description]
    +

    Along with an an optional banner for the usage help.

    -
      constructor: (rules, @banner) ->
    -    @rules = buildRules rules
    +
      constructor: (rules, @banner) ->
    +    @rules = buildRules rules
    @@ -171,25 +170,24 @@ specified options, and return it. Options after the first non-option argument are treated as arguments. options.arguments will be an array containing the remaining arguments. This is a simpler API than many option parsers that allow you to attach callback actions for every flag. Instead, -you're responsible for interpreting the options object. -

    +you're responsible for interpreting the options object.

    -
      parse: (args) ->
    -    options = arguments: []
    -    skippingArgument = no
    +            
      parse: (args) ->
    +    options = arguments: []
    +    skippingArgument = no
         originalArgs = args
         args = normalizeArguments args
    -    for arg, i in args
    -      if skippingArgument
    -        skippingArgument = no
    -        continue
    -      if arg is '--'
    -        pos = originalArgs.indexOf '--'
    -        options.arguments = options.arguments.concat originalArgs[(pos + 1)..]
    -        break
    -      isOption = !!(arg.match(LONG_FLAG) or arg.match(SHORT_FLAG))
    + for arg, i in args + if skippingArgument + skippingArgument = no + continue + if arg is '--' + pos = originalArgs.indexOf '--' + options.arguments = options.arguments.concat originalArgs[(pos + 1)..] + break + isOption = !!(arg.match(LONG_FLAG) or arg.match(SHORT_FLAG))
    @@ -201,25 +199,24 @@ you're responsible for interpreting the options object.

    the CS option parser is a little odd; options after the first -non-option argument are treated as non-option arguments themselves -

    +non-option argument are treated as non-option arguments themselves

    -
          seenNonOptionArg = options.arguments.length > 0
    -      unless seenNonOptionArg
    -        matchedRule = no
    -        for rule in @rules
    -          if rule.shortFlag is arg or rule.longFlag is arg
    -            value = true
    -            if rule.hasArgument
    -              skippingArgument = yes
    -              value = args[i + 1]
    -            options[rule.name] = if rule.isList then (options[rule.name] or []).concat value else value
    -            matchedRule = yes
    -            break
    -        throw new Error "unrecognized option: #{arg}" if isOption and not matchedRule
    -      if seenNonOptionArg or not isOption
    +            
          seenNonOptionArg = options.arguments.length > 0
    +      unless seenNonOptionArg
    +        matchedRule = no
    +        for rule in @rules
    +          if rule.shortFlag is arg or rule.longFlag is arg
    +            value = true
    +            if rule.hasArgument
    +              skippingArgument = yes
    +              value = args[i + 1]
    +            options[rule.name] = if rule.isList then (options[rule.name] or []).concat value else value
    +            matchedRule = yes
    +            break
    +        throw new Error "unrecognized option: #{arg}" if isOption and not matchedRule
    +      if seenNonOptionArg or not isOption
             options.arguments.push arg
         options
    @@ -233,20 +230,19 @@ non-option argument are treated as non-option arguments themselves

    Return the help text for this OptionParser, listing and describing all -of the valid options, for --help and such. -

    +of the valid options, for --help and such.

    -
      help: ->
    +            
      help: ->
         lines = []
    -    lines.unshift "#{@banner}\n" if @banner
    -    for rule in @rules
    -      spaces  = 15 - rule.longFlag.length
    -      spaces  = if spaces > 0 then repeat ' ', spaces else ''
    -      letPart = if rule.shortFlag then rule.shortFlag + ', ' else '    '
    -      lines.push '  ' + letPart + rule.longFlag + spaces + rule.description
    -    "\n#{ lines.join('\n') }\n"
    + lines.unshift "#{@banner}\n" if @banner + for rule in @rules + spaces = 15 - rule.longFlag.length + spaces = if spaces > 0 then repeat ' ', spaces else '' + letPart = if rule.shortFlag then rule.shortFlag + ', ' else ' ' + lines.push ' ' + letPart + rule.longFlag + spaces + rule.description + "\n#{ lines.join('\n') }\n"
    @@ -254,10 +250,10 @@ of the valid options, for --help and such.
  • -
    +
    -

    Helpers

    +

    Helpers

    @@ -270,9 +266,15 @@ of the valid options, for --help and such.
    - +

    Regex matchers for option flags.

    +
    +
    LONG_FLAG  = /^(--\w[\w\-]*)/
    +SHORT_FLAG = /^(-\w)$/
    +MULTI_FLAG = /^-(\w{2,})/
    +OPTIONAL   = /\[(\w+(\*?))\]/
    +
  • @@ -282,15 +284,15 @@ of the valid options, for --help and such.
    -

    Regex matchers for option flags. -

    +

    Build and return the list of option rules. If the optional short-flag is +unspecified, leave it out by padding with null.

    -
    LONG_FLAG  = /^(--\w[\w\-]*)/
    -SHORT_FLAG = /^(-\w)$/
    -MULTI_FLAG = /^-(\w{2,})/
    -OPTIONAL   = /\[(\w+(\*?))\]/
    +
    buildRules = (rules) ->
    +  for tuple in rules
    +    tuple.unshift null if tuple.length < 3
    +    buildRule tuple...
    @@ -301,16 +303,22 @@ OPTIONAL = /\[(\w+(\*?))\]/
    -

    Build and return the list of option rules. If the optional short-flag is -unspecified, leave it out by padding with null. -

    +

    Build a rule from a -o short flag, a --output [DIR] long flag, and the +description of what the option does.

    -
    buildRules = (rules) ->
    -  for tuple in rules
    -    tuple.unshift null if tuple.length < 3
    -    buildRule tuple...
    +
    buildRule = (shortFlag, longFlag, description, options = {}) ->
    +  match     = longFlag.match(OPTIONAL)
    +  longFlag  = longFlag.match(LONG_FLAG)[1]
    +  {
    +    name:         longFlag.substr 2
    +    shortFlag:    shortFlag
    +    longFlag:     longFlag
    +    description:  description
    +    hasArgument:  !!(match and match[1])
    +    isList:       !!(match and match[2])
    +  }
    @@ -321,46 +329,18 @@ unspecified, leave it out by padding with null.
    -

    Build a rule from a -o short flag, a --output [DIR] long flag, and the -description of what the option does. -

    - - - -
    buildRule = (shortFlag, longFlag, description, options = {}) ->
    -  match     = longFlag.match(OPTIONAL)
    -  longFlag  = longFlag.match(LONG_FLAG)[1]
    -  {
    -    name:         longFlag.substr 2
    -    shortFlag:    shortFlag
    -    longFlag:     longFlag
    -    description:  description
    -    hasArgument:  !!(match and match[1])
    -    isList:       !!(match and match[2])
    -  }
    - - - - -
  • -
    - -
    - -

    Normalize arguments by expanding merged flags into multiple flags. This allows -you to have -wl be the same as --watch --lint. -

    +you to have -wl be the same as --watch --lint.

    -
    normalizeArguments = (args) ->
    +            
    normalizeArguments = (args) ->
       args = args[..]
       result = []
    -  for arg in args
    -    if match = arg.match MULTI_FLAG
    -      result.push '-' + l for l in match[1].split ''
    -    else
    +  for arg in args
    +    if match = arg.match MULTI_FLAG
    +      result.push '-' + l for l in match[1].split ''
    +    else
           result.push arg
       result
    diff --git a/documentation/docs/register.html b/documentation/docs/register.html new file mode 100644 index 00000000..117c11f5 --- /dev/null +++ b/documentation/docs/register.html @@ -0,0 +1,251 @@ + + + + + register.coffee + + + + + +
    +
    + + + +
      + +
    • +
      +

      register.coffee

      +
      +
    • + + + +
    • +
      + +
      + +
      + +
      + +
      CoffeeScript  = require './coffee-script'
      +child_process = require 'child_process'
      +helpers       = require './helpers'
      +path          = require 'path'
      + +
    • + + +
    • +
      + +
      + +
      +

      Load and run a CoffeeScript file for Node, stripping any BOMs.

      + +
      + +
      loadFile = (module, filename) ->
      +  answer = CoffeeScript._compileFile filename, false
      +  module._compile answer, filename
      + +
    • + + +
    • +
      + +
      + +
      +

      If the installed version of Node supports require.extensions, register +CoffeeScript as an extension.

      + +
      + +
      if require.extensions
      +  for ext in CoffeeScript.FILE_EXTENSIONS
      +    require.extensions[ext] = loadFile
      + +
    • + + +
    • +
      + +
      + +
      +

      Patch Node's module loader to be able to handle mult-dot extensions. +This is a horrible thing that should not be required. Perhaps, one day, +when a truly benevolent dictator comes to rule over the Republik of Node, +it won't be.

      + +
      + +
        Module = require 'module'
      +
      +  findExtension = (filename) ->
      +    extensions = path.basename(filename).split '.'
      + +
    • + + +
    • +
      + +
      + +
      +

      Remove the initial dot from dotfiles.

      + +
      + +
          extensions.shift() if extensions[0] is ''
      + +
    • + + +
    • +
      + +
      + +
      +

      Start with the longest possible extension and work our way shortwards.

      + +
      + +
          while extensions.shift()
      +      curExtension = '.' + extensions.join '.'
      +      return curExtension if Module._extensions[curExtension]
      +    '.js'
      +
      +  Module::load = (filename) ->
      +    @filename = filename
      +    @paths = Module._nodeModulePaths path.dirname filename
      +    extension = findExtension filename
      +    Module._extensions[extension](this, filename)
      +    @loaded = true
      + +
    • + + +
    • +
      + +
      + +
      +

      If we're on Node, patch child_process.fork so that Coffee scripts are able +to fork both CoffeeScript files, and JavaScript files, directly.

      + +
      + +
      if child_process
      +  {fork} = child_process
      +  binary = require.resolve '../../bin/coffee'
      +  child_process.fork = (path, args, options) ->
      +    if helpers.isCoffee path
      +      unless Array.isArray args
      +        options = args or {}
      +        args = []
      +      args = [path].concat args
      +      path = binary
      +    fork path, args, options
      + +
    • + +
    +
    + + diff --git a/documentation/docs/repl.html b/documentation/docs/repl.html index b3f227b1..38506bbf 100644 --- a/documentation/docs/repl.html +++ b/documentation/docs/repl.html @@ -69,6 +69,11 @@ + + register.coffee + + + repl.coffee @@ -111,18 +116,18 @@
    -
    fs = require 'fs'
    -path = require 'path'
    -vm = require 'vm'
    -nodeREPL = require 'repl'
    -CoffeeScript = require './coffee-script'
    -{merge, prettyErrorMessage} = require './helpers'
    +            
    fs = require 'fs'
    +path = require 'path'
    +vm = require 'vm'
    +nodeREPL = require 'repl'
    +CoffeeScript = require './coffee-script'
    +{merge, updateSyntaxError} = require './helpers'
     
     replDefaults =
    -  prompt: 'coffee> ',
    -  historyFile: path.join process.env.HOME, '.coffee_history' if process.env.HOME
    -  historyMaxInputSize: 10240
    -  eval: (input, context, filename, cb) ->
    + prompt: 'coffee> ', + historyFile: path.join process.env.HOME, '.coffee_history' if process.env.HOME + historyMaxInputSize: 10240 + eval: (input, context, filename, cb) ->
  • @@ -133,12 +138,11 @@ replDefaults =
    -

    XXX: multiline hack. -

    +

    XXX: multiline hack.

    -
        input = input.replace /\uFF00/g, '\n'
    +
        input = input.replace /\uFF00/g, '\n'
    @@ -150,12 +154,11 @@ replDefaults =

    Node's REPL sends the input ending with a newline and then wrapped in -parens. Unwrap all that. -

    +parens. Unwrap all that.

    -
        input = input.replace /^\(([\s\S]*)\n\)$/m, '$1'
    +
        input = input.replace /^\(([\s\S]*)\n\)$/m, '$1'
    @@ -166,14 +169,13 @@ parens. Unwrap all that.
    -

    Require AST nodes to do some AST manipulation. -

    +

    Require AST nodes to do some AST manipulation.

    -
        {Block, Assign, Value, Literal} = require './nodes'
    +            
        {Block, Assign, Value, Literal} = require './nodes'
     
    -    try
    + try
    @@ -184,8 +186,7 @@ parens. Unwrap all that.
    -

    Generate the AST of the clean input. -

    +

    Generate the AST of the clean input.

    @@ -200,27 +201,20 @@ parens. Unwrap all that.
    -

    Add assignment to _ variable to force the input to be an expression. -

    +

    Add assignment to _ variable to force the input to be an expression.

    -
          ast = new Block [
    -        new Assign (new Value new Literal '_'), ast, '='
    +            
          ast = new Block [
    +        new Assign (new Value new Literal '_'), ast, '='
           ]
    -      js = ast.compile bare: yes, locals: Object.keys(context)
    -      cb null, vm.runInContext(js, context, filename)
    -    catch err
    -      cb prettyErrorMessage(err, filename, input, yes)
    -
    -addMultilineHandler = (repl) ->
    -  {rli, inputStream, outputStream} = repl
    -
    -  multiline =
    -    enabled: off
    -    initialPrompt: repl.prompt.replace /^[^> ]*/, (x) -> x.replace /./g, '-'
    -    prompt: repl.prompt.replace /^[^> ]*>?/, (x) -> x.replace /./g, '.'
    -    buffer: ''
    + js = ast.compile bare: yes, locals: Object.keys(context) + result = if context is global + vm.runInThisContext js, filename + else + vm.runInContext js, context, filename + cb null, result + catch err
    @@ -231,21 +225,21 @@ parens. Unwrap all that.
    -

    Proxy node's line listener -

    +

    AST's compile does not add source code information to syntax errors.

    -
      nodeLineListener = rli.listeners('line')[0]
    -  rli.removeListener 'line', nodeLineListener
    -  rli.on 'line', (cmd) ->
    -    if multiline.enabled
    -      multiline.buffer += "#{cmd}\n"
    -      rli.setPrompt multiline.prompt
    -      rli.prompt true
    -    else
    -      nodeLineListener cmd
    -    return
    +
          updateSyntaxError err, input
    +      cb err
    +
    +addMultilineHandler = (repl) ->
    +  {rli, inputStream, outputStream} = repl
    +
    +  multiline =
    +    enabled: off
    +    initialPrompt: repl.prompt.replace /^[^> ]*/, (x) -> x.replace /./g, '-'
    +    prompt: repl.prompt.replace /^[^> ]*>?/, (x) -> x.replace /./g, '.'
    +    buffer: ''
    @@ -256,14 +250,20 @@ parens. Unwrap all that.
    -

    Handle Ctrl-v -

    +

    Proxy node's line listener

    -
      inputStream.on 'keypress', (char, key) ->
    -    return unless key and key.ctrl and not key.meta and not key.shift and key.name is 'v'
    -    if multiline.enabled
    +
      nodeLineListener = rli.listeners('line')[0]
    +  rli.removeListener 'line', nodeLineListener
    +  rli.on 'line', (cmd) ->
    +    if multiline.enabled
    +      multiline.buffer += "#{cmd}\n"
    +      rli.setPrompt multiline.prompt
    +      rli.prompt true
    +    else
    +      nodeLineListener cmd
    +    return
    @@ -274,16 +274,13 @@ parens. Unwrap all that.
    -

    allow arbitrarily switching between modes any time before multiple lines are entered -

    +

    Handle Ctrl-v

    -
          unless multiline.buffer.match /\n/
    -        multiline.enabled = not multiline.enabled
    -        rli.setPrompt repl.prompt
    -        rli.prompt true
    -        return
    +
      inputStream.on 'keypress', (char, key) ->
    +    return unless key and key.ctrl and not key.meta and not key.shift and key.name is 'v'
    +    if multiline.enabled
    @@ -294,12 +291,15 @@ parens. Unwrap all that.
    -

    no-op unless the current line is empty -

    +

    allow arbitrarily switching between modes any time before multiple lines are entered

    -
          return if rli.line? and not rli.line.match /^\s*$/
    +
          unless multiline.buffer.match /\n/
    +        multiline.enabled = not multiline.enabled
    +        rli.setPrompt repl.prompt
    +        rli.prompt true
    +        return
    @@ -310,16 +310,11 @@ parens. Unwrap all that.
    -

    eval, print, loop -

    +

    no-op unless the current line is empty

    -
          multiline.enabled = not multiline.enabled
    -      rli.line = ''
    -      rli.cursor = 0
    -      rli.output.cursorTo 0
    -      rli.output.clearLine 1
    +
          return if rli.line? and not rli.line.match /^\s*$/
    @@ -330,19 +325,15 @@ parens. Unwrap all that.
    -

    XXX: multiline hack -

    +

    eval, print, loop

    -
          multiline.buffer = multiline.buffer.replace /\n/g, '\uFF00'
    -      rli.emit 'line', multiline.buffer
    -      multiline.buffer = ''
    -    else
    -      multiline.enabled = not multiline.enabled
    -      rli.setPrompt multiline.initialPrompt
    -      rli.prompt true
    -    return
    +
          multiline.enabled = not multiline.enabled
    +      rli.line = ''
    +      rli.cursor = 0
    +      rli.output.cursorTo 0
    +      rli.output.clearLine 1
    @@ -353,14 +344,18 @@ parens. Unwrap all that.
    -

    Store and load command history from a file -

    +

    XXX: multiline hack

    -
    addHistory = (repl, filename, maxSize) ->
    -  lastLine = null
    -  try
    +
          multiline.buffer = multiline.buffer.replace /\n/g, '\uFF00'
    +      rli.emit 'line', multiline.buffer
    +      multiline.buffer = ''
    +    else
    +      multiline.enabled = not multiline.enabled
    +      rli.setPrompt multiline.initialPrompt
    +      rli.prompt true
    +    return
    @@ -371,13 +366,13 @@ parens. Unwrap all that.
    -

    Get file info and at most maxSize of command history -

    +

    Store and load command history from a file

    -
        stat = fs.statSync filename
    -    size = Math.min maxSize, stat.size
    +
    addHistory = (repl, filename, maxSize) ->
    +  lastLine = null
    +  try
    @@ -388,14 +383,12 @@ parens. Unwrap all that.
    -

    Read last size bytes from the file -

    +

    Get file info and at most maxSize of command history

    -
        readFd = fs.openSync filename, 'r'
    -    buffer = new Buffer(size)
    -    fs.readSync readFd, buffer, 0, size, stat.size - size
    +
        stat = fs.statSync filename
    +    size = Math.min maxSize, stat.size
    @@ -406,12 +399,13 @@ parens. Unwrap all that.
    -

    Set the history on the interpreter -

    +

    Read last size bytes from the file

    -
        repl.rli.history = buffer.toString().split('\n').reverse()
    +
        readFd = fs.openSync filename, 'r'
    +    buffer = new Buffer(size)
    +    fs.readSync readFd, buffer, 0, size, stat.size - size
    @@ -422,12 +416,11 @@ parens. Unwrap all that.
    -

    If the history file was truncated we should pop off a potential partial line -

    +

    Set the history on the interpreter

    -
        repl.rli.history.pop() if stat.size > maxSize
    +
        repl.rli.history = buffer.toString().split('\n').reverse()
    @@ -438,19 +431,11 @@ parens. Unwrap all that.
    -

    Shift off the final blank newline -

    +

    If the history file was truncated we should pop off a potential partial line

    -
        repl.rli.history.shift() if repl.rli.history[0] is ''
    -    repl.rli.historyIndex = -1
    -    lastLine = repl.rli.history[0]
    -
    -  fd = fs.openSync filename, 'a'
    -
    -  repl.rli.addListener 'line', (code) ->
    -    if code and code.length and code isnt '.history' and lastLine isnt code
    +
        repl.rli.history.pop() if stat.size > maxSize
    @@ -461,15 +446,18 @@ parens. Unwrap all that.
    -

    Save the latest command in the file -

    +

    Shift off the final blank newline

    -
          fs.write fd, "#{code}\n"
    -      lastLine = code
    +            
        repl.rli.history.shift() if repl.rli.history[0] is ''
    +    repl.rli.historyIndex = -1
    +    lastLine = repl.rli.history[0]
     
    -  repl.rli.on 'exit', -> fs.close fd
    + fd = fs.openSync filename, 'a' + + repl.rli.addListener 'line', (code) -> + if code and code.length and code isnt '.history' and lastLine isnt code
    @@ -480,30 +468,49 @@ parens. Unwrap all that.
    -

    Add a command to show the history stack -

    +

    Save the latest command in the file

    -
      repl.commands['.history'] =
    -    help: 'Show command history'
    -    action: ->
    -      repl.outputStream.write "#{repl.rli.history[..].reverse().join '\n'}\n"
    +            
          fs.write fd, "#{code}\n"
    +      lastLine = code
    +
    +  repl.rli.on 'exit', -> fs.close fd
    + + + + +
  • +
    + +
    + +
    +

    Add a command to show the history stack

    + +
    + +
      repl.commands['.history'] =
    +    help: 'Show command history'
    +    action: ->
    +      repl.outputStream.write "#{repl.rli.history[..].reverse().join '\n'}\n"
           repl.displayPrompt()
     
    -module.exports =
    -  start: (opts = {}) ->
    -    [major, minor, build] = process.versions.node.split('.').map (n) -> parseInt(n)
    +module.exports =
    +  start: (opts = {}) ->
    +    [major, minor, build] = process.versions.node.split('.').map (n) -> parseInt(n)
     
    -    if major is 0 and minor < 8
    -      console.warn "Node 0.8.0+ required for CoffeeScript REPL"
    -      process.exit 1
    +    if major is 0 and minor < 8
    +      console.warn "Node 0.8.0+ required for CoffeeScript REPL"
    +      process.exit 1
     
    +    CoffeeScript.register()
    +    process.argv = ['coffee'].concat process.argv[2..]
         opts = merge replDefaults, opts
         repl = nodeREPL.start opts
    -    repl.on 'exit', -> repl.outputStream.write '\n'
    +    repl.on 'exit', -> repl.outputStream.write '\n'
         addMultilineHandler repl
    -    addHistory repl, opts.historyFile, opts.historyMaxInputSize if opts.historyFile
    +    addHistory repl, opts.historyFile, opts.historyMaxInputSize if opts.historyFile
         repl
  • diff --git a/documentation/docs/rewriter.html b/documentation/docs/rewriter.html index 79ad3e21..014c8b45 100644 --- a/documentation/docs/rewriter.html +++ b/documentation/docs/rewriter.html @@ -69,6 +69,11 @@ + + register.coffee + + + repl.coffee @@ -113,11 +118,17 @@ and shorthand syntax. This can greatly complicate a grammar and bloat the resulting parse table. Instead of making the parser handle it all, we take a series of passes over the token stream, using this Rewriter to convert shorthand into the unambiguous long form, add implicit indentation and -parentheses, and generally clean things up. -

    +parentheses, and generally clean things up.

    +

    Create a generated token: one that exists due to a use of implicit syntax.

    +
    generate = (tag, value, origin) ->
    +  tok = [tag, value]
    +  tok.generated = yes
    +  tok.origin = origin if origin
    +  tok
    + @@ -127,15 +138,12 @@ parentheses, and generally clean things up.
    -

    Create a generated token: one that exists due to a use of implicit syntax. -

    +

    The Rewriter class is used by the Lexer, directly against +its internal array of tokens.

    -
    generate = (tag, value) ->
    -    tok = [tag, value]
    -    tok.generated = yes
    -    tok
    +
    class exports.Rewriter
    @@ -146,13 +154,25 @@ parentheses, and generally clean things up.
    -

    The Rewriter class is used by the Lexer, directly against -its internal array of tokens. -

    +

    Helpful snippet for debugging:

    +
    console.log (t[0] + '/' + t[1] for t in @tokens).join ' '
    +

    Rewrite the token stream in multiple passes, one logical filter at +a time. This could certainly be changed into a single pass through the +stream, with a big ol' efficient switch, but it's much nicer to work with +like this. The order of these passes matters -- indentation must be +corrected before implicit parentheses can be wrapped around blocks of code.

    -
    class exports.Rewriter
    +
      rewrite: (@tokens) ->
    +    @removeLeadingNewlines()
    +    @closeOpenCalls()
    +    @closeOpenIndexes()
    +    @normalizeLines()
    +    @tagPostfixConditionals()
    +    @addImplicitBracesAndParens()
    +    @addLocationDataToGeneratedTokens()
    +    @tokens
    @@ -163,13 +183,33 @@ its internal array of tokens.
    -

    Helpful snippet for debugging: - -

    -
    console.log (t[0] + '/' + t[1] for t in @tokens).join ' '
    +

    Rewrite the token stream, looking one token ahead and behind. +Allow the return value of the block to tell us how many tokens to move +forwards (or backwards) in the stream, to make sure we don't miss anything +as tokens are inserted and removed, and the stream changes length under +our feet.

    +
      scanTokens: (block) ->
    +    {tokens} = this
    +    i = 0
    +    i += block.call this, token, i, tokens while token = tokens[i]
    +    true
    +
    +  detectEnd: (i, condition, action) ->
    +    {tokens} = this
    +    levels = 0
    +    while token = tokens[i]
    +      return action.call this, token, i     if levels is 0 and condition.call this, token, i
    +      return action.call this, token, i - 1 if not token or levels < 0
    +      if token[0] in EXPRESSION_START
    +        levels += 1
    +      else if token[0] in EXPRESSION_END
    +        levels -= 1
    +      i += 1
    +    i - 1
    + @@ -179,25 +219,14 @@ its internal array of tokens.
    -

    Rewrite the token stream in multiple passes, one logical filter at -a time. This could certainly be changed into a single pass through the -stream, with a big ol' efficient switch, but it's much nicer to work with -like this. The order of these passes matters -- indentation must be -corrected before implicit parentheses can be wrapped around blocks of code. -

    +

    Leading newlines would introduce an ambiguity in the grammar, so we +dispatch them here.

    -
      rewrite: (@tokens) ->
    -    @removeLeadingNewlines()
    -    @removeMidExpressionNewlines()
    -    @closeOpenCalls()
    -    @closeOpenIndexes()
    -    @addImplicitIndentation()
    -    @tagPostfixConditionals()
    -    @addImplicitBracesAndParens()
    -    @addLocationDataToGeneratedTokens()
    -    @tokens
    +
      removeLeadingNewlines: ->
    +    break for [tag], i in @tokens when tag isnt 'TERMINATOR'
    +    @tokens.splice 0, i if i
    @@ -208,33 +237,23 @@ corrected before implicit parentheses can be wrapped around blocks of code.
    -

    Rewrite the token stream, looking one token ahead and behind. -Allow the return value of the block to tell us how many tokens to move -forwards (or backwards) in the stream, to make sure we don't miss anything -as tokens are inserted and removed, and the stream changes length under -our feet. -

    +

    The lexer has tagged the opening parenthesis of a method call. Match it with +its paired close. We have the mis-nested outdent case included here for +calls that close on the same line, just before their outdent.

    -
      scanTokens: (block) ->
    -    {tokens} = this
    -    i = 0
    -    i += block.call this, token, i, tokens while token = tokens[i]
    -    true
    +            
      closeOpenCalls: ->
    +    condition = (token, i) ->
    +      token[0] in [')', 'CALL_END'] or
    +      token[0] is 'OUTDENT' and @tag(i - 1) is ')'
     
    -  detectEnd: (i, condition, action) ->
    -    {tokens} = this
    -    levels = 0
    -    while token = tokens[i]
    -      return action.call this, token, i     if levels is 0 and condition.call this, token, i
    -      return action.call this, token, i - 1 if not token or levels < 0
    -      if token[0] in EXPRESSION_START
    -        levels += 1
    -      else if token[0] in EXPRESSION_END
    -        levels -= 1
    -      i += 1
    -    i - 1
    + action = (token, i) -> + @tokens[if token[0] is 'OUTDENT' then i - 1 else i][0] = 'CALL_END' + + @scanTokens (token, i) -> + @detectEnd i + 1, condition, action if token[0] is 'CALL_START' + 1
    @@ -245,15 +264,21 @@ our feet.
    -

    Leading newlines would introduce an ambiguity in the grammar, so we -dispatch them here. -

    +

    The lexer has tagged the opening parenthesis of an indexing operation call. +Match it with its paired close.

    -
      removeLeadingNewlines: ->
    -    break for [tag], i in @tokens when tag isnt 'TERMINATOR'
    -    @tokens.splice 0, i if i
    +
      closeOpenIndexes: ->
    +    condition = (token, i) ->
    +      token[0] in [']', 'INDEX_END']
    +
    +    action = (token, i) ->
    +      token[0] = 'INDEX_END'
    +
    +    @scanTokens (token, i) ->
    +      @detectEnd i + 1, condition, action if token[0] is 'INDEX_START'
    +      1
    @@ -264,17 +289,20 @@ dispatch them here.
    -

    Some blocks occur in the middle of expressions -- when we're expecting -this, remove their trailing newlines. -

    +

    Match tags in token stream starting at i with pattern, skipping HERECOMMENTs +Pattern may consist of strings (equality), an array of strings (one of) +or null (wildcard)

    -
      removeMidExpressionNewlines: ->
    -    @scanTokens (token, i, tokens) ->
    -      return 1 unless token[0] is 'TERMINATOR' and @tag(i + 1) in EXPRESSION_CLOSE
    -      tokens.splice i, 1
    -      0
    +
      matchTags: (i, pattern...) ->
    +    fuzz = 0
    +    for j in [0 ... pattern.length]
    +      fuzz += 2 while @tag(i + j + fuzz) is 'HERECOMMENT'
    +      continue if not pattern[j]?
    +      pattern[j] = [pattern[j]] if typeof pattern[j] is 'string'
    +      return no if @tag(i + j + fuzz) not in pattern[j]
    +    yes
    @@ -285,24 +313,13 @@ this, remove their trailing newlines.
    -

    The lexer has tagged the opening parenthesis of a method call. Match it with -its paired close. We have the mis-nested outdent case included here for -calls that close on the same line, just before their outdent. -

    +

    yes iff standing in front of something looking like +@: or :, skipping over 'HERECOMMENT's

    -
      closeOpenCalls: ->
    -    condition = (token, i) ->
    -      token[0] in [')', 'CALL_END'] or
    -      token[0] is 'OUTDENT' and @tag(i - 1) is ')'
    -
    -    action = (token, i) ->
    -      @tokens[if token[0] is 'OUTDENT' then i - 1 else i][0] = 'CALL_END'
    -
    -    @scanTokens (token, i) ->
    -      @detectEnd i + 1, condition, action if token[0] is 'CALL_START'
    -      1
    +
      looksObjectish: (j) ->
    +    @matchTags(j, '@', null, ':') or @matchTags(j, null, ':')
    @@ -313,22 +330,22 @@ calls that close on the same line, just before their outdent.
    -

    The lexer has tagged the opening parenthesis of an indexing operation call. -Match it with its paired close. -

    +

    yes iff current line of tokens contain an element of tags on same +expression level. Stop searching at LINEBREAKS or explicit start of +containing balanced expression.

    -
      closeOpenIndexes: ->
    -    condition = (token, i) ->
    -      token[0] in [']', 'INDEX_END']
    -
    -    action = (token, i) ->
    -      token[0] = 'INDEX_END'
    -
    -    @scanTokens (token, i) ->
    -      @detectEnd i + 1, condition, action if token[0] is 'INDEX_START'
    -      1
    +
      findTagsBackwards: (i, tags) ->
    +    backStack = []
    +    while i >= 0 and (backStack.length or
    +          @tag(i) not in tags and
    +          (@tag(i) not in EXPRESSION_START or @tokens[i].generated) and
    +          @tag(i) not in LINEBREAKS)
    +      backStack.push @tag(i) if @tag(i) in EXPRESSION_END
    +      backStack.pop() if @tag(i) in EXPRESSION_START and backStack.length
    +      i -= 1
    +    @tag(i) in tags
    @@ -339,21 +356,12 @@ Match it with its paired close.
    -

    Match tags in token stream starting at i with pattern, skipping HERECOMMENTs -Pattern may consist of strings (equality), an array of strings (one of) -or null (wildcard) -

    +

    Look for signs of implicit calls and objects in the token stream and +add them.

    -
      matchTags: (i, pattern...) ->
    -    fuzz = 0
    -    for j in [0 ... pattern.length]
    -      fuzz += 2 while @tag(i + j + fuzz) is 'HERECOMMENT'
    -      continue if not pattern[j]?
    -      pattern[j] = [pattern[j]] if typeof pattern[j] is 'string'
    -      return no if @tag(i + j + fuzz) not in pattern[j]
    -    yes
    +
      addImplicitBracesAndParens: ->
    @@ -364,14 +372,18 @@ or null (wildcard)
    -

    yes iff standing in front of something looking like -@: or :, skipping over 'HERECOMMENT's -

    +

    Track current balancing depth (both implicit and explicit) on stack.

    -
      looksObjectish: (j) ->
    -    @matchTags(j, '@', null, ':') or @matchTags(j, null, ':')
    +
        stack = []
    +
    +    @scanTokens (token, i, tokens) ->
    +      [tag]     = token
    +      [prevTag] = prevToken = if i > 0 then tokens[i - 1] else []
    +      [nextTag] = if i < tokens.length - 1 then tokens[i + 1] else []
    +      stackTop  = -> stack[stack.length - 1]
    +      startIdx  = i
    @@ -382,23 +394,12 @@ or null (wildcard)
    -

    yes iff current line of tokens contain an element of tags on same -expression level. Stop searching at LINEBREAKS or explicit start of -containing balanced expression. -

    +

    Helper function, used for keeping track of the number of tokens consumed +and spliced, when returning for getting a new token.

    -
      findTagsBackwards: (i, tags) ->
    -    backStack = []
    -    while i >= 0 and (backStack.length or
    -          @tag(i) not in tags and
    -          (@tag(i) not in EXPRESSION_START or @tokens[i].generated) and
    -          @tag(i) not in LINEBREAKS)
    -      backStack.push @tag(i) if @tag(i) in EXPRESSION_END
    -      backStack.pop() if @tag(i) in EXPRESSION_START and backStack.length
    -      i -= 1
    -    @tag(i) in tags
    +
          forward   = (n) -> i - startIdx + n
    @@ -409,13 +410,13 @@ containing balanced expression.
    -

    Look for signs of implicit calls and objects in the token stream and -add them. -

    +

    Helper functions

    -
      addImplicitBracesAndParens: ->
    +
          inImplicit        = -> stackTop()?[2]?.ours
    +      inImplicitCall    = -> inImplicit() and stackTop()?[0] is '('
    +      inImplicitObject  = -> inImplicit() and stackTop()?[0] is '{'
    @@ -426,19 +427,35 @@ add them.
    -

    Track current balancing depth (both implicit and explicit) on stack. -

    +

    Unclosed control statement inside implicit parens (like +class declaration or if-conditionals)

    -
        stack = []
    +            
          inImplicitControl = -> inImplicit and stackTop()?[0] is 'CONTROL'
     
    -    @scanTokens (token, i, tokens) ->
    -      [tag]     = token
    -      [prevTag] = if i > 0 then tokens[i - 1] else []
    -      [nextTag] = if i < tokens.length - 1 then tokens[i + 1] else []
    -      stackTop  = -> stack[stack.length - 1]
    -      startIdx  = i
    + startImplicitCall = (j) -> + idx = j ? i + stack.push ['(', idx, ours: yes] + tokens.splice idx, 0, generate 'CALL_START', '(' + i += 1 if not j? + + endImplicitCall = -> + stack.pop() + tokens.splice i, 0, generate 'CALL_END', ')' + i += 1 + + startImplicitObject = (j, startsLine = yes) -> + idx = j ? i + stack.push ['{', idx, sameLine: yes, startsLine: startsLine, ours: yes] + tokens.splice idx, 0, generate '{', generate(new String('{')), token + i += 1 if not j? + + endImplicitObject = (j) -> + j = j ? i + stack.pop() + tokens.splice j, 0, generate '}', '}', token + i += 1
    @@ -449,13 +466,16 @@ add them.
    -

    Helper function, used for keeping track of the number of tokens consumed -and spliced, when returning for getting a new token. -

    +

    Don't end an implicit call on next indent if any of these are in an argument

    -
          forward   = (n) -> i - startIdx + n
    +
          if inImplicitCall() and tag in ['IF', 'TRY', 'FINALLY', 'CATCH',
    +        'CLASS', 'SWITCH']
    +        stack.push ['CONTROL', i, ours: true]
    +        return forward(1)
    +
    +      if tag is 'INDENT' and inImplicit()
    @@ -466,14 +486,19 @@ and spliced, when returning for getting a new token.
    -

    Helper functions -

    +

    An INDENT closes an implicit call unless

    +
      +
    1. We have seen a CONTROL argument on the line.
    2. +
    3. The last token before the indent is part of the list below
    4. +
    -
          inImplicit        = -> stackTop()?[2]?.ours
    -      inImplicitCall    = -> inImplicit() and stackTop()?[0] is '('
    -      inImplicitObject  = -> inImplicit() and stackTop()?[0] is '{'
    +
            if prevTag not in ['=>', '->', '[', '(', ',', '{', 'TRY', 'ELSE', '=']
    +          endImplicitCall() while inImplicitCall()
    +        stack.pop() if inImplicitControl()
    +        stack.push [tag, i]
    +        return forward(1)
    @@ -484,36 +509,13 @@ and spliced, when returning for getting a new token.
    -

    Unclosed control statement inside implicit parens (like -class declaration or if-conditionals) -

    +

    Straightforward start of explicit expression

    -
          inImplicitControl = -> inImplicit and stackTop()?[0] is 'CONTROL'
    -
    -      startImplicitCall = (j) ->
    -        idx = j ? i
    -        stack.push ['(', idx, ours: yes]
    -        tokens.splice idx, 0, generate 'CALL_START', '('
    -        i += 1 if not j?
    -
    -      endImplicitCall = ->
    -        stack.pop()
    -        tokens.splice i, 0, generate 'CALL_END', ')'
    -        i += 1
    -
    -      startImplicitObject = (j, startsLine = yes) ->
    -        idx = j ? i
    -        stack.push ['{', idx, sameLine: yes, startsLine: startsLine, ours: yes]
    -        tokens.splice idx, 0, generate '{', generate(new String('{'))
    -        i += 1 if not j?
    -
    -      endImplicitObject = (j) ->
    -        j = j ? i
    -        stack.pop()
    -        tokens.splice j, 0, generate '}', '}'
    -        i += 1
    +
          if tag in EXPRESSION_START
    +        stack.push [tag, i]
    +        return forward(1)
    @@ -524,17 +526,19 @@ class declaration or if-conditionals)
    -

    Don't end an implicit call on next indent if any of these are in an argument -

    +

    Close all implicit expressions inside of explicitly closed expressions.

    -
          if inImplicitCall() and tag in ['IF', 'TRY', 'FINALLY', 'CATCH',
    -        'CLASS', 'SWITCH']
    -        stack.push ['CONTROL', i, ours: true]
    -        return forward(1)
    -
    -      if tag is 'INDENT' and inImplicit()
    +
          if tag in EXPRESSION_END
    +        while inImplicit()
    +          if inImplicitCall()
    +            endImplicitCall()
    +          else if inImplicitObject()
    +            endImplicitObject()
    +          else
    +            stack.pop()
    +        stack.pop()
    @@ -545,21 +549,19 @@ class declaration or if-conditionals)
    -

    An INDENT closes an implicit call unless - -

    -
      -
    1. We have seen a CONTROL argument on the line.
    2. -
    3. The last token before the indent is part of the list below
    4. -
    +

    Recognize standard implicit calls like +f a, f() b, f? c, h[0] d etc.

    -
            if prevTag not in ['=>', '->', '[', '(', ',', '{', 'TRY', 'ELSE', '=']
    -          endImplicitCall() while inImplicitCall()
    -        stack.pop() if inImplicitControl()
    -        stack.push [tag, i]
    -        return forward(1)
    +
          if (tag in IMPLICIT_FUNC and token.spaced and not token.stringEnd or
    +          tag is '?' and i > 0 and not tokens[i - 1].spaced) and
    +         (nextTag in IMPLICIT_CALL or
    +          nextTag in IMPLICIT_UNSPACED_CALL and
    +          not tokens[i + 1]?.spaced and not tokens[i + 1]?.newLine)
    +        tag = token[0] = 'FUNC_EXIST' if tag is '?'
    +        startImplicitCall i + 1
    +        return forward(2)
    @@ -570,14 +572,33 @@ class declaration or if-conditionals)
    -

    Straightforward start of explicit expression -

    +

    Implicit call taking an implicit indented object as first argument.

    +
    f
    +  a: b
    +  c: d
    +

    and

    +
    f
    +  1
    +  a: b
    +  b: c
    +

    Don't accept implicit calls of this type, when on the same line +as the control strucutures below as that may misinterpret constructs like:

    +
    if f
    +   a: 1
    +

    as

    +
    if f(a: 1)
    +

    which is probably always unintended. +Furthermore don't allow this in literal arrays, as +that creates grammatical ambiguities.

    -
          if tag in EXPRESSION_START
    -        stack.push [tag, i]
    -        return forward(1)
    +
          if tag in IMPLICIT_FUNC and @matchTags(i + 1, 'INDENT', null, ':') and
    +         not @findTagsBackwards(i, ['CLASS', 'EXTENDS', 'IF', 'CATCH',
    +          'SWITCH', 'LEADING_WHEN', 'FOR', 'WHILE', 'UNTIL'])
    +        startImplicitCall i + 1
    +        stack.push ['INDENT', i + 2]
    +        return forward(3)
    @@ -588,20 +609,11 @@ class declaration or if-conditionals)
    -

    Close all implicit expressions inside of explicitly closed expressions. -

    +

    Implicit objects start here

    -
          if tag in EXPRESSION_END
    -        while inImplicit()
    -          if inImplicitCall()
    -            endImplicitCall()
    -          else if inImplicitObject()
    -            endImplicitObject()
    -          else
    -            stack.pop()
    -        stack.pop()
    +
          if tag is ':'
    @@ -612,20 +624,12 @@ class declaration or if-conditionals)
    -

    Recognize standard implicit calls like -f a, f() b, f? c, h[0] d etc. -

    +

    Go back to the (implicit) start of the object

    -
          if (tag in IMPLICIT_FUNC and token.spaced and not token.stringEnd or
    -          tag is '?' and i > 0 and not tokens[i - 1].spaced) and
    -         (nextTag in IMPLICIT_CALL or
    -          nextTag in IMPLICIT_UNSPACED_CALL and
    -          not tokens[i + 1]?.spaced and not tokens[i + 1]?.newLine)
    -        tag = token[0] = 'FUNC_EXIST' if tag is '?'
    -        startImplicitCall i + 1
    -        return forward(2)
    +
            if @tag(i - 2) is '@' then s = i - 2 else s = i - 1
    +        s -= 2 while @tag(s - 2) is 'HERECOMMENT'
    @@ -636,42 +640,13 @@ f a, f() b, f? c, h[0] d etc.
    -

    Implicit call taking an implicit indented object as first argument. - -

    -
    f
    -  a: b
    -  c: d
    -

    and - -

    -
    f
    -  1
    -  a: b
    -  b: c
    -

    Don't accept implicit calls of this type, when on the same line -as the control strucutures below as that may misinterpret constructs like: - -

    -
    if f
    -   a: 1
    -

    as - -

    -
    if f(a: 1)
    -

    which is probably always unintended. -Furthermore don't allow this in literal arrays, as -that creates grammatical ambiguities. -

    +

    Mark if the value is a for loop

    -
          if tag in IMPLICIT_FUNC and @matchTags(i + 1, 'INDENT', null, ':') and
    -         not @findTagsBackwards(i, ['CLASS', 'EXTENDS', 'IF', 'CATCH',
    -          'SWITCH', 'LEADING_WHEN', 'FOR', 'WHILE', 'UNTIL'])
    -        startImplicitCall i + 1
    -        stack.push ['INDENT', i + 2]
    -        return forward(3)
    +
            @insideForDeclaration = nextTag is 'FOR'
    +
    +        startsLine = s is 0 or @tag(s - 1) in LINEBREAKS or tokens[s - 1].newLine
    @@ -682,12 +657,18 @@ that creates grammatical ambiguities.
    -

    Implicit objects start here -

    +

    Are we just continuing an already declared object?

    -
          if tag is ':'
    +
            if stackTop()
    +          [stackTag, stackIdx] = stackTop()
    +          if (stackTag is '{' or stackTag is 'INDENT' and @tag(stackIdx - 1) is '{') and
    +             (startsLine or @tag(s - 1) is ',' or @tag(s - 1) is '{')
    +            return forward(1)
    +
    +        startImplicitObject(s, !!startsLine)
    +        return forward(2)
    @@ -698,15 +679,26 @@ that creates grammatical ambiguities.
    -

    Go back to the (implicit) start of the object -

    - +

    End implicit calls when chaining method calls +like e.g.:

    +
    f ->
    +  a
    +.g b, ->
    +  c
    +.h a
    +

    and also

    +
    f a
    +.g b
    +.h a
    +
    -
            if @tag(i - 2) is '@' then s = i - 2 else s = i - 1
    -        s -= 2 while @tag(s - 2) is 'HERECOMMENT'
    +            
          stackTop()[2].sameLine = no if inImplicitObject() and tag in LINEBREAKS
     
    -        startsLine = s is 0 or @tag(s - 1) in LINEBREAKS or tokens[s - 1].newLine
    + newLine = prevTag is 'OUTDENT' or prevToken.newLine + if tag in IMPLICIT_END or tag in CALL_CLOSERS and newLine + while inImplicit() + [stackTag, stackIdx, {sameLine, startsLine}] = stackTop()
    @@ -717,19 +709,12 @@ that creates grammatical ambiguities.
    -

    Are we just continuing an already declared object? -

    +

    Close implicit calls when reached end of argument list

    -
            if stackTop()
    -          [stackTag, stackIdx] = stackTop()
    -          if (stackTag is '{' or stackTag is 'INDENT' and @tag(stackIdx - 1) is '{') and
    -             (startsLine or @tag(s - 1) is ',' or @tag(s - 1) is '{')
    -            return forward(1)
    -
    -        startImplicitObject(s, !!startsLine)
    -        return forward(2)
    +
              if inImplicitCall() and prevTag isnt ','
    +            endImplicitCall()
    @@ -740,27 +725,14 @@ that creates grammatical ambiguities.
    -

    End implicit calls when chaining method calls -like e.g.: - -

    -
    f ->
    -  a
    -.g b, ->
    -  c
    -.h a
    +

    Close implicit objects such as: +return a: 1, b: 2 unless true

    -
          if prevTag is 'OUTDENT' and inImplicitCall() and tag in ['.', '?.', '::', '?::']
    -        endImplicitCall()
    -        return forward(1)
    -
    -      stackTop()[2].sameLine = no if inImplicitObject() and tag in LINEBREAKS
    -
    -      if tag in IMPLICIT_END
    -        while inImplicit()
    -          [stackTag, stackIdx, {sameLine, startsLine}] = stackTop()
    +
              else if inImplicitObject() and not @insideForDeclaration and sameLine and
    +                  tag isnt 'TERMINATOR' and prevTag isnt ':' and
    +            endImplicitObject()
    @@ -771,13 +743,17 @@ like e.g.:
    -

    Close implicit calls when reached end of argument list -

    +

    Close implicit objects when at end of line, line didn't end with a comma +and the implicit object didn't start the line or the next line doesn't look like +the continuation of an object.

    -
              if inImplicitCall() and prevTag isnt ','
    -            endImplicitCall()
    +
              else if inImplicitObject() and tag is 'TERMINATOR' and prevTag isnt ',' and
    +                  not (startsLine and @looksObjectish(i + 1))
    +            endImplicitObject()
    +          else
    +            break
    @@ -788,14 +764,21 @@ like e.g.:
    -

    Close implicit objects such as: -return a: 1, b: 2 unless true -

    - +

    Close implicit object if comma is the last character +and what comes after doesn't look like it belongs. +This is used for trailing commas and calls, like:

    +
    x =
    +    a: b,
    +    c: d,
    +e = 2
    +

    and

    +
    f a, b: c, d: e, f, g: h: i, j
    +
    -
              else if inImplicitObject() and sameLine and not startsLine
    -            endImplicitObject()
    +
          if tag is ',' and not @looksObjectish(i + 1) and inImplicitObject() and
    +         not @insideForDeclaration and
    +         (nextTag isnt 'TERMINATOR' or not @looksObjectish(i + 2))
    @@ -806,18 +789,17 @@ return a: 1, b: 2 unless true
    -

    Close implicit objects when at end of line, line didn't end with a comma -and the implicit object didn't start the line or the next line doesn't look like -the continuation of an object. -

    +

    When nextTag is OUTDENT the comma is insignificant and +should just be ignored so embed it in the implicit object.

    +

    When it isn't the comma go on to play a role in a call or +array further up the stack, so give it a chance.

    -
              else if inImplicitObject() and tag is 'TERMINATOR' and prevTag isnt ',' and
    -                  not (startsLine and @looksObjectish(i + 1))
    -            endImplicitObject()
    -          else
    -            break
    +
            offset = if nextTag is 'OUTDENT' then 1 else 0
    +        while inImplicitObject()
    +          endImplicitObject i + offset
    +      return forward(1)
    @@ -828,24 +810,26 @@ the continuation of an object.
    -

    Close implicit object if comma is the last character -and what comes after doesn't look like it belongs. -This is used for trailing commas and calls, like: - -

    -
    x =
    -    a: b,
    -    c: d,
    -e = 2
    -

    and - -

    -
    f a, b: c, d: e, f, g: h: i, j
    +

    Add location data to all tokens generated by the rewriter.

    -
          if tag is ',' and not @looksObjectish(i + 1) and inImplicitObject() and
    -         (nextTag isnt 'TERMINATOR' or not @looksObjectish(i + 2))
    +
      addLocationDataToGeneratedTokens: ->
    +    @scanTokens (token, i, tokens) ->
    +      return 1 if     token[2]
    +      return 1 unless token.generated or token.explicit
    +      if token[0] is '{' and nextLocation=tokens[i + 1]?[2]
    +        {first_line: line, first_column: column} = nextLocation
    +      else if prevLocation = tokens[i - 1]?[2]
    +        {last_line: line, last_column: column} = prevLocation
    +      else
    +        line = column = 0
    +      token[2] =
    +        first_line:   line
    +        first_column: column
    +        last_line:    line
    +        last_column:  column
    +      return 1
    @@ -856,21 +840,50 @@ e = 2
    -

    When nextTag is OUTDENT the comma is insignificant and -should just be ignored so embed it in the implicit object. - -

    -

    When it isn't the comma go on to play a role in a call or -array further up the stack, so give it a chance. -

    +

    Because our grammar is LALR(1), it can't handle some single-line +expressions that lack ending delimiters. The Rewriter adds the implicit +blocks, so it doesn't need to. To keep the grammar clean and tidy, trailing +newlines within expressions are removed and the indentation tokens of empty +blocks are added.

    -
    -        offset = if nextTag is 'OUTDENT' then 1 else 0
    -        while inImplicitObject()
    -          endImplicitObject i + offset
    -      return forward(1)
    +
      normalizeLines: ->
    +    starter = indent = outdent = null
    +
    +    condition = (token, i) ->
    +      token[1] isnt ';' and token[0] in SINGLE_CLOSERS and
    +      not (token[0] is 'TERMINATOR' and @tag(i + 1) in EXPRESSION_CLOSE) and
    +      not (token[0] is 'ELSE' and starter isnt 'THEN') and
    +      not (token[0] in ['CATCH', 'FINALLY'] and starter in ['->', '=>']) or
    +      token[0] in CALL_CLOSERS and @tokens[i - 1].newLine
    +
    +    action = (token, i) ->
    +      @tokens.splice (if @tag(i - 1) is ',' then i - 1 else i), 0, outdent
    +
    +    @scanTokens (token, i, tokens) ->
    +      [tag] = token
    +      if tag is 'TERMINATOR'
    +        if @tag(i + 1) is 'ELSE' and @tag(i - 1) isnt 'OUTDENT'
    +          tokens.splice i, 1, @indentation()...
    +          return 1
    +        if @tag(i + 1) in EXPRESSION_CLOSE
    +          tokens.splice i, 1
    +          return 0
    +      if tag is 'CATCH'
    +        for j in [1..2] when @tag(i + j) in ['OUTDENT', 'TERMINATOR', 'FINALLY']
    +          tokens.splice i + j, 0, @indentation()...
    +          return 2 + j
    +      if tag in SINGLE_LINERS and @tag(i + 1) isnt 'INDENT' and
    +         not (tag is 'ELSE' and @tag(i + 1) is 'IF')
    +        starter = tag
    +        [indent, outdent] = @indentation tokens[i]
    +        indent.fromThen   = true if starter is 'THEN'
    +        tokens.splice i + 1, 0, indent
    +        @detectEnd i + 2, condition, action
    +        tokens.splice i, 1 if tag is 'THEN'
    +        return 1
    +      return 1
    @@ -881,27 +894,29 @@ array further up the stack, so give it a chance.
    -

    Add location data to all tokens generated by the rewriter. -

    +

    Tag postfix conditionals as such, so that we can parse them with a +different precedence.

    -
      addLocationDataToGeneratedTokens: ->
    -    @scanTokens (token, i, tokens) ->
    -      return 1 if     token[2]
    -      return 1 unless token.generated or token.explicit
    -      if token[0] is '{' and nextLocation=tokens[i + 1]?[2]
    -          {first_line: line, first_column: column} = nextLocation
    -      else if prevLocation = tokens[i - 1]?[2]
    -          {last_line: line, last_column: column} = prevLocation
    -      else
    -          line = column = 0
    -      token[2] =
    -        first_line:   line
    -        first_column: column
    -        last_line:    line
    -        last_column:  column
    -      return 1
    +
      tagPostfixConditionals: ->
    +
    +    original = null
    +
    +    condition = (token, i) ->
    +      [tag] = token
    +      [prevTag] = @tokens[i - 1]
    +      tag is 'TERMINATOR' or (tag is 'INDENT' and prevTag not in SINGLE_LINERS)
    +
    +    action = (token, i) ->
    +      if token[0] isnt 'INDENT' or (token.generated and not token.fromThen)
    +        original[0] = 'POST_' + original[0]
    +
    +    @scanTokens (token, i) ->
    +      return 1 unless token[0] is 'IF'
    +      original = token
    +      @detectEnd i + 1, condition, action
    +      return 1
    @@ -912,47 +927,21 @@ array further up the stack, so give it a chance.
    -

    Because our grammar is LALR(1), it can't handle some single-line -expressions that lack ending delimiters. The Rewriter adds the implicit -blocks, so it doesn't need to. ')' can close a single-line block, -but we need to make sure it's balanced. -

    +

    Generate the indentation tokens, based on another token on the same line.

    -
      addImplicitIndentation: ->
    -    starter = indent = outdent = null
    +            
      indentation: (origin) ->
    +    indent  = ['INDENT', 2]
    +    outdent = ['OUTDENT', 2]
    +    if origin
    +      indent.generated = outdent.generated = yes
    +      indent.origin = outdent.origin = origin
    +    else
    +      indent.explicit = outdent.explicit = yes
    +    [indent, outdent]
     
    -    condition = (token, i) ->
    -      token[1] isnt ';' and token[0] in SINGLE_CLOSERS and
    -      not (token[0] is 'ELSE' and starter isnt 'THEN') and
    -      not (token[0] in ['CATCH', 'FINALLY'] and starter in ['->', '=>'])
    -
    -    action = (token, i) ->
    -      @tokens.splice (if @tag(i - 1) is ',' then i - 1 else i), 0, outdent
    -
    -    @scanTokens (token, i, tokens) ->
    -      [tag] = token
    -      if tag is 'TERMINATOR' and @tag(i + 1) is 'THEN'
    -        tokens.splice i, 1
    -        return 0
    -      if tag is 'ELSE' and @tag(i - 1) isnt 'OUTDENT'
    -        tokens.splice i, 0, @indentation()...
    -        return 2
    -      if tag is 'CATCH'
    -        for j in [1..2] when @tag(i + j) in ['OUTDENT', 'TERMINATOR', 'FINALLY']
    -          tokens.splice i + j, 0, @indentation()...
    -          return 2 + j
    -      if tag in SINGLE_LINERS and @tag(i + 1) isnt 'INDENT' and
    -         not (tag is 'ELSE' and @tag(i + 1) is 'IF')
    -        starter = tag
    -        [indent, outdent] = @indentation yes
    -        indent.fromThen   = true if starter is 'THEN'
    -        tokens.splice i + 1, 0, indent
    -        @detectEnd i + 2, condition, action
    -        tokens.splice i, 1 if tag is 'THEN'
    -        return 1
    -      return 1
    + generate: generate
    @@ -963,30 +952,11 @@ but we need to make sure it's balanced.
    -

    Tag postfix conditionals as such, so that we can parse them with a -different precedence. -

    +

    Look up a tag by token index.

    -
      tagPostfixConditionals: ->
    -
    -    original = null
    -
    -    condition = (token, i) ->
    -      [tag] = token
    -      [prevTag] = @tokens[i - 1]
    -      tag is 'TERMINATOR' or (tag is 'INDENT' and prevTag not in SINGLE_LINERS)
    -
    -    action = (token, i) ->
    -      if token[0] isnt 'INDENT' or (token.generated and not token.fromThen)
    -        original[0] = 'POST_' + original[0]
    -
    -    @scanTokens (token, i) ->
    -      return 1 unless token[0] is 'IF'
    -      original = token
    -      @detectEnd i + 1, condition, action
    -      return 1
    +
      tag: (i) -> @tokens[i]?[0]
    @@ -997,20 +967,10 @@ different precedence.
    -

    Generate the indentation tokens, based on another token on the same line. -

    +

    Constants

    -
      indentation: (implicit = no) ->
    -    indent  = ['INDENT', 2]
    -    outdent = ['OUTDENT', 2]
    -    indent.generated = outdent.generated = yes if implicit
    -    indent.explicit = outdent.explicit = yes if not implicit
    -    [indent, outdent]
    -
    -  generate: generate
    - @@ -1020,12 +980,19 @@ different precedence.
    -

    Look up a tag by token index. -

    +

    List of the token pairs that must be balanced.

    -
      tag: (i) -> @tokens[i]?[0]
    +
    BALANCED_PAIRS = [
    +  ['(', ')']
    +  ['[', ']']
    +  ['{', '}']
    +  ['INDENT', 'OUTDENT'],
    +  ['CALL_START', 'CALL_END']
    +  ['PARAM_START', 'PARAM_END']
    +  ['INDEX_START', 'INDEX_END']
    +]
    @@ -1033,13 +1000,16 @@ different precedence.
  • -
    +
    -

    Constants

    +

    The inverse mappings of BALANCED_PAIRS we're trying to fix up, so we can +look things up from either end.

    +
    exports.INVERSES = INVERSES = {}
    +
  • @@ -1049,9 +1019,17 @@ different precedence.
    - +

    The tokens that signal the start/end of a balanced pair.

    + +
    EXPRESSION_START = []
    +EXPRESSION_END   = []
    +
    +for [left, rite] in BALANCED_PAIRS
    +  EXPRESSION_START.push INVERSES[rite] = left
    +  EXPRESSION_END  .push INVERSES[left] = rite
    + @@ -1061,20 +1039,11 @@ different precedence.
    -

    List of the token pairs that must be balanced. -

    +

    Tokens that indicate the close of a clause of an expression.

    -
    BALANCED_PAIRS = [
    -  ['(', ')']
    -  ['[', ']']
    -  ['{', '}']
    -  ['INDENT', 'OUTDENT'],
    -  ['CALL_START', 'CALL_END']
    -  ['PARAM_START', 'PARAM_END']
    -  ['INDEX_START', 'INDEX_END']
    -]
    +
    EXPRESSION_CLOSE = ['CATCH', 'THEN', 'ELSE', 'FINALLY'].concat EXPRESSION_END
    @@ -1085,13 +1054,11 @@ different precedence.
    -

    The inverse mappings of BALANCED_PAIRS we're trying to fix up, so we can -look things up from either end. -

    +

    Tokens that, if followed by an IMPLICIT_CALL, indicate a function invocation.

    -
    exports.INVERSES = INVERSES = {}
    +
    IMPLICIT_FUNC    = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@', 'THIS']
    @@ -1102,17 +1069,17 @@ look things up from either end.
    -

    The tokens that signal the start/end of a balanced pair. -

    +

    If preceded by an IMPLICIT_FUNC, indicates a function invocation.

    -
    EXPRESSION_START = []
    -EXPRESSION_END   = []
    +            
    IMPLICIT_CALL    = [
    +  'IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'CLASS'
    +  'IF', 'TRY', 'SWITCH', 'THIS', 'BOOL', 'NULL', 'UNDEFINED', 'UNARY',
    +  'UNARY_MATH', 'SUPER', 'THROW', '@', '->', '=>', '[', '(', '{', '--', '++'
    +]
     
    -for [left, rite] in BALANCED_PAIRS
    -  EXPRESSION_START.push INVERSES[rite] = left
    -  EXPRESSION_END  .push INVERSES[left] = rite
    +IMPLICIT_UNSPACED_CALL = ['+', '-']
    @@ -1123,12 +1090,12 @@ EXPRESSION_END = []
    -

    Tokens that indicate the close of a clause of an expression. -

    +

    Tokens that always mark the end of an implicit call for single-liners.

    -
    EXPRESSION_CLOSE = ['CATCH', 'WHEN', 'ELSE', 'FINALLY'].concat EXPRESSION_END
    +
    IMPLICIT_END     = ['POST_IF', 'FOR', 'WHILE', 'UNTIL', 'WHEN', 'BY',
    +  'LOOP', 'TERMINATOR']
    @@ -1139,12 +1106,13 @@ EXPRESSION_END = []
    -

    Tokens that, if followed by an IMPLICIT_CALL, indicate a function invocation. -

    +

    Single-line flavors of block expressions that have unclosed endings. +The grammar can't disambiguate them, so we insert the implicit indentation.

    -
    IMPLICIT_FUNC    = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@', 'THIS']
    +
    SINGLE_LINERS    = ['ELSE', '->', '=>', 'TRY', 'FINALLY', 'THEN']
    +SINGLE_CLOSERS   = ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN']
    @@ -1155,18 +1123,11 @@ EXPRESSION_END = []
    -

    If preceded by an IMPLICIT_FUNC, indicates a function invocation. -

    +

    Tokens that end a line.

    -
    IMPLICIT_CALL    = [
    -  'IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'CLASS'
    -  'IF', 'TRY', 'SWITCH', 'THIS', 'BOOL', 'NULL', 'UNDEFINED', 'UNARY', 'SUPER'
    -  'THROW', '@', '->', '=>', '[', '(', '{', '--', '++'
    -]
    -
    -IMPLICIT_UNSPACED_CALL = ['+', '-']
    +
    LINEBREAKS       = ['TERMINATOR', 'INDENT', 'OUTDENT']
    @@ -1177,47 +1138,11 @@ IMPLICIT_UNSPACED_CALL = ['+',
    -

    Tokens that always mark the end of an implicit call for single-liners. -

    +

    Tokens that close open calls when they follow a newline.

    -
    IMPLICIT_END     = ['POST_IF', 'FOR', 'WHILE', 'UNTIL', 'WHEN', 'BY',
    -  'LOOP', 'TERMINATOR']
    - - - - -
  • -
    - -
    - -
    -

    Single-line flavors of block expressions that have unclosed endings. -The grammar can't disambiguate them, so we insert the implicit indentation. -

    - -
    - -
    SINGLE_LINERS    = ['ELSE', '->', '=>', 'TRY', 'FINALLY', 'THEN']
    -SINGLE_CLOSERS   = ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN']
    - -
  • - - -
  • -
    - -
    - -
    -

    Tokens that end a line. -

    - -
    - -
    LINEBREAKS       = ['TERMINATOR', 'INDENT', 'OUTDENT']
    +
    CALL_CLOSERS     = ['.', '?.', '::', '?::']
  • diff --git a/documentation/docs/scope.html b/documentation/docs/scope.html index 83030a42..944bbe93 100644 --- a/documentation/docs/scope.html +++ b/documentation/docs/scope.html @@ -69,6 +69,11 @@ + + register.coffee + + + repl.coffee @@ -113,11 +118,15 @@ generate code, you create a tree of scopes in the same shape as the nested function bodies. Each scope knows about the variables declared within it, and has a reference to its parent enclosing scope. In this way, we know which variables are new and need to be declared with var, and which are shared -with external scopes. -

    +with external scopes.

    +

    Import the helpers we plan to use.

    +
    {extend, last} = require './helpers'
    +
    +exports.Scope = class Scope
    + @@ -127,15 +136,11 @@ with external scopes.
    -

    Import the helpers we plan to use. -

    +

    The root is the top-level Scope object for a given file.

    -
    -{extend, last} = require './helpers'
    -
    -exports.Scope = class Scope
    +
      @root: null
    @@ -146,13 +151,17 @@ exports.Scope = class -

    The root is the top-level Scope object for a given file. -

    +

    Initialize a scope with its parent, for lookups up the chain, +as well as a reference to the Block node it belongs to, which is +where it should declare its variables, and a reference to the function that +it belongs to.

    -
    -  @root: null
    +
      constructor: (@parent, @expressions, @method) ->
    +    @variables = [{name: 'arguments', type: 'arguments'}]
    +    @positions = {}
    +    Scope.root = this unless @parent
    @@ -163,19 +172,16 @@ exports.Scope = class -

    Initialize a scope with its parent, for lookups up the chain, -as well as a reference to the Block node it belongs to, which is -where it should declare its variables, and a reference to the function that -it belongs to. -

    +

    Adds a new variable or overrides an existing one.

    -
    -  constructor: (@parent, @expressions, @method) ->
    -    @variables = [{name: 'arguments', type: 'arguments'}]
    -    @positions = {}
    -    Scope.root = this unless @parent
    +
      add: (name, type, immediate) ->
    +    return @parent.add name, type, immediate if @shared and not immediate
    +    if Object::hasOwnProperty.call @positions, name
    +      @variables[@positions[name]].type = type
    +    else
    +      @positions[name] = @variables.push({name, type}) - 1
    @@ -186,18 +192,17 @@ it belongs to.
    -

    Adds a new variable or overrides an existing one. -

    +

    When super is called, we need to find the name of the current method we're +in, so that we know how to invoke the same method of the parent class. This +can get complicated if super is being called from an inner function. +namedMethod will walk up the scope tree until it either finds the first +function object that has a name filled in, or bottoms out.

    -
    -  add: (name, type, immediate) ->
    -    return @parent.add name, type, immediate if @shared and not immediate
    -    if Object::hasOwnProperty.call @positions, name
    -      @variables[@positions[name]].type = type
    -    else
    -      @positions[name] = @variables.push({name, type}) - 1
    +
      namedMethod: ->
    +    return @method if @method?.name or !@parent
    +    @parent.namedMethod()
    @@ -208,19 +213,15 @@ it belongs to.
    -

    When super is called, we need to find the name of the current method we're -in, so that we know how to invoke the same method of the parent class. This -can get complicated if super is being called from an inner function. -namedMethod will walk up the scope tree until it either finds the first -function object that has a name filled in, or bottoms out. -

    +

    Look up a variable name in lexical scope, and declare it if it does not +already exist.

    -
    -  namedMethod: ->
    -    return @method if @method?.name or !@parent
    -    @parent.namedMethod()
    +
      find: (name) ->
    +    return yes if @check name
    +    @add name, 'var'
    +    no
    @@ -231,17 +232,14 @@ function object that has a name filled in, or bottoms out.
    -

    Look up a variable name in lexical scope, and declare it if it does not -already exist. -

    +

    Reserve a variable name as originating from a function parameter for this +scope. No var required for internal references.

    -
    -  find: (name) ->
    -    return yes if @check name
    -    @add name, 'var'
    -    no
    +
      parameter: (name) ->
    +    return if @shared and @parent.check name, yes
    +    @add name, 'param'
    @@ -252,16 +250,13 @@ already exist.
    -

    Reserve a variable name as originating from a function parameter for this -scope. No var required for internal references. -

    +

    Just check to see if a variable has already been declared, without reserving, +walks up to the root scope.

    -
    -  parameter: (name) ->
    -    return if @shared and @parent.check name, yes
    -    @add name, 'param'
    +
      check: (name) ->
    +    !!(@type(name) or @parent?.check(name))
    @@ -272,15 +267,15 @@ scope. No var required for internal references.
    -

    Just check to see if a variable has already been declared, without reserving, -walks up to the root scope. -

    +

    Generate a temporary variable name at the given index.

    -
    -  check: (name) ->
    -    !!(@type(name) or @parent?.check(name))
    +
      temporary: (name, index) ->
    +    if name.length > 1
    +      '_' + name + if index > 1 then index - 1 else ''
    +    else
    +      '_' + (index + parseInt name, 36).toString(36).replace /\d/g, 'a'
    @@ -291,17 +286,13 @@ walks up to the root scope.
    -

    Generate a temporary variable name at the given index. -

    +

    Gets the type of a variable.

    -
    -  temporary: (name, index) ->
    -    if name.length > 1
    -      '_' + name + if index > 1 then index - 1 else ''
    -    else
    -      '_' + (index + parseInt name, 36).toString(36).replace /\d/g, 'a'
    +
      type: (name) ->
    +    return v.type for v in @variables when v.name is name
    +    null
    @@ -312,15 +303,16 @@ walks up to the root scope.
    -

    Gets the type of a variable. -

    +

    If we need to store an intermediate result, find an available name for a +compiler-generated variable. _var, _var2, and so on...

    -
    -  type: (name) ->
    -    return v.type for v in @variables when v.name is name
    -    null
    +
      freeVariable: (name, reserve=true) ->
    +    index = 0
    +    index++ while @check((temp = @temporary name, index))
    +    @add temp, 'var', yes if reserve
    +    temp
    @@ -331,18 +323,14 @@ walks up to the root scope.
    -

    If we need to store an intermediate result, find an available name for a -compiler-generated variable. _var, _var2, and so on... -

    +

    Ensure that an assignment is made at the top of this scope +(or at the top-level scope, if requested).

    -
    -  freeVariable: (name, reserve=true) ->
    -    index = 0
    -    index++ while @check((temp = @temporary name, index))
    -    @add temp, 'var', yes if reserve
    -    temp
    +
      assign: (name, value) ->
    +    @add name, {value, assigned: yes}, yes
    +    @hasAssignments = yes
    @@ -353,16 +341,12 @@ compiler-generated variable. _var, _var2, and so on...
    -

    Ensure that an assignment is made at the top of this scope -(or at the top-level scope, if requested). -

    +

    Does this scope have any declared variables?

    -
    -  assign: (name, value) ->
    -    @add name, {value, assigned: yes}, yes
    -    @hasAssignments = yes
    +
      hasDeclarations: ->
    +    !!@declaredVariables().length
    @@ -373,14 +357,16 @@ compiler-generated variable. _var, _var2, and so on...
    -

    Does this scope have any declared variables? -

    +

    Return the list of variables first declared in this scope.

    -
    -  hasDeclarations: ->
    -    !!@declaredVariables().length
    +
      declaredVariables: ->
    +    realVars = []
    +    tempVars = []
    +    for v in @variables when v.type is 'var'
    +      (if v.name.charAt(0) is '_' then tempVars else realVars).push v.name
    +    realVars.sort().concat tempVars.sort()
    @@ -391,37 +377,13 @@ compiler-generated variable. _var, _var2, and so on...
    -

    Return the list of variables first declared in this scope. -

    - - - -
    -  declaredVariables: ->
    -    realVars = []
    -    tempVars = []
    -    for v in @variables when v.type is 'var'
    -      (if v.name.charAt(0) is '_' then tempVars else realVars).push v.name
    -    realVars.sort().concat tempVars.sort()
    - - - - -
  • -
    - -
    - -

    Return the list of assignments that are supposed to be made at the top -of this scope. -

    +of this scope.

    -
    -  assignedVariables: ->
    -    "#{v.name} = #{v.type.value}" for v in @variables when v.type.assigned
    +
      assignedVariables: ->
    +    "#{v.name} = #{v.type.value}" for v in @variables when v.type.assigned
  • diff --git a/documentation/docs/sourcemap.html b/documentation/docs/sourcemap.html index d47c527f..4f3b31d0 100644 --- a/documentation/docs/sourcemap.html +++ b/documentation/docs/sourcemap.html @@ -69,6 +69,11 @@ + + register.coffee + + + repl.coffee @@ -111,8 +116,13 @@

    Source maps allow JavaScript runtimes to match running JavaScript back to the original source code that corresponds to it. This can be minified JavaScript, but in our case, we're concerned with mapping pretty-printed -JavaScript back to CoffeeScript. -

    +JavaScript back to CoffeeScript.

    +

    In order to produce maps, we must keep track of positions (line number, column number) +that originated every node in the syntax tree, and be able to generate a +map file +— which is a compact, VLQ-encoded representation of the JSON serialization +of this information — to write out alongside the generated JavaScript.

    +

    LineMap

    @@ -125,25 +135,34 @@ JavaScript back to CoffeeScript.
    -

    In order to produce maps, we must keep track of positions (line number, column number) -that originated every node in the syntax tree, and be able to generate a -map file -— which is a compact, VLQ-encoded representation of the JSON serialization -of this information — to write out alongside the generated JavaScript. -

    +

    A LineMap object keeps track of information about original line and column +positions for a single line of output JavaScript code. +SourceMaps are implemented in terms of LineMaps.

    +
    class LineMap
    +  constructor: (@line) ->
    +    @columns = []
    +
    +  add: (column, [sourceLine, sourceColumn], options={}) ->
    +    return if @columns[column] and options.noReplace
    +    @columns[column] = {line: @line, column, sourceLine, sourceColumn}
    +
    +  sourceLocation: (column) ->
    +    column-- until (mapping = @columns[column]) or (column <= 0)
    +    mapping and [mapping.sourceLine, mapping.sourceColumn]
    +
  • -
    +
    -

    LineMap

    +

    SourceMap

    @@ -156,9 +175,18 @@ of this information — to write out alongside the generated JavaScript.
    - +

    Maps locations in a single generated JavaScript file back to locations in +the original CoffeeScript source file.

    +

    This is intentionally agnostic towards how a source map might be represented on +disk. Once the compiler is ready to produce a "v3"-style source map, we can walk +through the arrays of line and column buffer to produce it.

    +
    +
    class SourceMap
    +  constructor: ->
    +    @lines = []
    +
  • @@ -168,25 +196,17 @@ of this information — to write out alongside the generated JavaScript.
    -

    A LineMap object keeps track of information about original line and column -positions for a single line of output JavaScript code. -SourceMaps are implemented in terms of LineMaps. -

    +

    Adds a mapping to this SourceMap. sourceLocation and generatedLocation +are both [line, column] arrays. If options.noReplace is true, then if there +is already a mapping for the specified line and column, this will have no +effect.

    -
    -class LineMap
    -  constructor: (@line) ->
    -    @columns = []
    -
    -  add: (column, [sourceLine, sourceColumn], options={}) ->
    -    return if @columns[column] and options.noReplace
    -    @columns[column] = {line: @line, column, sourceLine, sourceColumn}
    -
    -  sourceLocation: (column) ->
    -    column-- until (mapping = @columns[column]) or (column <= 0)
    -    mapping and [mapping.sourceLine, mapping.sourceColumn]
    +
      add: (sourceLocation, generatedLocation, options = {}) ->
    +    [line, column] = generatedLocation
    +    lineMap = (@lines[line] or= new LineMap(line))
    +    lineMap.add column, sourceLocation, options
    @@ -194,13 +214,18 @@ positions for a single line of output JavaScript code.
  • -
    +
    -

    SourceMap

    +

    Look up the original position of a given line and column in the generated +code.

    +
      sourceLocation: ([line, column]) ->
    +    line-- until (lineMap = @lines[line]) or (line <= 0)
    +    lineMap and lineMap.sourceLocation column
    +
  • @@ -210,7 +235,8 @@ positions for a single line of output JavaScript code.
    - +

    V3 SourceMap Generation

    + @@ -222,12 +248,29 @@ positions for a single line of output JavaScript code.
    -

    Maps locations in a single generated JavaScript file back to locations in -the original CoffeeScript source file. -

    +

    Builds up a V3 source map, returning the generated JSON as a string. +options.sourceRoot may be used to specify the sourceRoot written to the source +map. Also, options.sourceFiles and options.generatedFile may be passed to +set "sources" and "file", respectively.

    +
      generate: (options = {}, code = null) ->
    +    writingline       = 0
    +    lastColumn        = 0
    +    lastSourceLine    = 0
    +    lastSourceColumn  = 0
    +    needComma         = no
    +    buffer            = ""
    +
    +    for lineMap, lineNumber in @lines when lineMap
    +      for mapping in lineMap.columns when mapping
    +        while writingline < mapping.line
    +          lastColumn = 0
    +          needComma = no
    +          buffer += ";"
    +          writingline++
    + @@ -237,17 +280,13 @@ the original CoffeeScript source file.
    -

    This is intentionally agnostic towards how a source map might be represented on -disk. Once the compiler is ready to produce a "v3"-style source map, we can walk -through the arrays of line and column buffer to produce it. -

    +

    Write a comma if we've already written a segment on this line.

    -
    -class SourceMap
    -  constructor: ->
    -    @lines = []
    +
            if needComma
    +          buffer += ","
    +          needComma = no
    @@ -258,19 +297,15 @@ through the arrays of line and column buffer to produce it.
    -

    Adds a mapping to this SourceMap. sourceLocation and generatedLocation -are both [line, column] arrays. If options.noReplace is true, then if there -is already a mapping for the specified line and column, this will have no -effect. -

    +

    Write the next segment. Segments can be 1, 4, or 5 values. If just one, then it +is a generated column which doesn't match anything in the source code.

    +

    The starting column in the generated source, relative to any previous recorded +column for the current line:

    -
    -  add: (sourceLocation, generatedLocation, options = {}) ->
    -    [line, column] = generatedLocation
    -    lineMap = (@lines[line] or= new LineMap(line))
    -    lineMap.add column, sourceLocation, options
    +
            buffer += @encodeVlq mapping.column - lastColumn
    +        lastColumn = mapping.column
    @@ -281,16 +316,11 @@ effect.
    -

    Look up the original position of a given line and column in the generated -code. -

    +

    The index into the list of sources:

    -
    -  sourceLocation: ([line, column]) ->
    -    line-- until (lineMap = @lines[line]) or (line <= 0)
    -    lineMap and lineMap.sourceLocation column
    +
            buffer += @encodeVlq 0
    @@ -298,13 +328,16 @@ code.
  • -
    +
    -

    V3 SourceMap Generation

    +

    The starting line in the original source, relative to the previous source line.

    +
            buffer += @encodeVlq mapping.sourceLine - lastSourceLine
    +        lastSourceLine = mapping.sourceLine
    +
  • @@ -314,9 +347,14 @@ code.
    - +

    The starting column in the original source, relative to the previous column.

    + +
            buffer += @encodeVlq mapping.sourceColumn - lastSourceColumn
    +        lastSourceColumn = mapping.sourceColumn
    +        needComma = yes
    + @@ -326,30 +364,21 @@ code.
    -

    Builds up a V3 source map, returning the generated JSON as a string. -options.sourceRoot may be used to specify the sourceRoot written to the source -map. Also, options.sourceFiles and options.generatedFile may be passed to -set "sources" and "file", respectively. -

    +

    Produce the canonical JSON object format for a "v3" source map.

    -
    -  generate: (options = {}, code = null) ->
    -    writingline       = 0
    -    lastColumn        = 0
    -    lastSourceLine    = 0
    -    lastSourceColumn  = 0
    -    needComma         = no
    -    buffer            = ""
    +            
        v3 =
    +      version:    3
    +      file:       options.generatedFile or ''
    +      sourceRoot: options.sourceRoot or ''
    +      sources:    options.sourceFiles or ['']
    +      names:      []
    +      mappings:   buffer
     
    -    for lineMap, lineNumber in @lines when lineMap
    -      for mapping in lineMap.columns when mapping
    -        while writingline < mapping.line
    -          lastColumn = 0
    -          needComma = no
    -          buffer += ";"
    -          writingline++
    + v3.sourcesContent = [code] if options.inline + + JSON.stringify v3, null, 2
    @@ -360,16 +389,10 @@ set "sources" and "file", respectively.
    -

    Write a comma if we've already written a segment on this line. -

    +

    Base64 VLQ Encoding

    -
    -        if needComma
    -          buffer += ","
    -          needComma = no
    - @@ -379,12 +402,21 @@ set "sources" and "file", respectively.
    -

    Write the next segment. Segments can be 1, 4, or 5 values. If just one, then it -is a generated column which doesn't match anything in the source code. -

    +

    Note that SourceMap VLQ encoding is "backwards". MIDI-style VLQ encoding puts +the most-significant-bit (MSB) from the original value into the MSB of the VLQ +encoded value (see Wikipedia). +SourceMap VLQ does things the other way around, with the least significat four +bits of the original value encoded into the first byte of the VLQ encoded value.

    +
      VLQ_SHIFT            = 5
    +  VLQ_CONTINUATION_BIT = 1 << VLQ_SHIFT             # 0010 0000
    +  VLQ_VALUE_MASK       = VLQ_CONTINUATION_BIT - 1   # 0001 1111
    +
    +  encodeVlq: (value) ->
    +    answer = ''
    + @@ -394,15 +426,11 @@ is a generated column which doesn't match anything in the source code.
    -

    The starting column in the generated source, relative to any previous recorded -column for the current line: -

    +

    Least significant bit represents the sign.

    -
    -        buffer += @encodeVlq mapping.column - lastColumn
    -        lastColumn = mapping.column
    +
        signBit = if value < 0 then 1 else 0
    @@ -413,13 +441,11 @@ column for the current line:
    -

    The index into the list of sources: -

    +

    The next bits are the actual value.

    -
    -        buffer += @encodeVlq 0
    +
        valueToEncode = (Math.abs(value) << 1) + signBit
    @@ -430,14 +456,17 @@ column for the current line:
    -

    The starting line in the original source, relative to the previous source line. -

    +

    Make sure we encode at least one character, even if valueToEncode is 0.

    -
    -        buffer += @encodeVlq mapping.sourceLine - lastSourceLine
    -        lastSourceLine = mapping.sourceLine
    +
        while valueToEncode or not answer
    +      nextChunk = valueToEncode & VLQ_VALUE_MASK
    +      valueToEncode = valueToEncode >> VLQ_SHIFT
    +      nextChunk |= VLQ_CONTINUATION_BIT if valueToEncode
    +      answer += @encodeBase64 nextChunk
    +
    +    answer
    @@ -448,16 +477,10 @@ column for the current line:
    -

    The starting column in the original source, relative to the previous column. -

    +

    Regular Base64 Encoding

    -
    -        buffer += @encodeVlq mapping.sourceColumn - lastSourceColumn
    -        lastSourceColumn = mapping.sourceColumn
    -        needComma = yes
    - @@ -467,23 +490,13 @@ column for the current line:
    -

    Produce the canonical JSON object format for a "v3" source map. -

    - + -
    -    v3 =
    -      version:    3
    -      file:       options.generatedFile or ''
    -      sourceRoot: options.sourceRoot or ''
    -      sources:    options.sourceFiles or ['']
    -      names:      []
    -      mappings:   buffer
    +            
      BASE64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
     
    -    v3.sourcesContent = [code] if options.inline
    -
    -    JSON.stringify v3, null, 2
    + encodeBase64: (value) -> + BASE64_CHARS[value] or throw new Error "Cannot Base64 encode value: #{value}"
    @@ -491,152 +504,14 @@ column for the current line:
  • -
    +
    -

    Base64 VLQ Encoding

    +

    Our API for source maps is just the SourceMap class.

    -
  • - - -
  • -
    - -
    - -
    - -
    - -
  • - - -
  • -
    - -
    - -
    -

    Note that SourceMap VLQ encoding is "backwards". MIDI-style VLQ encoding puts -the most-significant-bit (MSB) from the original value into the MSB of the VLQ -encoded value (see Wikipedia). -SourceMap VLQ does things the other way around, with the least significat four -bits of the original value encoded into the first byte of the VLQ encoded value. -

    - -
    - -
    -  VLQ_SHIFT            = 5
    -  VLQ_CONTINUATION_BIT = 1 << VLQ_SHIFT             # 0010 0000
    -  VLQ_VALUE_MASK       = VLQ_CONTINUATION_BIT - 1   # 0001 1111
    -
    -  encodeVlq: (value) ->
    -    answer = ''
    - -
  • - - -
  • -
    - -
    - -
    -

    Least significant bit represents the sign. -

    - -
    - -
        signBit = if value < 0 then 1 else 0
    - -
  • - - -
  • -
    - -
    - -
    -

    The next bits are the actual value. -

    - -
    - -
        valueToEncode = (Math.abs(value) << 1) + signBit
    - -
  • - - -
  • -
    - -
    - -
    -

    Make sure we encode at least one character, even if valueToEncode is 0. -

    - -
    - -
        while valueToEncode or not answer
    -      nextChunk = valueToEncode & VLQ_VALUE_MASK
    -      valueToEncode = valueToEncode >> VLQ_SHIFT
    -      nextChunk |= VLQ_CONTINUATION_BIT if valueToEncode
    -      answer += @encodeBase64 nextChunk
    -
    -    answer
    - -
  • - - -
  • -
    - -
    - -
    -

    Regular Base64 Encoding

    - -
    - -
  • - - -
  • -
    - -
    - -
    - -
    - -
    -  BASE64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
    -
    -  encodeBase64: (value) ->
    -    BASE64_CHARS[value] or throw new Error "Cannot Base64 encode value: #{value}"
    - -
  • - - -
  • -
    - -
    - -
    -

    Our API for source maps is just the SourceMap class. -

    - -
    - -
    -module.exports = SourceMap
    +
    module.exports = SourceMap