From 3527af89fa18996f42f46314af6f8582dba8c085 Mon Sep 17 00:00:00 2001 From: Jeremy Ashkenas Date: Wed, 13 Mar 2013 11:48:23 +0800 Subject: [PATCH] new new docs --- Cakefile | 2 +- documentation/docs/browser.html | 335 +-- documentation/docs/cake.html | 387 ++-- documentation/docs/coffee-script.html | 543 +++-- documentation/docs/command.html | 641 +++--- documentation/docs/docco.css | 398 ++-- documentation/docs/grammar.html | 1405 +++++++++---- documentation/docs/helpers.html | 495 +++-- documentation/docs/index.html | 201 +- documentation/docs/lexer.html | 1569 +++++++++----- documentation/docs/nodes.html | 2753 +++++++++++++++++-------- documentation/docs/optparse.html | 381 ++-- documentation/docs/repl.html | 397 ++-- documentation/docs/rewriter.html | 1033 +++++++--- documentation/docs/scope.html | 479 +++-- documentation/docs/sourcemap.html | 703 ++++--- 16 files changed, 7677 insertions(+), 4045 deletions(-) diff --git a/Cakefile b/Cakefile index d22a583893..bc847825e5 100644 --- a/Cakefile +++ b/Cakefile @@ -128,7 +128,7 @@ task 'doc:site', 'watch and continually rebuild the documentation for the websit task 'doc:source', 'rebuild the internal documentation', -> - exec 'docco -l linear src/*.*coffee && cp -rf docs documentation && rm -r docs', (err) -> + exec 'docco src/*.*coffee && cp -rf docs documentation && rm -r docs', (err) -> throw err if err diff --git a/documentation/docs/browser.html b/documentation/docs/browser.html index 38013c6732..b8f6c160ca 100644 --- a/documentation/docs/browser.html +++ b/documentation/docs/browser.html @@ -4,180 +4,195 @@ browser.coffee - + -
-
- -
- -

browser.coffee

- - - -
-

Table of Contents

-
    - - -
  1. - - browser.coffee - -
  2. - - -
  3. - - cake.coffee - -
  4. - - -
  5. - - coffee-script.coffee - -
  6. - - -
  7. - - command.coffee - -
  8. - - -
  9. - - grammar.coffee - -
  10. - - -
  11. - - helpers.coffee - -
  12. - - -
  13. - - index.coffee - -
  14. - - -
  15. - - lexer.coffee - -
  16. - - -
  17. - - nodes.coffee - -
  18. - - -
  19. - - optparse.coffee - -
  20. - - -
  21. - - repl.coffee - -
  22. - - -
  23. - - rewriter.coffee - -
  24. - - -
  25. - - scope.litcoffee - -
  26. - - -
  27. - - sourcemap.coffee - -
  28. - -
+ - - -

Override exported methods for non-Node.js engines. +

  • +
    + +
    + +
    +

    Override exported methods for non-Node.js engines.

    - -
    CoffeeScript = require './coffee-script'
    -CoffeeScript.require = require
    - - +
    + +
    CoffeeScript = require './coffee-script'
    +CoffeeScript.require = require
    + +
  • +
  • +
    + +
    + +
    +

    Use standard JavaScript eval to eval code.

    - -
    CoffeeScript.eval = (code, options = {}) ->
    +            
    + +
    CoffeeScript.eval = (code, options = {}) ->
       options.bare ?= on
    -  eval CoffeeScript.compile code, options
    - - + eval CoffeeScript.compile code, options
    + +
  • +
  • +
    + +
    + +
    +

    Running code does not provide access to this scope.

    - -
    CoffeeScript.run = (code, options = {}) ->
    +            
    + +
    CoffeeScript.run = (code, options = {}) ->
       options.bare = on
    -  Function(CoffeeScript.compile code, options)()
    - - + Function(CoffeeScript.compile code, options)()
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    return unless window?
    - - +
    + +
    return unless window?
    + +
  • +
  • +
    + +
    + +
    +

    Load a remote script from the current domain via XHR.

    - -
    CoffeeScript.load = (url, callback, options = {}) ->
    +            
    + +
    CoffeeScript.load = (url, callback, options = {}) ->
       xhr = if window.ActiveXObject
         new window.ActiveXObject('Microsoft.XMLHTTP')
       else
    @@ -191,19 +206,27 @@ 

    Table of Contents

    else throw new Error "Could not load #{url}" callback() if callback - xhr.send null
    - - + xhr.send null
    + +
  • +
  • +
    + +
    + +
    +

    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.

    - -
    runScripts = ->
    +            
    + +
    runScripts = ->
       scripts = document.getElementsByTagName 'script'
       coffeetypes = ['text/coffeescript', 'text/literate-coffeescript']
       coffees = (s for s in scripts when s.type in coffeetypes)
    @@ -219,25 +242,33 @@ 

    Table of Contents

    else CoffeeScript.run script.innerHTML, options execute() - null
    - - + null
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    if window.addEventListener
    +            
    + +
    if window.addEventListener
       addEventListener 'DOMContentLoaded', runScripts, no
     else
       attachEvent 'onload', runScripts
     
    -
    +
    + +
  • - -
    h
    -
    +
    diff --git a/documentation/docs/cake.html b/documentation/docs/cake.html index 4687895004..2bdff19a61 100644 --- a/documentation/docs/cake.html +++ b/documentation/docs/cake.html @@ -4,129 +4,111 @@ cake.coffee - + -
    -
    - -
    - -

    cake.coffee

    + - - -

    cake is a simplified version of Make +

  • +
    + +
    + +
    +

    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. @@ -140,79 +122,127 @@

    Table of Contents

    - -
    fs           = require 'fs'
    +            
    + +
    fs           = require 'fs'
     path         = require 'path'
     helpers      = require './helpers'
     optparse     = require './optparse'
     CoffeeScript = require './coffee-script'
     
    -existsSync   = fs.existsSync or path.existsSync
    - - +existsSync = fs.existsSync or path.existsSync
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    tasks     = {}
    +            
    + +
    tasks     = {}
     options   = {}
     switches  = []
    -oparse    = null
    - - +oparse = null
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    helpers.extend global,
    - - +
    + +
    helpers.extend global,
    + +
  • +
  • +
    + +
    + +
    +

    Define a Cake task with a short name, an optional sentence description, and the function to run as the action itself.

    - -
      task: (name, description, action) ->
    +            
    + +
      task: (name, description, action) ->
         [action, description] = [description, action] unless action
    -    tasks[name] = {name, description, action}
    - - + tasks[name] = {name, description, action}
    + +
  • +
  • +
    + +
    + +
    +

    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.

    - -
      option: (letter, flag, description) ->
    -    switches.push [letter, flag, description]
    - - +
    + +
      option: (letter, flag, description) ->
    +    switches.push [letter, flag, description]
    + +
  • +
  • +
    + +
    + +
    +

    Invoke another task in the current Cakefile.

    - -
      invoke: (name) ->
    +            
    + +
      invoke: (name) ->
         missingTask name unless tasks[name]
    -    tasks[name].action options
    - - + tasks[name].action options
    + +
  • +
  • +
    + +
    + +
    +

    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 @@ -220,8 +250,9 @@

    Table of Contents

    - -
    exports.run = ->
    +            
    + +
    exports.run = ->
       global.__originalDirname = fs.realpathSync '.'
       process.chdir cakefileDirectory __originalDirname
       args = process.argv[2..]
    @@ -232,17 +263,25 @@ 

    Table of Contents

    options = oparse.parse(args) catch e return fatalError "#{e}" - invoke arg for arg in options.arguments
    - - + invoke arg for arg in options.arguments
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    printTasks = ->
    +            
    + +
    printTasks = ->
       relative = path.relative or path.resolve
       cakefilePath = path.join relative(__originalDirname, process.cwd()), 'Cakefile'
       console.log "#{cakefilePath} defines the following tasks:\n"
    @@ -251,42 +290,58 @@ 

    Table of Contents

    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
    - - + console.log oparse.help() if switches.length
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    fatalError = (message) ->
    +            
    + +
    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}"
    - - +missingTask = (task) -> fatalError "No such task: #{task}"
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    cakefileDirectory = (dir) ->
    +            
    + +
    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()}"
     
    -
    +
    + +
  • - -
    h
    -
    +
    diff --git a/documentation/docs/coffee-script.html b/documentation/docs/coffee-script.html index ce3cd7d7c1..c30e3fa17a 100644 --- a/documentation/docs/coffee-script.html +++ b/documentation/docs/coffee-script.html @@ -4,129 +4,111 @@ coffee-script.coffee - + -
    -
    - -
    - -

    coffee-script.coffee

    - - - -
    -

    Table of Contents

    -
      - - -
    1. - - browser.coffee - -
    2. - - -
    3. - - cake.coffee - -
    4. - - -
    5. - - coffee-script.coffee - -
    6. - - -
    7. - - command.coffee - -
    8. - - -
    9. - - grammar.coffee - -
    10. - - -
    11. - - helpers.coffee - -
    12. - - -
    13. - - index.coffee - -
    14. - - -
    15. - - lexer.coffee - -
    16. - - -
    17. - - nodes.coffee - -
    18. - - -
    19. - - optparse.coffee - -
    20. - - -
    21. - - repl.coffee - -
    22. - - -
    23. - - rewriter.coffee - -
    24. - - -
    25. - - scope.litcoffee - -
    26. - - -
    27. - - sourcemap.coffee - -
    28. - -
    + - - +
  • +
    +

    coffee-script.coffee

    +
    +
  • -

    CoffeeScript can be used both on the server, as a command-line compiler based + + +

  • +
    + +
    + +
    +

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

    Table of Contents

    - -
    fs        = require 'fs'
    +            
    + +
    fs        = require 'fs'
     path      = require 'path'
     {Lexer}   = require './lexer'
     {parser}  = require './parser'
     helpers   = require './helpers'
     vm        = require 'vm'
    -sourcemap = require './sourcemap'
    - - +sourcemap = require './sourcemap'
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    loadFile = (module, filename) ->
    +            
    + +
    loadFile = (module, filename) ->
       raw = fs.readFileSync filename, 'utf8'
       stripped = if raw.charCodeAt(0) is 0xFEFF then raw.substring 1 else raw
       module._compile compile(stripped, {filename, literate: helpers.isLiterate filename}), filename
     
     if require.extensions
       for ext in ['.coffee', '.litcoffee', '.md', '.coffee.md']
    -    require.extensions[ext] = loadFile
    - - + require.extensions[ext] = loadFile
    + +
  • +
  • +
    + +
    + +
    +

    The current CoffeeScript version number.

    - -
    exports.VERSION = '1.6.1'
    - - +
    + +
    exports.VERSION = '1.6.1'
    + +
  • +
  • +
    + +
    + +
    +

    Expose helpers for testing.

    - -
    exports.helpers = helpers
    - - +
    + +
    exports.helpers = helpers
    + +
  • +
  • +
    + +
    + +
    +

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

    @@ -200,8 +214,9 @@

    Table of Contents

    - -
    exports.compile = compile = (code, options = {}) ->
    +            
    + +
    exports.compile = compile = (code, options = {}) ->
       {merge} = exports.helpers
       try
     
    @@ -217,17 +232,25 @@ 

    Table of Contents

    currentLine += 1 if options.sourceMap currentColumn = 0 js = "" - for fragment in fragments
    - - + for fragment in fragments
    + +
  • +
  • +
    + +
    + +
    +

    Update the sourcemap with data from each fragment

    - -
          if sourceMap
    +            
    + +
          if sourceMap
             if fragment.locationData
               sourceMap.addMapping(
                 [fragment.locationData.first_line, fragment.locationData.first_column],
    @@ -235,17 +258,25 @@ 

    Table of Contents

    {noReplace: true}) newLines = helpers.count fragment.code, "\n" currentLine += newLines - currentColumn = fragment.code.length - (if newLines then fragment.code.lastIndexOf "\n" else 0)
    - - + currentColumn = fragment.code.length - (if newLines then fragment.code.lastIndexOf "\n" else 0)
    + +
  • +
  • +
    + +
    + +
    +

    Copy the code from each fragment into the final JavaScript.

    - -
          js += fragment.code
    +            
    + +
          js += fragment.code
     
       catch err
         err.message = "In #{options.filename}, #{err.message}" if options.filename
    @@ -262,101 +293,165 @@ 

    Table of Contents

    answer.v3SourceMap = sourcemap.generateV3SourceMap sourceMap, coffeeFile, jsFile answer else - js
    - - + js
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    exports.tokens = (code, options) ->
    -  lexer.tokenize code, options
    - - +
    + +
    exports.tokens = (code, options) ->
    +  lexer.tokenize code, options
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    exports.nodes = (source, options) ->
    +            
    + +
    exports.nodes = (source, options) ->
       if typeof source is 'string'
         parser.parse lexer.tokenize source, options
       else
    -    parser.parse source
    - - + parser.parse source
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    exports.run = (code, options = {}) ->
    -  mainModule = require.main
    - - +
    + +
    exports.run = (code, options = {}) ->
    +  mainModule = require.main
    + +
  • +
  • +
    + +
    + +
    +

    Set the filename.

    - -
      mainModule.filename = process.argv[1] =
    -      if options.filename then fs.realpathSync(options.filename) else '.'
    - - +
    + +
      mainModule.filename = process.argv[1] =
    +      if options.filename then fs.realpathSync(options.filename) else '.'
    + +
  • +
  • +
    + +
    + +
    +

    Clear the module cache.

    - -
      mainModule.moduleCache and= {}
    - - +
    + +
      mainModule.moduleCache and= {}
    + +
  • +
  • +
    + +
    + +
    +

    Assign paths for node_modules loading

    - -
      mainModule.paths = require('module')._nodeModulePaths path.dirname fs.realpathSync options.filename
    - - +
    + +
      mainModule.paths = require('module')._nodeModulePaths path.dirname fs.realpathSync options.filename
    + +
  • +
  • +
    + +
    + +
    +

    Compile.

    - -
      if not helpers.isCoffee(mainModule.filename) or require.extensions
    +            
    + +
      if not helpers.isCoffee(mainModule.filename) or require.extensions
         mainModule._compile compile(code, options), mainModule.filename
       else
    -    mainModule._compile code, mainModule.filename
    - - + mainModule._compile code, mainModule.filename
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    exports.eval = (code, options = {}) ->
    +            
    + +
    exports.eval = (code, options = {}) ->
       return unless code = code.trim()
       Script = vm.Script
       if Script
    @@ -370,32 +465,48 @@ 

    Table of Contents

    else sandbox = global sandbox.__filename = options.filename || 'eval' - sandbox.__dirname = path.dirname sandbox.__filename
    - - + sandbox.__dirname = path.dirname sandbox.__filename
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
        unless sandbox isnt global or sandbox.module or sandbox.require
    +            
    + +
        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'
    + +
  • +
  • +
    + +
    + +
    +

    use the same hack node currently uses for their own REPL

    - -
          _require.paths = _module.paths = Module._nodeModulePaths process.cwd()
    +            
    + +
          _require.paths = _module.paths = Module._nodeModulePaths process.cwd()
           _require.resolve = (request) -> Module._resolveFilename request, _module
       o = {}
       o[k] = v for own k, v of options
    @@ -404,28 +515,44 @@ 

    Table of Contents

    if sandbox is global vm.runInThisContext js else - vm.runInContext js, sandbox
    - - + vm.runInContext js, sandbox
    + +
  • +
  • +
    + +
    + +
    +

    Instantiate a Lexer for our use here.

    - -
    lexer = new Lexer
    - - +
    + +
    lexer = new Lexer
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    parser.lexer =
    +            
    + +
    parser.lexer =
       lex: ->
         token = @tokens[@pos++]
         if token
    @@ -442,11 +569,11 @@ 

    Table of Contents

    parser.yy = require './nodes' -
    +
    + +
  • - -
    h
    -
    +
    diff --git a/documentation/docs/command.html b/documentation/docs/command.html index d051de29b2..f77d706612 100644 --- a/documentation/docs/command.html +++ b/documentation/docs/command.html @@ -4,129 +4,111 @@ command.coffee - + -
    -
    - -
    - -

    command.coffee

    - - - -
    -

    Table of Contents

    -
      - - -
    1. - - browser.coffee - -
    2. - - -
    3. - - cake.coffee - -
    4. - - -
    5. - - coffee-script.coffee - -
    6. - - -
    7. - - command.coffee - -
    8. - - -
    9. - - grammar.coffee - -
    10. - - -
    11. - - helpers.coffee - -
    12. - - -
    13. - - index.coffee - -
    14. - - -
    15. - - lexer.coffee - -
    16. - - -
    17. - - nodes.coffee - -
    18. - - -
    19. - - optparse.coffee - -
    20. - - -
    21. - - repl.coffee - -
    22. - - -
    23. - - rewriter.coffee - -
    24. - - -
    25. - - scope.litcoffee - -
    26. - - -
    27. - - sourcemap.coffee - -
    28. - -
    + - - -

    The coffee utility. Handles command-line compilation of CoffeeScript + +

  • +
    + +
    + +
    +

    The coffee utility. Handles command-line compilation of CoffeeScript into various forms: saved into .js files or printed to stdout, piped to JavaScript Lint or recompiled every time the source is saved, printed as a token stream or as the syntax tree, or launch an @@ -137,8 +119,9 @@

    Table of Contents

    - -
    fs             = require 'fs'
    +            
    + +
    fs             = require 'fs'
     path           = require 'path'
     helpers        = require './helpers'
     optparse       = require './optparse'
    @@ -146,46 +129,70 @@ 

    Table of Contents

    {spawn, exec} = require 'child_process' {EventEmitter} = require 'events' -exists = fs.exists or path.exists
    - - +exists = fs.exists or path.exists
    + +
  • +
  • +
    + +
    + +
    +

    Allow CoffeeScript to emit Node.js events.

    - -
    helpers.extend CoffeeScript, new EventEmitter
    +            
    + +
    helpers.extend CoffeeScript, new EventEmitter
     
     printLine = (line) -> process.stdout.write line + '\n'
     printWarn = (line) -> process.stderr.write line + '\n'
     
    -hidden = (file) -> /^\.|~$/.test file
    - - +hidden = (file) -> /^\.|~$/.test file
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    BANNER = '''
    +            
    + +
    BANNER = '''
       Usage: coffee [options] path/to/script.coffee -- [args]
     
       If called without options, `coffee` will run your script.
    -'''
    - - +'''
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    SWITCHES = [
    +            
    + +
    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']
    @@ -202,34 +209,50 @@ 

    Table of Contents

    ['-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'] -]
    - - +]
    + +
  • +
  • +
    + +
    + +
    +

    Top-level objects shared by all the functions.

    - -
    opts         = {}
    +            
    + +
    opts         = {}
     sources      = []
     sourceCode   = []
     notSources   = {}
     watchers     = {}
    -optionParser = null
    - - +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

    - -
    exports.run = ->
    +            
    + +
    exports.run = ->
       parseOptions()
       return forkNode()                      if opts.nodejs
       return usage()                         if opts.help
    @@ -244,19 +267,27 @@ 

    Table of Contents

    process.argv = process.argv[0..1].concat literals process.argv[0] = 'coffee' for source in sources - compilePath source, yes, path.normalize source
    - - + compilePath source, yes, path.normalize source
    + +
  • +
  • +
    + +
    + +
    +

    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.

    - -
    compilePath = (source, topLevel, base) ->
    +            
    + +
    compilePath = (source, topLevel, base) ->
       fs.stat source, (err, stats) ->
         throw err if err and err.code isnt 'ENOENT'
         if err?.code is 'ENOENT'
    @@ -281,19 +312,27 @@ 

    Table of Contents

    compileScript(source, code.toString(), base) else notSources[source] = yes - removeSource source, base
    - - + removeSource source, base
    + +
  • +
  • +
    + +
    + +
    +

    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.

    - -
    compileScript = (file, input, base) ->
    +            
    + +
    compileScript = (file, input, base) ->
       o = opts
       options = compileOptions file
       try
    @@ -324,53 +363,77 @@ 

    Table of Contents

    return if CoffeeScript.listeners('failure').length return printLine err.message + '\x07' if o.watch printWarn err instanceof Error and err.stack or "ERROR: #{err}" - process.exit 1
    - - + process.exit 1
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    compileStdio = ->
    +            
    + +
    compileStdio = ->
       code = ''
       stdin = process.openStdin()
       stdin.on 'data', (buffer) ->
         code += buffer.toString() if buffer
       stdin.on 'end', ->
    -    compileScript null, code
    - - + compileScript null, code
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    joinTimeout = null
    +            
    + +
    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
    - - + compileScript opts.join, sourceCode.join('\n'), opts.join
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    watch = (source, base) ->
    +            
    + +
    watch = (source, base) ->
     
       prevStats = null
       compileTimeout = null
    @@ -406,17 +469,25 @@ 

    Table of Contents

    rewatch = -> watcher?.close() - watcher = fs.watch source, compile
    - - + watcher = fs.watch source, compile
    + +
  • +
  • +
    + +
    + +
    +

    Watch a directory of files for new additions.

    - -
    watchDir = (source, base) ->
    +            
    + +
    watchDir = (source, base) ->
       readdirTimeout = null
       try
         watcher = fs.watch source, ->
    @@ -441,18 +512,26 @@ 

    Table of Contents

    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()
    - - + compileJoin()
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    removeSource = (source, base, removeJs) ->
    +            
    + +
    removeSource = (source, base, removeJs) ->
       index = sources.indexOf source
       sources.splice index, 1
       sourceCode.splice index, 1
    @@ -462,26 +541,41 @@ 

    Table of Contents

    if itExists fs.unlink jsPath, (err) -> throw err if err and err.code isnt 'ENOENT' - timeLog "removed #{source}"
    - - + timeLog "removed #{source}"
    + +
  • +
  • +
    + +
    + +
    +

    Get the corresponding output JavaScript path for a source file.

    - -
    outputPath = (source, base, extension=".js") ->
    +            
    + +
    outputPath = (source, base, extension=".js") ->
       basename  = helpers.baseFileName source, yes
       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
    -  path.join dir, basename + extension
    - - + path.join dir, basename + extension
    + +
  • +
  • +
    + +
    + +
    +

    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. @@ -492,8 +586,9 @@

    Table of Contents

    - -
    writeJs = (base, sourcePath, js, generatedSourceMap = null) ->
    +            
    + +
    writeJs = (base, sourcePath, js, generatedSourceMap = null) ->
       jsPath = outputPath sourcePath, base
       sourceMapPath = outputPath sourcePath, base, ".map"
       jsDir  = path.dirname jsPath
    @@ -511,72 +606,112 @@ 

    Table of Contents

    if err printLine "Could not write source map: #{err.message}" exists jsDir, (itExists) -> - if itExists then compile() else exec "mkdir -p #{jsDir}", compile
    - - + if itExists then compile() else exec "mkdir -p #{jsDir}", compile
    + +
  • +
  • +
    + +
    + +
    +

    Convenience for cleaner setTimeouts.

    - -
    wait = (milliseconds, func) -> setTimeout func, milliseconds
    - - +
    + +
    wait = (milliseconds, func) -> setTimeout func, milliseconds
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    timeLog = (message) ->
    -  console.log "#{(new Date).toLocaleTimeString()} - #{message}"
    - - +
    + +
    timeLog = (message) ->
    +  console.log "#{(new Date).toLocaleTimeString()} - #{message}"
    + +
  • +
  • +
    + +
    + +
    +

    Pipe compiled JS through JSLint (requires a working jsl command), printing any errors or warnings that arise.

    - -
    lint = (file, js) ->
    +            
    + +
    lint = (file, js) ->
       printIt = (buffer) -> printLine file + ':\t' + buffer.toString().trim()
       conf = __dirname + '/../../extras/jsl.conf'
       jsl = spawn 'jsl', ['-nologo', '-stdin', '-conf', conf]
       jsl.stdout.on 'data', printIt
       jsl.stderr.on 'data', printIt
       jsl.stdin.write js
    -  jsl.stdin.end()
    - - + jsl.stdin.end()
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    printTokens = (tokens) ->
    +            
    + +
    printTokens = (tokens) ->
       strings = for token in tokens
         tag = token[0]
         value = token[1].toString().replace(/\n/, '\\n')
         "[#{tag} #{value}]"
    -  printLine strings.join(' ')
    - - + printLine strings.join(' ')
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    parseOptions = ->
    +            
    + +
    parseOptions = ->
       optionParser  = new optparse.OptionParser SWITCHES, BANNER
       o = opts      = optionParser.parse process.argv[2..]
       o.compile     or=  !!o.output
    @@ -584,70 +719,102 @@ 

    Table of Contents

    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
    - - + return
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    compileOptions = (filename) ->
    +            
    + +
    compileOptions = (filename) ->
       {
         filename
         literate: helpers.isLiterate(filename)
         bare: opts.bare
         header: opts.compile
         sourceMap: opts.map
    -  }
    - - + }
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    forkNode = ->
    +            
    + +
    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]
    - - + customFds: [0, 1, 2]
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    usage = ->
    -  printLine (new optparse.OptionParser SWITCHES, BANNER).help()
    - - +
    + +
    usage = ->
    +  printLine (new optparse.OptionParser SWITCHES, BANNER).help()
    + +
  • +
  • +
    + +
    + +
    +

    Print the --version message and exit.

    - -
    version = ->
    +            
    + +
    version = ->
       printLine "CoffeeScript version #{CoffeeScript.VERSION}"
     
    -
    +
    + +
  • - -
    h
    -
    +
    diff --git a/documentation/docs/docco.css b/documentation/docs/docco.css index 37d7875ae9..d0c69fcfd4 100644 --- a/documentation/docs/docco.css +++ b/documentation/docs/docco.css @@ -30,136 +30,85 @@ font-style: normal; } -@font-face { - font-family: 'fleurons'; - src: url('public/fonts/fleurons.eot'); - src: url('public/fonts/fleurons.eot?#iefix') format('embedded-opentype'), - url('public/fonts/fleurons.woff') format('woff'), - url('public/fonts/fleurons.ttf') format('truetype'); - font-weight: normal; - font-style: normal; +/*--------------------- Layout ----------------------------*/ +html { height: 100%; } +body { + font-family: "aller-light"; + font-size: 14px; + line-height: 18px; + color: #30404f; + margin: 0; padding: 0; + height:100%; } +#container { min-height: 100%; } -/*--------------------- Base Styles ----------------------------*/ +a { + color: #000; +} -body { - font-family: "aller-light"; - background: url('public/images/gray.png'); - background-size: 322px; - margin: 0; +b, strong { + font-weight: normal; + font-family: "aller-bold"; } -hr { - height: 1px; - background: #ddd; - border: 0; +p, ul, ol { + margin: 15px 0 0px; } h1, h2, h3, h4, h5, h6 { color: #112233; + line-height: 1em; font-weight: normal; font-family: "novecento-bold"; text-transform: uppercase; - line-height: 1em; - margin-top: 50px; -} - h1 { - margin: 0; - text-align: center; - } - h2 { - font-size: 1.3em; - } - h1:after { - content: "8"; - display: block; - font-family: "fleurons"; - color: #999; - font-size: 80px; - padding: 10px 0 25px; - } - -a { - text-decoration: none; - color: #000; + margin: 30px 0 15px 0; } - a:hover { - text-decoration: underline; - } -b, strong { - font-weight: normal; - font-family: "aller-bold"; +h1 { + margin-top: 40px; } -blockquote { - border-left: 5px solid #ccc; - margin-left: 0; - padding: 1px 0 1px 1em; +hr { + border: 0; + background: 1px solid #ddd; + height: 1px; + margin: 20px 0; } - .page blockquote p { - font-family: Menlo, Consolas, Monaco, monospace; - font-size: 14px; line-height: 19px; - color: #999; - margin: 10px 0 0; - white-space: pre-wrap; - } pre, tt, code { - font-family: Menlo, Consolas, Monaco, monospace; - font-size: 12px; - display: inline-block; - border: 1px solid #EAEAEA; - background: #f8f8f8; - color: #555; - padding: 0 5px; - line-height: 20px; + font-size: 12px; line-height: 16px; + font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace; + margin: 0; padding: 0; } - .page pre { + .annotation pre { + display: block; margin: 0; - width: 608px; - padding: 10px 15px; + padding: 7px 10px; background: #fcfcfc; -moz-box-shadow: inset 0 0 10px rgba(0,0,0,0.1); -webkit-box-shadow: inset 0 0 10px rgba(0,0,0,0.1); box-shadow: inset 0 0 10px rgba(0,0,0,0.1); - overflow-x: scroll; + overflow-x: auto; } - .page pre code { + .annotation pre code { border: 0; padding: 0; background: transparent; } -.fleur { - font-family: "fleurons"; - font-size: 100px; - text-align: center; - margin: 40px 0; - color: #ccc; -} -/*--------------------- Layout ----------------------------*/ - -.container { - width: 760px; - margin: 0 auto; - background: #fff; - background: rgba(255,255,255, 0.4); - overflow: hidden; +blockquote { + border-left: 5px solid #ccc; + margin-left: 0; + padding: 1px 0 1px 1em; } - .page { - width: 640px; - padding: 30px; - margin: 30px; - background: #fff; - font-size: 17px; - line-height: 26px; + .sections blockquote p { + font-family: Menlo, Consolas, Monaco, monospace; + font-size: 14px; line-height: 19px; + color: #999; + margin: 10px 0 0; + white-space: pre-wrap; } - .page p { - color: #30404f; - margin: 26px 0; - } ul.sections { list-style: none; @@ -167,42 +116,251 @@ ul.sections { margin:0; } -.page li p { - margin: 12px 0; +/* + Force border-box so that % widths fit the parent + container without overlap because of margin/padding. + + More Info : http://www.quirksmode.org/css/box.html +*/ +ul.sections > li > div { + -moz-box-sizing: border-box; /* firefox */ + -ms-box-sizing: border-box; /* ie */ + -webkit-box-sizing: border-box; /* webkit */ + -khtml-box-sizing: border-box; /* konqueror */ + box-sizing: border-box; /* css3 */ +} + + +/*---------------------- Jump Page -----------------------------*/ +#jump_to, #jump_page { + margin: 0; + background: white; + -webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777; + -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px; + font: 16px Arial; + cursor: pointer; + text-align: right; + list-style: none; } -.toc { - max-height: 0; - overflow: hidden; - text-align: center; - font-size: 13px; - line-height: 20px; - -moz-transition: max-height 1s; - -webkit-transition: max-height 1s; - transition: max-height 1s; +#jump_to a { + text-decoration: none; +} + +#jump_to a.large { + display: none; +} +#jump_to a.small { + font-size: 22px; + font-weight: bold; + color: #676767; } - .header:hover .toc { - max-height: 500px; + +#jump_to, #jump_wrapper { + position: fixed; + right: 0; top: 0; + padding: 10px 15px; + margin:0; +} + +#jump_wrapper { + display: none; + padding:0; +} + +#jump_to:hover #jump_wrapper { + display: block; +} + +#jump_page { + padding: 5px 0 3px; + margin: 0 0 25px 25px; +} + +#jump_page .source { + display: block; + padding: 15px; + text-decoration: none; + border-top: 1px solid #eee; +} + +#jump_page .source:hover { + background: #f5f5ff; +} + +#jump_page .source:first-child { +} + +/*---------------------- Low resolutions (> 320px) ---------------------*/ +@media only screen and (min-width: 320px) { + .pilwrap { display: none; } + + ul.sections > li > div { + display: block; + padding:5px 10px 0 10px; + } + + ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol { + padding-left: 30px; + } + + ul.sections > li > div.content { + background: #f5f5ff; + overflow-x:auto; + -webkit-box-shadow: inset 0 0 5px #e5e5ee; + box-shadow: inset 0 0 5px #e5e5ee; + border: 1px solid #dedede; + margin:5px 10px 5px 10px; + padding-bottom: 5px; + } + + ul.sections > li > div.annotation pre { + margin: 7px 0 7px; + padding-left: 15px; + } + + ul.sections > li > div.annotation p tt, .annotation code { + background: #f8f8ff; + border: 1px solid #dedede; + font-size: 12px; + padding: 0 0.2em; + } +} + +/*---------------------- (> 481px) ---------------------*/ +@media only screen and (min-width: 481px) { + #container { + position: relative; + } + body { + background-color: #F5F5FF; + font-size: 15px; + line-height: 21px; + } + pre, tt, code { + line-height: 18px; + } + p, ul, ol { + margin: 0 0 15px; + } + + + #jump_to { + padding: 5px 10px; } - .toc h3 { - margin-top: 20px; + #jump_wrapper { + padding: 0; } - .toc ol { - margin: 0 0 20px 0; + #jump_to, #jump_page { + font: 10px Arial; + text-transform: uppercase; + } + #jump_page .source { + padding: 5px 10px; + } + #jump_to a.large { + display: inline-block; + } + #jump_to a.small { + display: none; + } + + + + #background { + position: absolute; + top: 0; bottom: 0; + width: 350px; + background: #fff; + border-right: 1px solid #e5e5ee; + z-index: -1; + } + + ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol { + padding-left: 40px; + } + + ul.sections > li { + white-space: nowrap; + } + + ul.sections > li > div { display: inline-block; + } + + ul.sections > li > div.annotation { + max-width: 350px; + min-width: 350px; + min-height: 5px; + padding: 13px; + overflow-x: hidden; + white-space: normal; + vertical-align: top; text-align: left; - list-style-type: upper-roman; } - .toc li { - font-family: 'novecento-bold'; + ul.sections > li > div.annotation pre { + margin: 15px 0 15px; + padding-left: 15px; + } + + ul.sections > li > div.content { + padding: 13px; + vertical-align: top; + background: #f5f5ff; + border: none; + -webkit-box-shadow: none; + box-shadow: none; + } + + .pilwrap { + position: relative; + display: inline; + } + + .pilcrow { + font: 12px Arial; + text-decoration: none; + color: #454545; + position: absolute; + top: 3px; left: -20px; + padding: 1px 2px; + opacity: 0; + -webkit-transition: opacity 0.2s linear; + } + .for-h1 .pilcrow { + top: 47px; + } + .for-h2 .pilcrow, .for-h3 .pilcrow, .for-h4 .pilcrow { + top: 35px; } - .toc li a { - font-family: 'aller-light'; - } + ul.sections > li > div.annotation:hover .pilcrow { + opacity: 1; + } +} + +/*---------------------- (> 1025px) ---------------------*/ +@media only screen and (min-width: 1025px) { -/*---------------------- Syntax Highlighting -----------------------------*/ + body { + font-size: 16px; + line-height: 24px; + } + #background { + width: 525px; + } + ul.sections > li > div.annotation { + max-width: 525px; + min-width: 525px; + padding: 10px 25px 1px 50px; + } + ul.sections > li > div.content { + padding: 9px 15px 16px 25px; + } +} + +/*---------------------- Syntax Highlighting -----------------------------*/ td.linenos { background-color: #f0f0f0; padding-right: 10px; } span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } body .hll { background-color: #ffffcc } diff --git a/documentation/docs/grammar.html b/documentation/docs/grammar.html index ac6e274987..5f29d9db26 100644 --- a/documentation/docs/grammar.html +++ b/documentation/docs/grammar.html @@ -4,129 +4,111 @@ grammar.coffee - + -
    -
    - -
    - -

    grammar.coffee

    - - - -
    -

    Table of Contents

    -
      - - -
    1. - - browser.coffee - -
    2. - - -
    3. - - cake.coffee - -
    4. - - -
    5. - - coffee-script.coffee - -
    6. - - -
    7. - - command.coffee - -
    8. - - -
    9. - - grammar.coffee - -
    10. - - -
    11. - - helpers.coffee - -
    12. - - -
    13. - - index.coffee - -
    14. - - -
    15. - - lexer.coffee - -
    16. - - -
    17. - - nodes.coffee - -
    18. - - -
    19. - - optparse.coffee - -
    20. - - -
    21. - - repl.coffee - -
    22. - - -
    23. - - rewriter.coffee - -
    24. - - -
    25. - - scope.litcoffee - -
    26. - - -
    27. - - sourcemap.coffee - -
    28. - -
    + - - -

    The CoffeeScript parser is generated by Jison + +

  • +
    + +
    + +
    +

    The CoffeeScript parser is generated by Jison from this grammar file. Jison is a bottom-up parser generator, similar in style to Bison, implemented in JavaScript. It can recognize LALR(1), LR(0), SLR(1), and LR(1) @@ -148,30 +130,54 @@

    Table of Contents

    - -
    {Parser} = require 'jison'
    - - +
    + +
    {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*\}/
    - - +
    + +
    unwrap = /^function\s*\(\)\s*\{\s*return\s*([\s\S]*);\s*\}/
    + +
  • +
  • +
    + +
    + +
    +

    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, @@ -180,35 +186,52 @@

    Jison DSL

    - -
    o = (patternString, action, options) ->
    +            
    + +
    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}())"
    - - + action = if match = unwrap.exec action then match[1] else "(#{action}())"
    + +
  • +
  • +
    + +
    + +
    +

    All runtime functions we need are defined on "yy"

    - -
      action = action.replace /\bnew /g, '$&yy.'
    -  action = action.replace /\b(?:Block\.wrap|extend)\b/g, 'yy.$&'
    - - +
    + +
      action = action.replace /\bnew /g, '$&yy.'
    +  action = action.replace /\b(?:Block\.wrap|extend)\b/g, 'yy.$&'
    + +
  • +
  • +
    + +
    + +
    +

    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.

    - -
      addLocationDataFn = (first, last) ->
    +            
    + +
      addLocationDataFn = (first, last) ->
         if not last
           "yy.addLocationDataFn(@#{first})"
         else
    @@ -217,17 +240,32 @@ 

    Jison DSL

    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]
    - - + [patternString, "$$ = #{addLocationDataFn(1, patternCount)}(#{action});", options]
    + +
  • +
  • +
    + +
    + +
    +

    Grammatical Rules

    - - +
    + +
  • +
  • +
    + +
    + +
    +

    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 @@ -241,68 +279,108 @@

    Grammatical Rules

    - -
    grammar =
    - - +
    + +
    grammar =
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      Root: [
    +            
    + +
      Root: [
         o '',                                       -> new Block
         o 'Body'
         o 'Block TERMINATOR'
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      Body: [
    +            
    + +
      Body: [
         o 'Line',                                   -> Block.wrap [$1]
         o 'Body TERMINATOR Line',                   -> $1.push $3
         o 'Body TERMINATOR'
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      Line: [
    +            
    + +
      Line: [
         o 'Expression'
         o 'Statement'
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    Pure statements which cannot be expressions.

    - -
      Statement: [
    +            
    + +
      Statement: [
         o 'Return'
         o 'Comment'
         o 'STATEMENT',                              -> new Literal $1
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    All the different types of expressions in our language. The basic unit of CoffeeScript is the Expression -- everything that can be an expression is one. Blocks serve as the building blocks of many other rules, making @@ -310,8 +388,9 @@

    Grammatical Rules

    - -
      Expression: [
    +            
    + +
      Expression: [
         o 'Value'
         o 'Invocation'
         o 'Code'
    @@ -324,59 +403,91 @@ 

    Grammatical Rules

    o 'Switch' o 'Class' o 'Throw' - ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      Block: [
    +            
    + +
      Block: [
         o 'INDENT OUTDENT',                         -> new Block
         o 'INDENT Body OUTDENT',                    -> $2
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    A literal identifier, a variable name or property.

    - -
      Identifier: [
    +            
    + +
      Identifier: [
         o 'IDENTIFIER',                             -> new Literal $1
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      AlphaNumeric: [
    +            
    + +
      AlphaNumeric: [
         o 'NUMBER',                                 -> new Literal $1
         o 'STRING',                                 -> new Literal $1
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      Literal: [
    +            
    + +
      Literal: [
         o 'AlphaNumeric'
         o 'JS',                                     -> new Literal $1
         o 'REGEX',                                  -> new Literal $1
    @@ -384,32 +495,48 @@ 

    Grammatical Rules

    o 'UNDEFINED', -> new Undefined o 'NULL', -> new Null o 'BOOL', -> new Bool $1 - ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      Assign: [
    +            
    + +
      Assign: [
         o 'Assignable = Expression',                -> new Assign $1, $3
         o 'Assignable = TERMINATOR Expression',     -> new Assign $1, $4
         o 'Assignable = INDENT Expression OUTDENT', -> new Assign $1, $4
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      AssignObj: [
    +            
    + +
      AssignObj: [
         o 'ObjAssignable',                          -> new Value $1
         o 'ObjAssignable : Expression',             -> new Assign LOC(1)(new Value($1)), $3, 'object'
         o 'ObjAssignable :
    @@ -421,206 +548,318 @@ 

    Grammatical Rules

    o 'Identifier' o 'AlphaNumeric' o 'ThisProperty' - ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    A return statement from a function body.

    - -
      Return: [
    +            
    + +
      Return: [
         o 'RETURN Expression',                      -> new Return $2
         o 'RETURN',                                 -> new Return
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    A block comment.

    - -
      Comment: [
    +            
    + +
      Comment: [
         o 'HERECOMMENT',                            -> new Comment $1
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    The Code node is the function literal. It's defined by an indented block of Block preceded by a function arrow, with an optional parameter list.

    - -
      Code: [
    +            
    + +
      Code: [
         o 'PARAM_START ParamList PARAM_END FuncGlyph Block', -> new Code $2, $5, $4
         o 'FuncGlyph Block',                        -> new Code [], $2, $1
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      FuncGlyph: [
    +            
    + +
      FuncGlyph: [
         o '->',                                     -> 'func'
         o '=>',                                     -> 'boundfunc'
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    An optional, trailing comma.

    - -
      OptComma: [
    +            
    + +
      OptComma: [
         o ''
         o ','
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      ParamList: [
    +            
    + +
      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
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      Param: [
    +            
    + +
      Param: [
         o 'ParamVar',                               -> new Param $1
         o 'ParamVar ...',                           -> new Param $1, null, on
         o 'ParamVar = Expression',                  -> new Param $1, $3
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    Function Parameters

    - -
      ParamVar: [
    +            
    + +
      ParamVar: [
         o 'Identifier'
         o 'ThisProperty'
         o 'Array'
         o 'Object'
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    A splat that occurs outside of a parameter list.

    - -
      Splat: [
    +            
    + +
      Splat: [
         o 'Expression ...',                         -> new Splat $1
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    Variables and properties that can be assigned to.

    - -
      SimpleAssignable: [
    +            
    + +
      SimpleAssignable: [
         o 'Identifier',                             -> new Value $1
         o 'Value Accessor',                         -> $1.add $2
         o 'Invocation Accessor',                    -> new Value $1, [].concat $2
         o 'ThisProperty'
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    Everything that can be assigned to.

    - -
      Assignable: [
    +            
    + +
      Assignable: [
         o 'SimpleAssignable'
         o 'Array',                                  -> new Value $1
         o 'Object',                                 -> new Value $1
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      Value: [
    +            
    + +
      Value: [
         o 'Assignable'
         o 'Literal',                                -> new Value $1
         o 'Parenthetical',                          -> new Value $1
         o 'Range',                                  -> new Value $1
         o 'This'
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      Accessor: [
    +            
    + +
      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'
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    Indexing into an object or array using bracket notation.

    - -
      Index: [
    +            
    + +
      Index: [
         o 'INDEX_START IndexValue INDEX_END',       -> $2
         o 'INDEX_SOAK  Index',                      -> extend $2, soak : yes
       ]
    @@ -628,47 +867,71 @@ 

    Grammatical Rules

    IndexValue: [ o 'Expression', -> new Index $1 o 'Slice', -> new Slice $1 - ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      Object: [
    +            
    + +
      Object: [
         o '{ AssignList OptComma }',                -> new Obj $2, $1.generated
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      AssignList: [
    +            
    + +
      AssignList: [
         o '',                                                       -> []
         o 'AssignObj',                                              -> [$1]
         o 'AssignList , AssignObj',                                 -> $1.concat $3
         o 'AssignList OptComma TERMINATOR AssignObj',               -> $1.concat $4
         o 'AssignList OptComma INDENT AssignList OptComma OUTDENT', -> $1.concat $4
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      Class: [
    +            
    + +
      Class: [
         o 'CLASS',                                           -> new Class
         o 'CLASS Block',                                     -> new Class null, null, $2
         o 'CLASS EXTENDS Expression',                        -> new Class null, $3
    @@ -677,216 +940,343 @@ 

    Grammatical Rules

    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 - ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    Ordinary function invocation, or a chained series of calls.

    - -
      Invocation: [
    +            
    + +
      Invocation: [
         o 'Value OptFuncExist Arguments',           -> new Call $1, $3, $2
         o 'Invocation OptFuncExist Arguments',      -> new Call $1, $3, $2
         o 'SUPER',                                  -> new Call 'super', [new Splat new Literal 'arguments']
         o 'SUPER Arguments',                        -> new Call 'super', $2
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    An optional existence check on a function.

    - -
      OptFuncExist: [
    +            
    + +
      OptFuncExist: [
         o '',                                       -> no
         o 'FUNC_EXIST',                             -> yes
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    The list of arguments to a function call.

    - -
      Arguments: [
    +            
    + +
      Arguments: [
         o 'CALL_START CALL_END',                    -> []
         o 'CALL_START ArgList OptComma CALL_END',   -> $2
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    A reference to the this current object.

    - -
      This: [
    +            
    + +
      This: [
         o 'THIS',                                   -> new Value new Literal 'this'
         o '@',                                      -> new Value new Literal 'this'
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    A reference to a property on this.

    - -
      ThisProperty: [
    +            
    + +
      ThisProperty: [
         o '@ Identifier',                           -> new Value LOC(1)(new Literal('this')), [LOC(2)(new Access($2))], 'this'
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    The array literal.

    - -
      Array: [
    +            
    + +
      Array: [
         o '[ ]',                                    -> new Arr []
         o '[ ArgList OptComma ]',                   -> new Arr $2
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    Inclusive and exclusive range dots.

    - -
      RangeDots: [
    +            
    + +
      RangeDots: [
         o '..',                                     -> 'inclusive'
         o '...',                                    -> 'exclusive'
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    The CoffeeScript range literal.

    - -
      Range: [
    +            
    + +
      Range: [
         o '[ Expression RangeDots Expression ]',    -> new Range $2, $4, $3
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    Array slice literals.

    - -
      Slice: [
    +            
    + +
      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
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    The ArgList is both the list of objects passed into a function call, as well as the contents of an array literal (i.e. comma-separated expressions). Newlines work as well.

    - -
      ArgList: [
    +            
    + +
      ArgList: [
         o 'Arg',                                              -> [$1]
         o 'ArgList , Arg',                                    -> $1.concat $3
         o 'ArgList OptComma TERMINATOR Arg',                  -> $1.concat $4
         o 'INDENT ArgList OptComma OUTDENT',                  -> $2
         o 'ArgList OptComma INDENT ArgList OptComma OUTDENT', -> $1.concat $4
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    Valid arguments are Blocks or Splats.

    - -
      Arg: [
    +            
    + +
      Arg: [
         o 'Expression'
         o 'Splat'
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    Just simple, comma-separated, required arguments (no fancy syntax). We need this to be separate from the ArgList for use in Switch blocks, where having the newlines wouldn't make sense.

    - -
      SimpleArgs: [
    +            
    + +
      SimpleArgs: [
         o 'Expression'
         o 'SimpleArgs , Expression',                -> [].concat $1, $3
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      Try: [
    +            
    + +
      Try: [
         o 'TRY Block',                              -> new Try $2
         o 'TRY Block Catch',                        -> new Try $2, $3[0], $3[1]
         o 'TRY Block FINALLY Block',                -> new Try $2, null, null, $4
         o 'TRY Block Catch FINALLY Block',          -> new Try $2, $3[0], $3[1], $5
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      Catch: [
    +            
    + +
      Catch: [
         o 'CATCH Identifier Block',                 -> [$2, $3]
         o 'CATCH Object Block',                     -> [LOC(2)(new Value($2)), $3]
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    Throw an exception object.

    - -
      Throw: [
    +            
    + +
      Throw: [
         o 'THROW Expression',                       -> new Throw $2
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    Parenthetical expressions. Note that the Parenthetical is a Value, not an Expression, so if you need to use an expression in a place where only values are accepted, wrapping it in parentheses will always do @@ -894,37 +1284,54 @@

    Grammatical Rules

    - -
      Parenthetical: [
    +            
    + +
      Parenthetical: [
         o '( Body )',                               -> new Parens $2
         o '( INDENT Body OUTDENT )',                -> new Parens $3
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    The condition portion of a while loop.

    - -
      WhileSource: [
    +            
    + +
      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
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    The while loop can either be normal, with a block of expressions to execute, or postfix, with a single expression. There is no do..while.

    - -
      While: [
    +            
    + +
      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])
    @@ -934,19 +1341,27 @@ 

    Grammatical Rules

    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] - ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    Array, object, and range comprehensions, at the most generic level. Comprehensions can either be normal, with a block of expressions to execute, or postfix, with a single expression.

    - -
      For: [
    +            
    + +
      For: [
         o 'Statement  ForBody',                     -> new For $1, $2
         o 'Expression ForBody',                     -> new For $1, $2
         o 'ForBody    Block',                       -> new For $2, $1
    @@ -960,50 +1375,74 @@ 

    Grammatical Rules

    ForStart: [ o 'FOR ForVariables', -> $2 o 'FOR OWN ForVariables', -> $3.own = yes; $3 - ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      ForValue: [
    +            
    + +
      ForValue: [
         o 'Identifier'
         o 'ThisProperty'
         o 'Array',                                  -> new Value $1
         o 'Object',                                 -> new Value $1
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    An array or range comprehension has variables for the current element and (optional) reference to the current index. Or, key, value, in the case of object comprehensions.

    - -
      ForVariables: [
    +            
    + +
      ForVariables: [
         o 'ForValue',                               -> [$1]
         o 'ForValue , ForValue',                    -> [$1, $3]
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    The source of a comprehension is an array or object with an optional guard clause. If it's an array comprehension, you can also choose to step through in fixed-size increments.

    - -
      ForSource: [
    +            
    + +
      ForSource: [
         o 'FORIN Expression',                               -> source: $2
         o 'FOROF Expression',                               -> source: $2, object: yes
         o 'FORIN Expression WHEN Expression',               -> source: $2, guard: $4
    @@ -1023,55 +1462,86 @@ 

    Grammatical Rules

    Whens: [ o 'When' o 'Whens When', -> $1.concat $2 - ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    An individual When clause, with action.

    - -
      When: [
    +            
    + +
      When: [
         o 'LEADING_WHEN SimpleArgs Block',            -> [[$2, $3]]
         o 'LEADING_WHEN SimpleArgs Block TERMINATOR', -> [[$2, $3]]
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    The most basic form of if is a condition and an action. The following if-related rules are broken up along these lines in order to avoid ambiguity.

    - -
      IfBlock: [
    +            
    + +
      IfBlock: [
         o 'IF Expression Block',                    -> new If $2, $3, type: $1
         o 'IfBlock ELSE IF Expression Block',       -> $1.addElse new If $4, $5, type: $3
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      If: [
    +            
    + +
      If: [
         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
    -  ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    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 @@ -1081,8 +1551,9 @@

    Grammatical Rules

    - -
      Operation: [
    +            
    + +
      Operation: [
         o 'UNARY Expression',                       -> new Op $1 , $2
         o '-     Expression',                      (-> new Op '-', $2), prec: 'UNARY'
         o '+     Expression',                      (-> new Op '+', $2), prec: 'UNARY'
    @@ -1090,17 +1561,25 @@ 

    Grammatical Rules

    o '-- SimpleAssignable', -> new Op '--', $2 o '++ SimpleAssignable', -> new Op '++', $2 o 'SimpleAssignable --', -> new Op '--', $1, null, true - o 'SimpleAssignable ++', -> new Op '++', $1, null, true
    - - + o 'SimpleAssignable ++', -> new Op '++', $1, null, true
    + +
  • +
  • +
    + +
    + +
    +

    The existential operator.

    - -
        o 'Expression ?',                           -> new Existence $1
    +            
    + +
        o 'Expression ?',                           -> new Existence $1
     
         o 'Expression +  Expression',               -> new Op '+' , $1, $3
         o 'Expression -  Expression',               -> new Op '-' , $1, $3
    @@ -1122,17 +1601,32 @@ 

    Grammatical Rules

    o 'SimpleAssignable COMPOUND_ASSIGN TERMINATOR Expression', -> new Assign $1, $4, $2 o 'SimpleAssignable EXTENDS Expression', -> new Extends $1, $3 - ]
    - - + ]
    + +
  • +
  • +
    + +
    + +
    +

    Precedence

    - - +
    + +
  • +
  • +
    + +
    + +
    +

    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: @@ -1143,8 +1637,9 @@

    Precedence

    (2 + 3) * 4
    - -
    operators = [
    +            
    + +
    operators = [
       ['left',      '.', '?.', '::', '?::']
       ['left',      'CALL_START', 'CALL_END']
       ['nonassoc',  '++', '--']
    @@ -1161,17 +1656,32 @@ 

    Precedence

    ['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) @@ -1179,36 +1689,45 @@

    Wrapping Up

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

    - -
    exports.parser = new Parser
    +            
    + +
    exports.parser = new Parser
       tokens      : tokens.join ' '
       bnf         : grammar
       operators   : operators.reverse()
       startSymbol : 'Root'
     
    -
    +
    + +
  • - -
    h
    -
    +
    diff --git a/documentation/docs/helpers.html b/documentation/docs/helpers.html index 00a9fb4ffe..e51b148363 100644 --- a/documentation/docs/helpers.html +++ b/documentation/docs/helpers.html @@ -4,129 +4,111 @@ helpers.coffee - + -
    -
    - -
    - -

    helpers.coffee

    - - - -
    -

    Table of Contents

    -
      - - -
    1. - - browser.coffee - -
    2. - - -
    3. - - cake.coffee - -
    4. - - -
    5. - - coffee-script.coffee - -
    6. - - -
    7. - - command.coffee - -
    8. - - -
    9. - - grammar.coffee - -
    10. - - -
    11. - - helpers.coffee - -
    12. - - -
    13. - - index.coffee - -
    14. - - -
    15. - - lexer.coffee - -
    16. - - -
    17. - - nodes.coffee - -
    18. - - -
    19. - - optparse.coffee - -
    20. - - -
    21. - - repl.coffee - -
    22. - - -
    23. - - rewriter.coffee - -
    24. - - -
    25. - - scope.litcoffee - -
    26. - - -
    27. - - sourcemap.coffee - -
    28. - -
    + - - +
  • +
    +

    helpers.coffee

    +
    +
  • -

    This file contains the common helper functions that we'd like to share among + + +

  • +
    + +
    + +
    +

    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. @@ -135,171 +117,268 @@

    Table of Contents

    - -
    exports.starts = (string, literal, start) ->
    -  literal is string.substr start, literal.length
    - - +
    + +
    exports.starts = (string, literal, start) ->
    +  literal is string.substr start, literal.length
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    exports.ends = (string, literal, back) ->
    +            
    + +
    exports.ends = (string, literal, back) ->
       len = literal.length
    -  literal is string.substr string.length - len - (back or 0), len
    - - + literal is string.substr string.length - len - (back or 0), len
    + +
  • +
  • +
    + +
    + +
    +

    Trim out all falsy values from an array.

    - -
    exports.compact = (array) ->
    -  item for item in array when item
    - - +
    + +
    exports.compact = (array) ->
    +  item for item in array when item
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    exports.count = (string, substr) ->
    +            
    + +
    exports.count = (string, substr) ->
       num = pos = 0
       return 1/0 unless substr.length
       num++ while pos = 1 + string.indexOf substr, pos
    -  num
    - - + num
    + +
  • +
  • +
    + +
    + +
    +

    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.merge = (options, overrides) ->
    -  extend (extend {}, options), overrides
    - - +
    + +
    exports.merge = (options, overrides) ->
    +  extend (extend {}, options), overrides
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    extend = exports.extend = (object, properties) ->
    +            
    + +
    extend = exports.extend = (object, properties) ->
       for key, val of properties
         object[key] = val
    -  object
    - - + object
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    exports.flatten = flatten = (array) ->
    +            
    + +
    exports.flatten = flatten = (array) ->
       flattened = []
       for element in array
         if element instanceof Array
           flattened = flattened.concat flatten element
         else
           flattened.push element
    -  flattened
    - - + flattened
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    exports.del = (obj, key) ->
    +            
    + +
    exports.del = (obj, key) ->
       val =  obj[key]
       delete obj[key]
    -  val
    - - + val
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    exports.last = (array, back) -> array[array.length - (back or 0) - 1]
    - - +
    + +
    exports.last = (array, back) -> array[array.length - (back or 0) - 1]
    + +
  • +
  • +
    + +
    + +
    +

    Typical Array::some

    - -
    exports.some = Array::some ? (fn) ->
    +            
    + +
    exports.some = Array::some ? (fn) ->
       return true for e in this when fn e
    -  false
    - - + false
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    buildLocationData = (first, last) ->
    +            
    + +
    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
    - - + last_column: last.last_column
    + +
  • +
  • +
    + +
    + +
    +

    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.

    - -
    exports.addLocationDataFn = (first, last) ->
    +            
    + +
    exports.addLocationDataFn = (first, last) ->
         (obj) ->
           if ((typeof obj) is 'object') and (!!obj['updateLocationDataIfMissing'])
             obj.updateLocationDataIfMissing buildLocationData(first, last)
     
    -      return obj
    - - + return obj
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    exports.locationDataToString = (obj) ->
    +            
    + +
    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
     
    @@ -307,49 +386,73 @@ 

    Table of Contents

    "#{locationData.first_line + 1}:#{locationData.first_column + 1}-" + "#{locationData.last_line + 1}:#{locationData.last_column + 1}" else - "No location data"
    - - + "No location data"
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    exports.baseFileName = (file, stripExt = no) ->
    +            
    + +
    exports.baseFileName = (file, stripExt = no) ->
       parts = file.split('/')
       file = parts[parts.length - 1]
       return file unless stripExt
       parts = file.split('.')
       parts.pop()
       parts.pop() if parts[parts.length - 1] is 'coffee'
    -  parts.join('.')
    - - + parts.join('.')
    + +
  • +
  • +
    + +
    + +
    +

    Determine if a filename represents a CoffeeScript file.

    - -
    exports.isCoffee = (file) -> /\.((lit)?coffee|coffee\.md)$/.test file
    - - +
    + +
    exports.isCoffee = (file) -> /\.((lit)?coffee|coffee\.md)$/.test file
    + +
  • +
  • +
    + +
    + +
    +

    Determine if a filename represents a Literate CoffeeScript file.

    - -
    exports.isLiterate = (file) -> /\.(litcoffee|coffee\.md)$/.test file
    +            
    + +
    exports.isLiterate = (file) -> /\.(litcoffee|coffee\.md)$/.test file
     
    -
    +
    + +
  • - -
    h
    -
    +
    diff --git a/documentation/docs/index.html b/documentation/docs/index.html index d3284afd3d..7e121330fa 100644 --- a/documentation/docs/index.html +++ b/documentation/docs/index.html @@ -4,139 +4,122 @@ index.coffee - + -
    -
    - - - - -

    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'
     
    -
    +
    + +
  • - -
    h
    -
    +
    diff --git a/documentation/docs/lexer.html b/documentation/docs/lexer.html index 5ec3834592..79ffdb7654 100644 --- a/documentation/docs/lexer.html +++ b/documentation/docs/lexer.html @@ -4,129 +4,111 @@ lexer.coffee - + -
    -
    - -
    - -

    lexer.coffee

    - - - -
    -

    Table of Contents

    -
      - - -
    1. - - browser.coffee - -
    2. - - -
    3. - - cake.coffee - -
    4. - - -
    5. - - coffee-script.coffee - -
    6. - - -
    7. - - command.coffee - -
    8. - - -
    9. - - grammar.coffee - -
    10. - - -
    11. - - helpers.coffee - -
    12. - - -
    13. - - index.coffee - -
    14. - - -
    15. - - lexer.coffee - -
    16. - - -
    17. - - nodes.coffee - -
    18. - - -
    19. - - optparse.coffee - -
    20. - - -
    21. - - repl.coffee - -
    22. - - -
    23. - - rewriter.coffee - -
    24. - - -
    25. - - scope.litcoffee - -
    26. - - -
    27. - - sourcemap.coffee - -
    28. - -
    + - - -

    The CoffeeScript Lexer. Uses a series of token-matching regexes to attempt + +

  • +
    + +
    + +
    +

    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: @@ -142,40 +124,72 @@

    Table of Contents

    - -
    {Rewriter, INVERSES} = require './rewriter'
    - - +
    + +
    {Rewriter, INVERSES} = require './rewriter'
    + +
  • +
  • +
    + +
    + +
    +

    Import the helpers we need.

    - -
    {count, starts, compact, last, locationDataToString} = require './helpers'
    - - +
    + +
    {count, starts, compact, last, locationDataToString} = require './helpers'
    + +
  • +
  • +
    + +
    + +
    +

    The Lexer Class

    - - +
    + +
  • +
  • +
    + +
    + +
    +

    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
    - - +
    + +
    exports.Lexer = class 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 @@ -192,8 +206,9 @@

    The Lexer Class

    - -
      tokenize: (code, opts = {}) ->
    +            
    + +
      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.
    @@ -206,19 +221,27 @@ 

    The Lexer Class

    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.
    - - + code = @clean code # The stripped, cleaned original source code.
    + +
  • +
  • +
    + +
    + +
    +

    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.

    - -
        i = 0
    +            
    + +
        i = 0
         while @chunk = code[i..]
           consumed = \
                @identifierToken() or
    @@ -230,36 +253,52 @@ 

    The Lexer Class

    @numberToken() or @regexToken() or @jsToken() or - @literalToken()
    - - + @literalToken()
    + +
  • +
  • +
    + +
    + +
    +

    Update position

    - -
          [@chunkLine, @chunkColumn] = @getLineAndColumnFromChunk consumed
    +            
    + +
          [@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
    - - + (new Rewriter).rewrite @tokens
    + +
  • +
  • +
    + +
    + +
    +

    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.

    - -
      clean: (code) ->
    +            
    + +
      clean: (code) ->
         code = code.slice(1) if code.charCodeAt(0) is BOM
         code = code.replace(/\r/g, '').replace TRAILING_SPACES, ''
         if WHITESPACE.test code
    @@ -272,17 +311,32 @@ 

    The Lexer Class

    else '# ' + line code = lines.join '\n' - code
    - - + 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 @@ -292,20 +346,29 @@

    Tokenizers

    - -
      identifierToken: ->
    +            
    + +
      identifierToken: ->
         return 0 unless match = IDENTIFIER.exec @chunk
    -    [input, id, colon] = match
    - - + [input, id, colon] = match
    + +
  • +
  • +
    + +
    + +
    +

    Preserve length of id for location data

    - -
        idLength = id.length
    +            
    + +
        idLength = id.length
         poppedToken = undefined
     
         if id is 'own' and @tag() is 'FOR'
    @@ -362,18 +425,26 @@ 

    Tokenizers

    colonOffset = input.lastIndexOf ':' @token ':', ':', colonOffset, colon.length - input.length
    - - + input.length
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      numberToken: ->
    +            
    + +
      numberToken: ->
         return 0 unless match = NUMBER.exec @chunk
         number = match[0]
         if /^0[BOX]/.test number
    @@ -390,18 +461,26 @@ 

    Tokenizers

    if binaryLiteral = /^0b([01]+)/.exec number number = '0x' + (parseInt binaryLiteral[1], 2).toString 16 @token 'NUMBER', number, 0, lexedLength - lexedLength
    - - + lexedLength
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      stringToken: ->
    +            
    + +
      stringToken: ->
         switch @chunk.charAt 0
           when "'"
             return 0 unless match = SIMPLESTR.exec @chunk
    @@ -417,18 +496,26 @@ 

    Tokenizers

    return 0 if octalEsc = /^(?:\\.|[^\\])*\\(?:0[0-7]|[1-7])/.test string @error "octal escape sequences #{string} are not allowed" - string.length
    - - + string.length
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      heredocToken: ->
    +            
    + +
      heredocToken: ->
         return 0 unless match = HEREDOC.exec @chunk
         heredoc = match[0]
         quote = heredoc.charAt 0
    @@ -437,17 +524,25 @@ 

    Tokenizers

    @interpolateString doc, heredoc: yes, strOffset: 3, lexedLength: heredoc.length else @token 'STRING', @makeString(doc, quote, yes), 0, heredoc.length - heredoc.length
    - - + heredoc.length
    + +
  • +
  • +
    + +
    + +
    +

    Matches and consumes comments.

    - -
      commentToken: ->
    +            
    + +
      commentToken: ->
         return 0 unless match = @chunk.match COMMENT
         [comment, here] = match
         if here
    @@ -455,32 +550,48 @@ 

    Tokenizers

    (@sanitizeHeredoc here, herecomment: true, indent: Array(@indent + 1).join(' ')), 0, comment.length - comment.length
    - - + comment.length
    + +
  • +
  • +
    + +
    + +
    +

    Matches JavaScript interpolated directly into the source via backticks.

    - -
      jsToken: ->
    +            
    + +
      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
    - - + script.length
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      regexToken: ->
    +            
    + +
      regexToken: ->
         return 0 if @chunk.charAt(0) isnt '/'
         if match = HEREGEX.exec @chunk
           length = @heregexToken match
    @@ -493,17 +604,25 @@ 

    Tokenizers

    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
    - - + match.length
    + +
  • +
  • +
    + +
    + +
    +

    Matches multiline extended regular expressions.

    - -
      heregexToken: (match) ->
    +            
    + +
      heregexToken: (match) ->
         [heregex, body, flags] = match
         if 0 > body.indexOf '#{'
           re = body.replace(HEREGEX_OMIT, '').replace(/\//g, '\\/')
    @@ -518,17 +637,25 @@ 

    Tokenizers

    if tag is 'TOKENS' tokens.push value... else if tag is 'NEOSTRING' - continue unless value = value.replace HEREGEX_OMIT, ''
    - - + continue unless value = value.replace HEREGEX_OMIT, ''
    + +
  • +
  • +
    + +
    + +
    +

    Convert NEOSTRING into STRING

    - -
            value = value.replace /\\/g, '\\\\'
    +            
    + +
            value = value.replace /\\/g, '\\\\'
             token[0] = 'STRING'
             token[1] = @makeString(value, '"', yes)
             tokens.push token
    @@ -538,43 +665,66 @@ 

    Tokenizers

    prev = last @tokens plusToken = ['+', '+'] plusToken[2] = prev[2] # Copy location data - tokens.push plusToken
    - - + tokens.push plusToken
    + +
  • +
  • +
    + +
    + +
    +

    Remove the extra "+"

    - -
        tokens.pop()
    +            
    + +
        tokens.pop()
     
         unless tokens[0]?[0] is 'STRING'
           @token 'STRING', '""', 0, 0
           @token '+', '+', 0, 0
         @tokens.push tokens...
     
    -    if flags
    - - + if flags
    + +
  • +
  • +
    + +
    + +
    +

    Find the flags in the heregex

    - -
          flagsOffset = heregex.lastIndexOf flags
    +            
    + +
          flagsOffset = heregex.lastIndexOf flags
           @token ',', ',', flagsOffset, 0
           @token 'STRING', '"' + flags + '"', flagsOffset, flags.length
     
         @token ')', ')', heregex.length-1, 0
    -    heregex.length
    - - + heregex.length
    + +
  • +
  • +
    + +
    + +
    +

    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: @@ -588,8 +738,9 @@

    Tokenizers

    - -
      lineToken: ->
    +            
    + +
      lineToken: ->
         return 0 unless match = MULTI_DENT.exec @chunk
         indent = match[0]
         @seenFor = no
    @@ -613,18 +764,26 @@ 

    Tokenizers

    @indebt = 0 @outdentToken @indent - size, noNewlines, indent.length @indent = size - indent.length
    - - + indent.length
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      outdentToken: (moveOut, noNewlines, outdentLength) ->
    +            
    + +
      outdentToken: (moveOut, noNewlines, outdentLength) ->
         while moveOut > 0
           len = @indents.length - 1
           if @indents[len] is undefined
    @@ -645,53 +804,84 @@ 

    Tokenizers

    @tokens.pop() while @value() is ';' @token 'TERMINATOR', '\n', outdentLength, 0 unless @tag() is 'TERMINATOR' or noNewlines - this
    - - + this
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      whitespaceToken: ->
    +            
    + +
      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
    - - + if match then match[0].length else 0
    + +
  • +
  • +
    + +
    + +
    +

    Generate a newline token. Consecutive newlines get merged together.

    - -
      newlineToken: (offset) ->
    +            
    + +
      newlineToken: (offset) ->
         @tokens.pop() while @value() is ';'
         @token 'TERMINATOR', '\n', offset, 0 unless @tag() is 'TERMINATOR'
    -    this
    - - + this
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      suppressNewlines: ->
    +            
    + +
      suppressNewlines: ->
         @tokens.pop() if @value() is '\\'
    -    this
    - - + this
    + +
  • +
  • +
    + +
    + +
    +

    We treat all other single characters as a token. E.g.: ( ) , . ! 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 @@ -700,8 +890,9 @@

    Tokenizers

    - -
      literalToken: ->
    +            
    + +
      literalToken: ->
         if match = OPERATOR.exec @chunk
           [value] = match
           @tagParameters() if CODE.test value
    @@ -737,24 +928,40 @@ 

    Tokenizers

    when '(', '{', '[' then @ends.push INVERSES[value] when ')', '}', ']' then @pair value @token tag, value - value.length
    - - + value.length
    + +
  • +
  • +
    + +
    + +
    +

    Token Manipulators

    - - +
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      sanitizeHeredoc: (doc, options) ->
    +            
    + +
      sanitizeHeredoc: (doc, options) ->
         {indent, herecomment} = options
         if herecomment
           if HEREDOC_ILLEGAL.test doc
    @@ -767,19 +974,27 @@ 

    Token Manipulators

    doc = doc.replace /// \n #{indent} ///g, '\n' if indent doc = doc.replace /\n# \n/g, '\n\n' if @literate doc = doc.replace /^\n/, '' unless herecomment - doc
    - - + doc
    + +
  • +
  • +
    + +
    + +
    +

    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.

    - -
      tagParameters: ->
    +            
    + +
      tagParameters: ->
         return this if @tag() isnt ')'
         stack = []
         {tokens} = this
    @@ -795,22 +1010,37 @@ 

    Token Manipulators

    tok[0] = 'PARAM_START' return this else return this - this
    - - + this
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      closeIndentation: ->
    -    @outdentToken @indent
    - - +
    + +
      closeIndentation: ->
    +    @outdentToken @indent
    + +
  • +
  • +
    + +
    + +
    +

    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 @@ -818,8 +1048,9 @@

    Token Manipulators

    - -
      balancedString: (str, end) ->
    +            
    + +
      balancedString: (str, end) ->
         continueCount = 0
         stack = [end]
         for i in [1...str.length]
    @@ -845,11 +1076,18 @@ 

    Token Manipulators

    else if end is '"' and prev is '#' and letter is '{' stack.push end = '}' prev = letter - @error "missing #{ stack.pop() }, starting"
    - - + @error "missing #{ stack.pop() }, starting"
    + +
  • +
  • +
    + +
    + +
    +

    Expand variables and expressions inside double-quoted strings using Ruby-like notation for substitution of arbitrary expressions. @@ -872,34 +1110,51 @@

    Token Manipulators

    current chunk.
  • - -
      interpolateString: (str, options = {}) ->
    +            
    + +
      interpolateString: (str, options = {}) ->
         {heredoc, regex, offsetInChunk, strOffset, lexedLength} = options
         offsetInChunk = offsetInChunk || 0
         strOffset = strOffset || 0
    -    lexedLength = lexedLength || str.length
    - - + lexedLength = lexedLength || str.length
    + + +
  • +
    + +
    + +
    +

    Clip leading \n from heredoc

    - -
        if heredoc and str.length > 0 and str[0] == '\n'
    +            
    + +
        if heredoc and str.length > 0 and str[0] == '\n'
           str = str[1...]
    -      strOffset++
    - - + strOffset++
    + +
  • +
  • +
    + +
    + +
    +

    Parse the string.

    - -
        tokens = []
    +            
    + +
        tokens = []
         pi = 0
         i  = -1
         while letter = str.charAt i += 1
    @@ -908,17 +1163,25 @@ 

    Token Manipulators

    continue unless letter is '#' and str.charAt(i+1) is '{' and (expr = @balancedString str[i + 1..], '}') - continue
    - - + continue
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
          tokens.push @makeToken('NEOSTRING', str[pi...i], strOffset + pi) if pi < i
    +            
    + +
          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)
    @@ -928,147 +1191,242 @@ 

    Token Manipulators

    if len = nested.length if len > 1 nested.unshift @makeToken '(', '(', strOffset + i + 1, 0 - nested.push @makeToken ')', ')', strOffset + i + 1 + inner.length, 0
    - - + nested.push @makeToken ')', ')', strOffset + i + 1 + inner.length, 0
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
              tokens.push ['TOKENS', nested]
    +            
    + +
              tokens.push ['TOKENS', nested]
           i += expr.length
           pi = i + 1
    -    tokens.push @makeToken('NEOSTRING', str[pi..], strOffset + pi) if i > pi < str.length
    - - + tokens.push @makeToken('NEOSTRING', str[pi..], strOffset + pi) if i > pi < str.length
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
        return tokens if regex
    - - +
    + +
        return tokens if regex
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
        return @token 'STRING', '""', offsetInChunk, lexedLength unless tokens.length
    - - +
    + +
        return @token 'STRING', '""', offsetInChunk, lexedLength unless tokens.length
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
        tokens.unshift @makeToken('NEOSTRING', '', offsetInChunk) unless tokens[0][0] is 'NEOSTRING'
    +            
    + +
        tokens.unshift @makeToken('NEOSTRING', '', offsetInChunk) unless tokens[0][0] is 'NEOSTRING'
     
    -    @token '(', '(', offsetInChunk, 0 if interpolated = tokens.length > 1
    - - + @token '(', '(', offsetInChunk, 0 if interpolated = tokens.length > 1
    + +
  • +
  • +
    + +
    + +
    +

    Push all the tokens

    - -
        for token, i in tokens
    +            
    + +
        for token, i in tokens
           [tag, value] = token
    -      if i
    - - + if i
    + +
  • +
  • +
    + +
    + +
    +

    Create a 0-length "+" token.

    - -
            plusToken = @token '+', '+' if i
    +            
    + +
            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'
    - - + if tag is 'TOKENS'
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
            @tokens.push value...
    -      else if tag is 'NEOSTRING'
    - - +
    + +
            @tokens.push value...
    +      else if tag is 'NEOSTRING'
    + +
  • +
  • +
    + +
    + +
    +

    Convert NEOSTRING into STRING

    - -
            token[0] = 'STRING'
    +            
    + +
            token[0] = 'STRING'
             token[1] = @makeString value, '"', heredoc
             @tokens.push token
           else
             @error "Unexpected #{tag}"
         @token ')', ')', offsetInChunk + lexedLength, 0 if interpolated
    -    tokens
    - - + tokens
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      pair: (tag) ->
    +            
    + +
      pair: (tag) ->
         unless tag is wanted = last @ends
    -      @error "unmatched #{tag}" unless 'OUTDENT' is wanted
    - - + @error "unmatched #{tag}" unless 'OUTDENT' is wanted
    + +
  • +
  • +
    + +
    + +
    +

    Auto-close INDENT to support syntax like this:

    el.click((event) ->
       el.hide())
    - -
          @indent -= size = last @indents
    +            
    + +
          @indent -= size = last @indents
           @outdentToken size, true
           return @pair tag
    -    @ends.pop()
    - - + @ends.pop()
    + +
  • +
  • +
    + +
    + +
    +

    Helpers

    - - +
    + +
  • +
  • +
    + +
    + +
    +

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

    @@ -1076,8 +1434,9 @@

    Helpers

    - -
      getLineAndColumnFromChunk: (offset) ->
    +            
    + +
      getLineAndColumnFromChunk: (offset) ->
         if offset is 0
           return [@chunkLine, @chunkColumn]
     
    @@ -1095,42 +1454,65 @@ 

    Helpers

    else column += string.length - [@chunkLine + lineCount, column]
    - - + [@chunkLine + lineCount, column]
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      makeToken: (tag, value, offsetInChunk = 0, length = value.length) ->
    +            
    + +
      makeToken: (tag, value, offsetInChunk = 0, length = value.length) ->
         locationData = {}
         [locationData.first_line, locationData.first_column] =
    -      @getLineAndColumnFromChunk offsetInChunk
    - - + @getLineAndColumnFromChunk offsetInChunk
    + +
  • +
  • +
    + +
    + +
    +

    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
    +            
    + +
        lastCharacter = Math.max 0, length - 1
         [locationData.last_line, locationData.last_column] =
           @getLineAndColumnFromChunk offsetInChunk + (length - 1)
     
         token = [tag, value, locationData]
     
    -    token
    - - + token
    + +
  • +
  • +
    + +
    + +
    +

    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 @@ -1141,125 +1523,206 @@

    Helpers

    - -
      token: (tag, value, offsetInChunk, length) ->
    +            
    + +
      token: (tag, value, offsetInChunk, length) ->
         token = @makeToken tag, value, offsetInChunk, length
         @tokens.push token
    -    token
    - - + token
    + +
  • +
  • +
    + +
    + +
    +

    Peek at a tag in the current token stream.

    - -
      tag: (index, tag) ->
    -    (tok = last @tokens, index) and if tag then tok[0] = tag else tok[0]
    - - +
    + +
      tag: (index, tag) ->
    +    (tok = last @tokens, index) and if tag then tok[0] = tag else tok[0]
    + +
  • +
  • +
    + +
    + +
    +

    Peek at a value in the current token stream.

    - -
      value: (index, val) ->
    -    (tok = last @tokens, index) and if val then tok[1] = val else tok[1]
    - - +
    + +
      value: (index, val) ->
    +    (tok = last @tokens, index) and if val then tok[1] = val else tok[1]
    + +
  • +
  • +
    + +
    + +
    +

    Are we in the midst of an unfinished expression?

    - -
      unfinished: ->
    +            
    + +
      unfinished: ->
         LINE_CONTINUER.test(@chunk) or
         @tag() in ['\\', '.', '?.', '?::', 'UNARY', 'MATH', '+', '-', 'SHIFT', 'RELATION'
    -               'COMPARE', 'LOGIC', 'THROW', 'EXTENDS']
    - - + 'COMPARE', 'LOGIC', 'THROW', 'EXTENDS']
    + +
  • +
  • +
    + +
    + +
    +

    Converts newlines for string literals.

    - -
      escapeLines: (str, heredoc) ->
    -    str.replace MULTILINER, if heredoc then '\\n' else ''
    - - +
    + +
      escapeLines: (str, heredoc) ->
    +    str.replace MULTILINER, if heredoc then '\\n' else ''
    + +
  • +
  • +
    + +
    + +
    +

    Constructs a string token by escaping quotes and newlines.

    - -
      makeString: (body, quote, heredoc) ->
    +            
    + +
      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
    - - + quote + @escapeLines(body, heredoc) + quote
    + +
  • +
  • +
    + +
    + +
    +

    Throws a syntax error on the current @line.

    - -
      error: (message) ->
    - - +
    + +
      error: (message) ->
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
        throw SyntaxError "#{message} on line #{ @chunkLine + 1 }"
    - - +
    + +
        throw SyntaxError "#{message} on line #{ @chunkLine + 1 }"
    + +
  • +
  • +
    + +
    + +
    +

    Constants

    - - +
    + +
  • +
  • +
    + +
    + +
    +

    Keywords that CoffeeScript shares in common with JavaScript.

    - -
    JS_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'
    -]
    - - +]
    + +
  • +
  • +
    + +
    + +
    +

    CoffeeScript-only keywords.

    - -
    COFFEE_KEYWORDS = ['undefined', 'then', 'unless', 'until', 'loop', 'of', 'by', 'when']
    +            
    + +
    COFFEE_KEYWORDS = ['undefined', 'then', 'unless', 'until', 'loop', 'of', 'by', 'when']
     
     COFFEE_ALIAS_MAP =
       and  : '&&'
    @@ -1273,60 +1736,92 @@ 

    Constants

    off : 'false' COFFEE_ALIASES = (key for key of COFFEE_ALIAS_MAP) -COFFEE_KEYWORDS = COFFEE_KEYWORDS.concat COFFEE_ALIASES
    - - +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.

    - -
    RESERVED = [
    +            
    + +
    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'
     ]
     
    -STRICT_PROSCRIBED = ['arguments', 'eval']
    - - +STRICT_PROSCRIBED = ['arguments', 'eval']
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    JS_FORBIDDEN = JS_KEYWORDS.concat(RESERVED).concat(STRICT_PROSCRIBED)
    +            
    + +
    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
    - - +exports.STRICT_PROSCRIBED = STRICT_PROSCRIBED
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    BOM = 65279
    - - +
    + +
    BOM = 65279
    + +
  • +
  • +
    + +
    + +
    +

    Token matching regexes.

    - -
    IDENTIFIER = /// ^
    +            
    + +
    IDENTIFIER = /// ^
       ( [$A-Za-z_\x7f-\uffff][$\w\x7f-\uffff]* )
       ( [^\n\S]* : (?!:) )?  # Is this a property name?
     ///
    @@ -1362,17 +1857,25 @@ 

    Constants

    SIMPLESTR = /^'[^\\']*(?:\\.[^\\']*)*'/ -JSTOKEN = /^`[^\\`]*(?:\\.[^\\`]*)*`/
    - - +JSTOKEN = /^`[^\\`]*(?:\\.[^\\`]*)*`/
    + +
  • +
  • +
    + +
    + +
    +

    Regex-matching-regexes.

    - -
    REGEX = /// ^
    +            
    + +
    REGEX = /// ^
       (/ (?! [\s=] )   # disallow leading whitespace or equals signs
       [^ [ / \n \\ ]*  # every other thing
       (?:
    @@ -1388,17 +1891,25 @@ 

    Constants

    HEREGEX = /// ^ /{3} ([\s\S]+?) /{3} ([imgy]{0,4}) (?!\w) /// -HEREGEX_OMIT = /\s+(?:#.*)?/g
    - - +HEREGEX_OMIT = /\s+(?:#.*)?/g
    + +
  • +
  • +
    + +
    + +
    +

    Token cleaning regexes.

    - -
    MULTILINER      = /\n/g
    +            
    + +
    MULTILINER      = /\n/g
     
     HEREDOC_INDENT  = /\n+([^\n\S]*)/g
     
    @@ -1406,93 +1917,164 @@ 

    Constants

    LINE_CONTINUER = /// ^ \s* (?: , | \??\.(?![.\d]) | :: ) /// -TRAILING_SPACES = /\s+$/
    - - +TRAILING_SPACES = /\s+$/
    + +
  • +
  • +
    + +
    + +
    +

    Compound assignment tokens.

    - -
    COMPOUND_ASSIGN = [
    +            
    + +
    COMPOUND_ASSIGN = [
       '-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?=', '<<=', '>>=', '>>>=', '&=', '^=', '|='
    -]
    - - +]
    + +
  • +
  • +
    + +
    + +
    +

    Unary tokens.

    - -
    UNARY   = ['!', '~', 'NEW', 'TYPEOF', 'DELETE', 'DO']
    - - +
    + +
    UNARY   = ['!', '~', 'NEW', 'TYPEOF', 'DELETE', 'DO']
    + +
  • +
  • +
    + +
    + +
    +

    Logical tokens.

    - -
    LOGIC   = ['&&', '||', '&', '|', '^']
    - - +
    + +
    LOGIC   = ['&&', '||', '&', '|', '^']
    + +
  • +
  • +
    + +
    + +
    +

    Bit-shifting tokens.

    - -
    SHIFT   = ['<<', '>>', '>>>']
    - - +
    + +
    SHIFT   = ['<<', '>>', '>>>']
    + +
  • +
  • +
    + +
    + +
    +

    Comparison tokens.

    - -
    COMPARE = ['==', '!=', '<', '>', '<=', '>=']
    - - +
    + +
    COMPARE = ['==', '!=', '<', '>', '<=', '>=']
    + +
  • +
  • +
    + +
    + +
    +

    Mathematical tokens.

    - -
    MATH    = ['*', '/', '%']
    - - +
    + +
    MATH    = ['*', '/', '%']
    + +
  • +
  • +
    + +
    + +
    +

    Relational tokens that are negatable with not prefix.

    - -
    RELATION = ['IN', 'OF', 'INSTANCEOF']
    - - +
    + +
    RELATION = ['IN', 'OF', 'INSTANCEOF']
    + +
  • +
  • +
    + +
    + +
    +

    Boolean tokens.

    - -
    BOOL = ['TRUE', 'FALSE']
    - - +
    + +
    BOOL = ['TRUE', 'FALSE']
    + +
  • +
  • +
    + +
    + +
    +

    Tokens which a regular expression will never immediately follow, but which a division operator might. @@ -1504,49 +2086,74 @@

    Constants

    - -
    NOT_REGEX = ['NUMBER', 'REGEX', 'BOOL', 'NULL', 'UNDEFINED', '++', '--', ']']
    - - +
    + +
    NOT_REGEX = ['NUMBER', 'REGEX', 'BOOL', 'NULL', 'UNDEFINED', '++', '--', ']']
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
    NOT_SPACED_REGEX = NOT_REGEX.concat ')', '}', 'THIS', 'IDENTIFIER', 'STRING'
    - - +
    + +
    NOT_SPACED_REGEX = NOT_REGEX.concat ')', '}', 'THIS', 'IDENTIFIER', 'STRING'
    + +
  • +
  • +
    + +
    + +
    +

    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.

    - -
    CALLABLE  = ['IDENTIFIER', 'STRING', 'REGEX', ')', ']', '}', '?', '::', '@', 'THIS', 'SUPER']
    -INDEXABLE = CALLABLE.concat 'NUMBER', 'BOOL', 'NULL', 'UNDEFINED'
    - - +
    + +
    CALLABLE  = ['IDENTIFIER', 'STRING', 'REGEX', ')', ']', '}', '?', '::', '@', 'THIS', 'SUPER']
    +INDEXABLE = CALLABLE.concat 'NUMBER', 'BOOL', 'NULL', 'UNDEFINED'
    + +
  • +
  • +
    + +
    + +
    +

    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.

    - -
    LINE_BREAK = ['INDENT', 'OUTDENT', 'TERMINATOR']
    +            
    + +
    LINE_BREAK = ['INDENT', 'OUTDENT', 'TERMINATOR']
     
    -
    +
    + +
  • - -
    h
    -
    +
    diff --git a/documentation/docs/nodes.html b/documentation/docs/nodes.html index 4f139b89cb..073f7cb71a 100644 --- a/documentation/docs/nodes.html +++ b/documentation/docs/nodes.html @@ -4,129 +4,111 @@ nodes.coffee - + -
    -
    - -
    - -

    nodes.coffee

    - - - -
    -

    Table of Contents

    -
      - - -
    1. - - browser.coffee - -
    2. - - -
    3. - - cake.coffee - -
    4. - - -
    5. - - coffee-script.coffee - -
    6. - - -
    7. - - command.coffee - -
    8. - - -
    9. - - grammar.coffee - -
    10. - - -
    11. - - helpers.coffee - -
    12. - - -
    13. - - index.coffee - -
    14. - - -
    15. - - lexer.coffee - -
    16. - - -
    17. - - nodes.coffee - -
    18. - - -
    19. - - optparse.coffee - -
    20. - - -
    21. - - repl.coffee - -
    22. - - -
    23. - - rewriter.coffee - -
    24. - - -
    25. - - scope.litcoffee - -
    26. - - -
    27. - - sourcemap.coffee - -
    28. - -
    + - - -

    nodes.coffee contains all of the node classes for the syntax tree. Most + +

  • +
    + +
    + +
    +

    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. @@ -134,49 +116,81 @@

    Table of Contents

    - -
    Error.stackTraceLimit = Infinity
    +            
    + +
    Error.stackTraceLimit = Infinity
     
     {Scope} = require './scope'
    -{RESERVED, STRICT_PROSCRIBED} = require './lexer'
    - - +{RESERVED, STRICT_PROSCRIBED} = require './lexer'
    + +
  • +
  • +
    + +
    + +
    +

    Import the helpers we plan to use.

    - -
    {compact, flatten, extend, merge, del, starts, ends, last, some, addLocationDataFn, locationDataToString} = require './helpers'
    - - +
    + +
    {compact, flatten, extend, merge, del, starts, ends, last, some, addLocationDataFn, locationDataToString} = require './helpers'
    + +
  • +
  • +
    + +
    + +
    +

    Functions required by parser

    - -
    exports.extend = extend
    -exports.addLocationDataFn = addLocationDataFn
    - - +
    + +
    exports.extend = extend
    +exports.addLocationDataFn = addLocationDataFn
    + +
  • +
  • +
    + +
    + +
    +

    Constant functions for nodes that don't need customization.

    - -
    YES     = -> yes
    +            
    + +
    YES     = -> yes
     NO      = -> no
     THIS    = -> this
    -NEGATE  = -> @negated = not @negated; this
    - - +NEGATE = -> @negated = not @negated; this
    + +
  • +
  • +
    + +
    + +
    +

    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 @@ -185,30 +199,46 @@

    CodeFragment

    - -
    exports.CodeFragment = class CodeFragment
    +            
    + +
    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)]}"
    - - + "#{@code}#{[if @locationData then ": " + locationDataToString(@locationData)]}"
    + +
  • +
  • +
    + +
    + +
    +

    Convert an array of CodeFragments into a string.

    - -
    fragmentsToText = (fragments) ->
    -  (fragment.code for fragment in fragments).join('')
    - - +
    + +
    fragmentsToText = (fragments) ->
    +  (fragment.code for fragment in fragments).join('')
    + +
  • +
  • +
    + +
    + +
    +

    Base

    The Base is the abstract base class for all nodes in the syntax tree. Each subclass implements the compileNode method, which performs the @@ -222,15 +252,23 @@

    Base

    - -
    exports.Base = class Base
    +            
    + +
    exports.Base = class Base
     
       compile: (o, lvl) ->
    -    fragmentsToText @compileToFragments o, lvl
    - - + fragmentsToText @compileToFragments o, lvl
    + +
  • +
  • +
    + +
    + +
    +

    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 @@ -240,8 +278,9 @@

    Base

    - -
      compileToFragments: (o, lvl) ->
    +            
    + +
      compileToFragments: (o, lvl) ->
         o        = extend {}, o
         o.level  = lvl if lvl
         node     = @unfoldSoak(o) or this
    @@ -249,26 +288,41 @@ 

    Base

    if o.level is LEVEL_TOP or not node.isStatement(o) node.compileNode o else - node.compileClosure o
    - - + node.compileClosure o
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      compileClosure: (o) ->
    +            
    + +
      compileClosure: (o) ->
         if @jumps()
           throw SyntaxError 'cannot use a pure statement in an expression.'
         o.sharedScope = yes
    -    Closure.wrap(this).compileNode o
    - - + Closure.wrap(this).compileNode o
    + +
  • +
  • +
    + +
    + +
    +

    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. @@ -280,8 +334,9 @@

    Base

    - -
      cache: (o, level, reused) ->
    +            
    + +
      cache: (o, level, reused) ->
         unless @isComplex()
           ref = if level then @compileToFragments o, level else this
           [ref, ref]
    @@ -291,28 +346,43 @@ 

    Base

    if level then [sub.compileToFragments(o, level), [@makeCode(ref.value)]] else [sub, ref] cacheToCodeFragments: (cacheValues) -> - [fragmentsToText(cacheValues[0]), fragmentsToText(cacheValues[1])]
    - - + [fragmentsToText(cacheValues[0]), fragmentsToText(cacheValues[1])]
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      makeReturn: (res) ->
    +            
    + +
      makeReturn: (res) ->
         me = @unwrapAll()
         if res
           new Call new Literal("#{res}.push"), [me]
         else
    -      new Return me
    - - + new Return me
    + +
  • +
  • +
    + +
    + +
    +

    Does this node, or any of its children, contain a node of a certain kind? Recursively traverses down the children of the nodes, yielding to a block and returning true when the block finds a match. contains does not cross @@ -320,64 +390,97 @@

    Base

    - -
      contains: (pred) ->
    +            
    + +
      contains: (pred) ->
         contains = no
         @traverseChildren no, (node) ->
           if pred node
             contains = yes
             return no
    -    contains
    - - + contains
    + +
  • +
  • +
    + +
    + +
    +

    Is this node of a certain type, or does it contain the type?

    - -
      containsType: (type) ->
    -    this instanceof type or @contains (node) -> node instanceof type
    - - +
    + +
      containsType: (type) ->
    +    this instanceof type or @contains (node) -> node instanceof type
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      lastNonComment: (list) ->
    +            
    + +
      lastNonComment: (list) ->
         i = list.length
         return list[i] while i-- when list[i] not instanceof Comment
    -    null
    - - + null
    + +
  • +
  • +
    + +
    + +
    +

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

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

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

    - -
      eachChild: (func) ->
    +            
    + +
      eachChild: (func) ->
         return this unless @children
         for attr in @children when @[attr]
           for child in flatten [@[attr]]
    @@ -395,18 +498,26 @@ 

    Base

    unwrapAll: -> node = this continue until node is node = node.unwrap() - node
    - - + node
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      children: []
    +            
    + +
      children: []
     
       isStatement     : NO
       jumps           : NO
    @@ -415,28 +526,44 @@ 

    Base

    isAssignable : NO unwrap : THIS - unfoldSoak : NO
    - - + unfoldSoak : NO
    + +
  • +
  • +
    + +
    + +
    +

    Is this node used to assign a certain variable?

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

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

    - -
      updateLocationDataIfMissing: (locationData) ->
    +            
    + +
      updateLocationDataIfMissing: (locationData) ->
         if not @locationData
           @locationData = {}
           extend @locationData, locationData
    @@ -448,28 +575,43 @@ 

    Base

    new CodeFragment this, code wrapInBraces: (fragments) -> - [].concat @makeCode('('), fragments, @makeCode(')')
    - - + [].concat @makeCode('('), fragments, @makeCode(')')
    + +
  • +
  • +
    + +
    + +
    +

    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.

    - -
      joinFragmentArrays: (fragmentsList, joinStr) ->
    +            
    + +
      joinFragmentArrays: (fragmentsList, joinStr) ->
         answer = []
         for fragments,i in fragmentsList
           if i then answer.push @makeCode joinStr
           answer = answer.concat fragments
    -    answer
    - - + answer
    + +
  • +
  • +
    + +
    + +
    +

    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 @@ -477,69 +619,110 @@

    Block

    - -
    exports.Block = class Block extends Base
    +            
    + +
    exports.Block = class Block extends Base
       constructor: (nodes) ->
         @expressions = compact flatten nodes or []
     
    -  children: ['expressions']
    - - + children: ['expressions']
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      push: (node) ->
    +            
    + +
      push: (node) ->
         @expressions.push node
    -    this
    - - + this
    + +
  • +
  • +
    + +
    + +
    +

    Remove and return the last expression of this expression list.

    - -
      pop: ->
    -    @expressions.pop()
    - - +
    + +
      pop: ->
    +    @expressions.pop()
    + +
  • +
  • +
    + +
    + +
    +

    Add an expression at the beginning of this expression list.

    - -
      unshift: (node) ->
    +            
    + +
      unshift: (node) ->
         @expressions.unshift node
    -    this
    - - + this
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      unwrap: ->
    -    if @expressions.length is 1 then @expressions[0] else this
    - - +
    + +
      unwrap: ->
    +    if @expressions.length is 1 then @expressions[0] else this
    + +
  • +
  • +
    + +
    + +
    +

    Is this an empty block of code?

    - -
      isEmpty: ->
    +            
    + +
      isEmpty: ->
         not @expressions.length
     
       isStatement: (o) ->
    @@ -549,18 +732,26 @@ 

    Block

    jumps: (o) -> for exp in @expressions - return exp if exp.jumps o
    - - + return exp if exp.jumps o
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      makeReturn: (res) ->
    +            
    + +
      makeReturn: (res) ->
         len = @expressions.length
         while len--
           expr = @expressions[len]
    @@ -568,30 +759,46 @@ 

    Block

    @expressions[len] = expr.makeReturn res @expressions.splice(len, 1) if expr instanceof Return and not expr.expression break - this
    - - + this
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      compileToFragments: (o = {}, level) ->
    -    if o.scope then super o, level else @compileRoot o
    - - +
    + +
      compileToFragments: (o = {}, level) ->
    +    if o.scope then super o, level else @compileRoot o
    + +
  • +
  • +
    + +
    + +
    +

    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.

    - -
      compileNode: (o) ->
    +            
    + +
      compileNode: (o) ->
         @tab  = o.indent
         top   = o.level is LEVEL_TOP
         compiledNodes = []
    @@ -600,19 +807,27 @@ 

    Block

    node = node.unwrapAll() node = (node.unfoldSoak(o) or node) - if node instanceof Block
    - - + if node instanceof Block
    + +
  • +
  • +
    + +
    + +
    +

    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

    - -
            compiledNodes.push node.compileNode o
    +            
    + +
            compiledNodes.push node.compileNode o
           else if top
             node.front = true
             fragments = node.compileToFragments o
    @@ -631,11 +846,18 @@ 

    Block

    answer = @joinFragmentArrays(compiledNodes, ', ') else answer = [@makeCode "void 0"] - if compiledNodes.length > 1 and o.level >= LEVEL_LIST then @wrapInBraces answer else answer
    - - + if compiledNodes.length > 1 and o.level >= LEVEL_LIST then @wrapInBraces answer else answer
    + +
  • +
  • +
    + +
    + +
    +

    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, @@ -643,8 +865,9 @@

    Block

    - -
      compileRoot: (o) ->
    +            
    + +
      compileRoot: (o) ->
         o.indent  = if o.bare then '' else TAB
         o.scope   = new Scope null, this, null
         o.level   = LEVEL_TOP
    @@ -662,18 +885,26 @@ 

    Block

    @expressions = rest fragments = @compileWithDeclarations o return fragments if o.bare - [].concat prelude, @makeCode("(function() {\n"), fragments, @makeCode("\n}).call(this);\n")
    - - + [].concat prelude, @makeCode("(function() {\n"), fragments, @makeCode("\n}).call(this);\n")
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      compileWithDeclarations: (o) ->
    +            
    + +
      compileWithDeclarations: (o) ->
         fragments = []
         post = []
         for exp, i in @expressions
    @@ -699,24 +930,39 @@ 

    Block

    fragments.push @makeCode ",\n#{@tab + TAB}" if declars fragments.push @makeCode (scope.assignedVariables().join ",\n#{@tab + TAB}") fragments.push @makeCode ';\n' - fragments.concat post
    - - + fragments.concat post
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      @wrap: (nodes) ->
    +            
    + +
      @wrap: (nodes) ->
         return nodes[0] if nodes.length is 1 and nodes[0] instanceof Block
    -    new Block nodes
    - - + new Block nodes
    + +
  • +
  • +
    + +
    + +
    +

    Literal

    Literals are static values that can be passed through directly into JavaScript without translation, such as: strings, numbers, @@ -724,8 +970,9 @@

    Literal

    - -
    exports.Literal = class Literal extends Base
    +            
    + +
    exports.Literal = class Literal extends Base
       constructor: (@value) ->
     
       makeReturn: ->
    @@ -774,19 +1021,27 @@ 

    Literal

    isAssignable: NO isComplex: NO compileNode: -> [@makeCode @val] - constructor: (@val) ->
    - - + constructor: (@val) ->
    + +
  • +
  • +
    + +
    + +
    +

    Return

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

    - -
    exports.Return = class Return extends Base
    +            
    + +
    exports.Return = class Return extends Base
       constructor: (expr) ->
         @expression = expr if expr and not expr.unwrap().isUndefined
     
    @@ -801,33 +1056,49 @@ 

    Return

    if expr and expr not instanceof Return then expr.compileToFragments o, level else super o, level compileNode: (o) -> - answer = []
    - - + answer = []
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
        answer.push @makeCode(@tab + "return#{[" " if @expression]}")
    +            
    + +
        answer.push @makeCode(@tab + "return#{[" " if @expression]}")
         if @expression
           answer = answer.concat @expression.compileToFragments(o, LEVEL_PAREN)
         answer.push @makeCode ";"
    -    return answer
    - - + return answer
    + +
  • +
  • +
    + +
    + +
    +

    Value

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

    - -
    exports.Value = class Value extends Base
    +            
    + +
    exports.Value = class Value extends Base
       constructor: (base, props, tag) ->
         return base if not props and base instanceof Value
         @base       = base
    @@ -835,32 +1106,48 @@ 

    Value

    @[tag] = true if tag return this - children: ['base', 'properties']
    - - + children: ['base', 'properties']
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      add: (props) ->
    +            
    + +
      add: (props) ->
         @properties = @properties.concat props
         this
     
       hasProperties: ->
    -    !!@properties.length
    - - + !!@properties.length
    + +
  • +
  • +
    + +
    + +
    +

    Some boolean checks for the benefit of other nodes.

    - -
      isArray        : -> not @properties.length and @base instanceof Arr
    +            
    + +
      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
    @@ -879,31 +1166,47 @@ 

    Value

    (@base instanceof Obj) and (not onlyGenerated or @base.generated) isSplice: -> - last(@properties) instanceof Slice
    - - + last(@properties) instanceof Slice
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      unwrap: ->
    -    if @properties.length then this else @base
    - - +
    + +
      unwrap: ->
    +    if @properties.length then this else @base
    + +
  • +
  • +
    + +
    + +
    +

    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

    - -
      cacheReference: (o) ->
    +            
    + +
      cacheReference: (o) ->
         name = last @properties
         if @properties.length < 2 and not @base.isComplex() and not name?.isComplex()
           return [this, this]  # `a` `a.b`
    @@ -916,11 +1219,18 @@ 

    Value

    nref = new Literal o.scope.freeVariable 'name' name = new Index new Assign nref, name.index nref = new Index nref - [base.add(name), new Value(bref or base.base, [nref or name])]
    - - + [base.add(name), new Value(bref or base.base, [nref or name])]
    + +
  • +
  • +
    + +
    + +
    +

    We compile a value to JavaScript by compiling and joining each property. Things get much more interesting if the chain of properties has soak operators ?. interspersed. Then we have to take care not to accidentally @@ -928,8 +1238,9 @@

    Value

    - -
      compileNode: (o) ->
    +            
    + +
      compileNode: (o) ->
         @base.front = @front
         props = @properties
         fragments = @base.compileToFragments o, (if props.length then LEVEL_ACCESS else null)
    @@ -937,17 +1248,25 @@ 

    Value

    fragments.push @makeCode '.' for prop in props fragments.push (prop.compileToFragments o)... - fragments
    - - + fragments
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      unfoldSoak: (o) ->
    +            
    + +
      unfoldSoak: (o) ->
         @unfoldedSoak ?= do =>
           if ifn = @base.unfoldSoak o
             ifn.body.properties.push @properties...
    @@ -961,19 +1280,27 @@ 

    Value

    fst = new Parens new Assign ref, fst snd.base = ref return new If new Existence(fst), snd, soak: on - no
    - - + no
    + +
  • +
  • +
    + +
    + +
    +

    Comment

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

    - -
    exports.Comment = class Comment extends Base
    +            
    + +
    exports.Comment = class Comment extends Base
       constructor: (@comment) ->
     
       isStatement:     YES
    @@ -982,52 +1309,76 @@ 

    Comment

    compileNode: (o, level) -> code = '/*' + multident(@comment, @tab) + "\n#{@tab}*/\n" code = o.indent + code if (level or o.level) is LEVEL_TOP - [@makeCode code]
    - - + [@makeCode code]
    + +
  • +
  • +
    + +
    + +
    +

    Call

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

    - -
    exports.Call = class Call extends Base
    +            
    + +
    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']
    - - + children: ['variable', 'args']
    + +
  • +
  • +
    + +
    + +
    +

    Tag this invocation as creating a new instance.

    - -
      newInstance: ->
    +            
    + +
      newInstance: ->
         base = @variable?.base or @variable
         if base instanceof Call and not base.isNew
           base.newInstance()
         else
           @isNew = true
    -    this
    - - + this
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      superReference: (o) ->
    +            
    + +
      superReference: (o) ->
         method = o.scope.namedMethod()
         if method?.klass
           accesses = [new Access(new Literal '__super__')]
    @@ -1037,29 +1388,45 @@ 

    Call

    else if method?.ctor "#{method.name}.__super__.constructor" else - throw SyntaxError 'cannot call super outside of an instance method.'
    - - + throw SyntaxError 'cannot call super outside of an instance method.'
    + +
  • +
  • +
    + +
    + +
    +

    The appropriate this value for a super call.

    - -
      superThis : (o) ->
    +            
    + +
      superThis : (o) ->
         method = o.scope.method
    -    (method and not method.klass and method.context) or "this"
    - - + (method and not method.klass and method.context) or "this"
    + +
  • +
  • +
    + +
    + +
    +

    Soaked chained invocations unfold into if/else ternary structures.

    - -
      unfoldSoak: (o) ->
    +            
    + +
      unfoldSoak: (o) ->
         if @soak
           if @variable
             return ifn if ifn = unfoldSoak o, this, 'variable'
    @@ -1088,17 +1455,25 @@ 

    Call

    else call.variable.base = ifn ifn = unfoldSoak o, call, 'variable' - ifn
    - - + ifn
    + +
  • +
  • +
    + +
    + +
    +

    Compile a vanilla function call.

    - -
      compileNode: (o) ->
    +            
    + +
      compileNode: (o) ->
         @variable?.front = @front
         compiledArray = Splat.compileSplattedArray o, @args, true
         if compiledArray.length
    @@ -1119,11 +1494,18 @@ 

    Call

    fragments.push @makeCode "(" fragments.push compiledArgs... fragments.push @makeCode ")" - fragments
    - - + fragments
    + +
  • +
  • +
    + +
    + +
    +

    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 @@ -1134,8 +1516,9 @@

    Call

    - -
      compileSplat: (o, splatArgs) ->
    +            
    + +
      compileSplat: (o, splatArgs) ->
         if @isSuper
           return [].concat @makeCode("#{ @superReference o }.apply(#{@superThis(o)}, "),
             splatArgs, @makeCode(")")
    @@ -1168,11 +1551,18 @@ 

    Call

    else ref = 'null' answer = answer.concat fun - answer = answer.concat @makeCode(".apply(#{ref}, "), splatArgs, @makeCode(")")
    - - + answer = answer.concat @makeCode(".apply(#{ref}, "), splatArgs, @makeCode(")")
    + +
  • +
  • +
    + +
    + +
    +

    Extends

    Node to extend an object's prototype with an ancestor object. After goog.inherits from the @@ -1180,34 +1570,51 @@

    Extends

    - -
    exports.Extends = class Extends extends Base
    +            
    + +
    exports.Extends = class Extends extends Base
       constructor: (@child, @parent) ->
     
    -  children: ['child', 'parent']
    - - + children: ['child', 'parent']
    + +
  • +
  • +
    + +
    + +
    +

    Hooks one constructor into another's prototype chain.

    - -
      compileToFragments: (o) ->
    -    new Call(new Value(new Literal utility 'extends'), [@child, @parent]).compileToFragments o
    - - +
    + +
      compileToFragments: (o) ->
    +    new Call(new Value(new Literal utility 'extends'), [@child, @parent]).compileToFragments o
    + +
  • +
  • +
    + +
    + +
    +

    Access

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

    - -
    exports.Access = class Access extends Base
    +            
    + +
    exports.Access = class Access extends Base
       constructor: (@name, tag) ->
         @name.asKey = yes
         @soak  = tag is 'soak'
    @@ -1223,18 +1630,26 @@ 

    Access

    name.push @makeCode "]" name - isComplex: NO
    - - + isComplex: NO
    + +
  • +
  • +
    + +
    + +
    +

    Index

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

    - -
    exports.Index = class Index extends Base
    +            
    + +
    exports.Index = class Index extends Base
       constructor: (@index) ->
     
       children: ['index']
    @@ -1243,11 +1658,18 @@ 

    Index

    [].concat @makeCode("["), (@index.compileToFragments o, LEVEL_PAREN), @makeCode("]") isComplex: -> - @index.isComplex()
    - - + @index.isComplex()
    + +
  • +
  • +
    + +
    + +
    +

    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 @@ -1255,88 +1677,129 @@

    Range

    - -
    exports.Range = class Range extends Base
    +            
    + +
    exports.Range = class Range extends Base
     
       children: ['from', 'to']
     
       constructor: (@from, @to, tag) ->
         @exclusive = tag is 'exclusive'
    -    @equals = if @exclusive then '' else '='
    - - + @equals = if @exclusive then '' else '='
    + +
  • +
  • +
    + +
    + +
    +

    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.

    - -
      compileVariables: (o) ->
    +            
    + +
      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
    - - + @stepNum = @stepVar.match(SIMPLENUM) if @stepVar
    + +
  • +
  • +
    + +
    + +
    +

    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) ->
    +            
    + +
      compileNode: (o) ->
         @compileVariables o unless @fromVar
    -    return @compileArray(o) unless o.index
    - - + return @compileArray(o) unless o.index
    + +
  • +
  • +
    + +
    + +
    +

    Set up endpoints.

    - -
        known    = @fromNum and @toNum
    +            
    + +
        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}"]
    - - + [lt, gt] = ["#{idx} <#{@equals}", "#{idx} >#{@equals}"]
    + +
  • +
  • +
    + +
    + +
    +

    Generate the condition.

    - -
        condPart = if @stepNum
    +            
    + +
        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}"
    - - + "#{cond} ? #{lt} #{@toVar} : #{gt} #{@toVar}"
    + +
  • +
  • +
    + +
    + +
    +

    Generate the step.

    - -
        stepPart = if @stepVar
    +            
    + +
        stepPart = if @stepVar
           "#{idx} += #{@stepVar}"
         else if known
           if namedIndex
    @@ -1350,27 +1813,43 @@ 

    Range

    "#{cond} ? #{idx}++ : #{idx}--" varPart = "#{idxName} = #{varPart}" if namedIndex - stepPart = "#{idxName} = #{stepPart}" if namedIndex
    - - + stepPart = "#{idxName} = #{stepPart}" if namedIndex
    + +
  • +
  • +
    + +
    + +
    +

    The final loop body.

    - -
        [@makeCode "#{varPart}; #{condPart}; #{stepPart}"]
    - - +
    + +
        [@makeCode "#{varPart}; #{condPart}; #{stepPart}"]
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      compileArray: (o) ->
    +            
    + +
      compileArray: (o) ->
         if @fromNum and @toNum and Math.abs(@fromNum - @toNum) <= 20
           range = [+@fromNum..+@toNum]
           range.pop() if @exclusive
    @@ -1389,11 +1868,18 @@ 

    Range

    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 ? ''})"]
    - - + [@makeCode "(function() {#{pre}\n#{idt}for (#{body})#{post}}).apply(this#{args ? ''})"]
    + +
  • +
  • +
    + +
    + +
    +

    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 @@ -1401,37 +1887,54 @@

    Slice

    - -
    exports.Slice = class Slice extends Base
    +            
    + +
    exports.Slice = class Slice extends Base
     
       children: ['range']
     
       constructor: (@range) ->
    -    super()
    - - + super()
    + +
  • +
  • +
    + +
    + +
    +

    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) ->
    +            
    + +
      compileNode: (o) ->
         {to, from} = @range
    -    fromCompiled = from and from.compileToFragments(o, LEVEL_PAREN) or [@makeCode '0']
    - - + fromCompiled = from and from.compileToFragments(o, LEVEL_PAREN) or [@makeCode '0']
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
        if to
    +            
    + +
        if to
           compiled     = to.compileToFragments o, LEVEL_PAREN
           compiledText = fragmentsToText compiled
           if not (not @range.exclusive and +compiledText is -1)
    @@ -1442,18 +1945,26 @@ 

    Slice

    else compiled = to.compileToFragments o, LEVEL_ACCESS "+#{fragmentsToText compiled} + 1 || 9e9" - [@makeCode ".slice(#{ fragmentsToText fromCompiled }#{ toStr or '' })"]
    - - + [@makeCode ".slice(#{ fragmentsToText fromCompiled }#{ toStr or '' })"]
    + +
  • +
  • +
    + +
    + +
    +

    Obj

    An object literal, nothing fancy.

    - -
    exports.Obj = class Obj extends Base
    +            
    + +
    exports.Obj = class Obj extends Base
       constructor: (props, @generated = false) ->
         @objects = @properties = props or []
     
    @@ -1491,18 +2002,26 @@ 

    Obj

    assigns: (name) -> for prop in @properties when prop.assigns name then return yes - no
    - - + no
    + +
  • +
  • +
    + +
    + +
    +

    Arr

    An array literal.

    - -
    exports.Arr = class Arr extends Base
    +            
    + +
    exports.Arr = class Arr extends Base
       constructor: (objs) ->
         @objects = objs or []
     
    @@ -1530,11 +2049,18 @@ 

    Arr

    assigns: (name) -> for obj in @objects when obj.assigns name then return yes - no
    - - + no
    + +
  • +
  • +
    + +
    + +
    +

    Class

    The CoffeeScript class definition. Initialize a Class with its name, an optional superclass, and a @@ -1542,23 +2068,32 @@

    Class

    - -
    exports.Class = class Class extends Base
    +            
    + +
    exports.Class = class Class extends Base
       constructor: (@variable, @parent, @body = new Block) ->
         @boundFuncs = []
         @body.classBody = yes
     
    -  children: ['variable', 'parent', 'body']
    - - + children: ['variable', 'parent', 'body']
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      determineName: ->
    +            
    + +
      determineName: ->
         return null unless @variable
         decl = if tail = last @variable.properties
           tail instanceof Access and tail.name.value
    @@ -1566,36 +2101,52 @@ 

    Class

    @variable.base.value if decl in STRICT_PROSCRIBED throw SyntaxError "variable name may not be #{decl}" - decl and= IDENTIFIER.test(decl) and decl
    - - + decl and= IDENTIFIER.test(decl) and decl
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      setContext: (name) ->
    +            
    + +
      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
    - - + node.context = name if node.bound
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      addBoundFunctions: (o) ->
    +            
    + +
      addBoundFunctions: (o) ->
         if @boundFuncs.length
           o.scope.assign '_this', 'this'
           for [name, func] in @boundFuncs
    @@ -1604,18 +2155,26 @@ 

    Class

    rhs = new Code func.params, body, 'boundfunc' bound = new Assign lhs, rhs @ctor.body.unshift bound - return
    - - + return
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      addProperties: (node, name, o) ->
    +            
    + +
      addProperties: (node, name, o) ->
         props = node.base.properties[..]
         exprs = while assign = props.shift()
           if assign instanceof Assign
    @@ -1643,17 +2202,25 @@ 

    Class

    @boundFuncs.push [base, func] func.bound = no assign - compact exprs
    - - + compact exprs
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      walkBody: (name, o) ->
    +            
    + +
      walkBody: (name, o) ->
         @traverseChildren false, (child) =>
           cont = true
           return false if child instanceof Class
    @@ -1663,35 +2230,51 @@ 

    Class

    cont = false exps[i] = @addProperties node, name, o child.expressions = exps = flatten exps - cont and child not instanceof Class
    - - + cont and child not instanceof 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.

    - -
      hoistDirectivePrologue: ->
    +            
    + +
      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
    - - + @directives = expressions.splice 0, index
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      ensureConstructor: (name) ->
    +            
    + +
      ensureConstructor: (name) ->
         if not @ctor
           @ctor = new Code
           @ctor.body.push new Literal "#{name}.__super__.constructor.apply(this, arguments)" if @parent
    @@ -1700,19 +2283,27 @@ 

    Class

    @body.expressions.unshift @ctor @ctor.ctor = @ctor.name = name @ctor.klass = null - @ctor.noReturn = yes
    - - + @ctor.noReturn = yes
    + +
  • +
  • +
    + +
    + +
    +

    Instead of generating the JavaScript string directly, we build up the equivalent syntax tree and compile that, in pieces. You can see the constructor, property assignments, and inheritance getting built out below.

    - -
      compileNode: (o) ->
    +            
    + +
      compileNode: (o) ->
         decl  = @determineName()
         name  = decl or '_Class'
         name = "_#{name}" if name.reserved
    @@ -1739,19 +2330,27 @@ 

    Class

    klass = new Parens call, yes klass = new Assign @variable, klass if @variable - klass.compileToFragments o
    - - + klass.compileToFragments o
    + +
  • +
  • +
    + +
    + +
    +

    Assign

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

    - -
    exports.Assign = class Assign extends Base
    +            
    + +
    exports.Assign = class Assign extends Base
       constructor: (@variable, @value, @context, options) ->
         @param = options and options.param
         @subpattern = options and options.subpattern
    @@ -1768,11 +2367,18 @@ 

    Assign

    @[if @context is 'object' then 'value' else 'variable'].assigns name unfoldSoak: (o) -> - unfoldSoak o, this, 'variable'
    - - + unfoldSoak o, this, 'variable'
    + +
  • +
  • +
    + +
    + +
    +

    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 @@ -1780,8 +2386,9 @@

    Assign

    - -
      compileNode: (o) ->
    +            
    + +
      compileNode: (o) ->
         if isValue = @variable instanceof Value
           return @compilePatternMatch o if @variable.isArray() or @variable.isObject()
           return @compileSplice       o if @variable.isSplice()
    @@ -1802,11 +2409,18 @@ 

    Assign

    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
    - - + if o.level <= LEVEL_LIST then answer else @wrapInBraces answer
    + +
  • +
  • +
    + +
    + +
    +

    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 @@ -1814,8 +2428,9 @@

    Assign

    - -
      compilePatternMatch: (o) ->
    +            
    + +
      compilePatternMatch: (o) ->
         top       = o.level is LEVEL_TOP
         {value}   = this
         {objects} = @variable.base
    @@ -1823,17 +2438,25 @@ 

    Assign

    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
    - - + if top and olen is 1 and (obj = objects[0]) not instanceof Splat
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
          if obj instanceof Assign
    +            
    + +
          if obj instanceof Assign
             {variable: {base: idx}, value: obj} = obj
           else
             idx = if isObject
    @@ -1849,54 +2472,86 @@ 

    Assign

    vvar = value.compileToFragments o, LEVEL_LIST vvarText = fragmentsToText vvar assigns = [] - splat = false
    - - + splat = false
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
        if not IDENTIFIER.test(vvarText) or @variable.assigns(vvarText)
    +            
    + +
        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
    - - + for obj, i in objects
    + +
  • +
  • +
    + +
    + +
    +

    A regular array pattern-match.

    - -
          idx = i
    +            
    + +
          idx = i
           if isObject
    -        if obj instanceof Assign
    - - + if obj instanceof Assign
    + +
  • +
  • +
    + +
    + +
    +

    A regular object pattern-match.

    - -
              {variable: {base: idx}, value: obj} = obj
    -        else
    - - +
    + +
              {variable: {base: idx}, value: obj} = obj
    +        else
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
              if obj.base instanceof Parens
    +            
    + +
              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
    @@ -1928,45 +2583,69 @@ 

    Assign

    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
    - - + if o.level < LEVEL_LIST then fragments else @wrapInBraces fragments
    + +
  • +
  • +
    + +
    + +
    +

    When compiling a conditional assignment, take care to ensure that the operands are only evaluated once, even though we have to reference them more than once.

    - -
      compileConditional: (o) ->
    -    [left, right] = @variable.cacheReference o
    - - +
    + +
      compileConditional: (o) ->
    +    [left, right] = @variable.cacheReference o
    + +
  • +
  • +
    + +
    + +
    +

    Disallow conditional assignment of undefined variables.

    - -
        if not left.properties.length and left.base instanceof Literal and
    +            
    + +
        if not left.properties.length and left.base instanceof Literal and
                left.base.value != "this" and not o.scope.check left.base.value
           throw new Error "the variable \"#{left.base.value}\" can't be assigned with #{@context} because it has not been defined."
         if "?" in @context then o.isExistentialEquals = true
    -    new Op(@context[...-1], left, new Assign(right, @value, '=') ).compileToFragments o
    - - + new Op(@context[...-1], left, new Assign(right, @value, '=') ).compileToFragments o
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      compileSplice: (o) ->
    +            
    + +
      compileSplice: (o) ->
         {range: {from, to, exclusive}} = @variable.properties.pop()
         name = @variable.compile o
         if from
    @@ -1984,11 +2663,18 @@ 

    Assign

    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
    - - + if o.level > LEVEL_TOP then @wrapInBraces answer else answer
    + +
  • +
  • +
    + +
    + +
    +

    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 @@ -1996,8 +2682,9 @@

    Code

    - -
    exports.Code = class Code extends Base
    +            
    + +
    exports.Code = class Code extends Base
       constructor: (params, body, tag) ->
         @params  = params or []
         @body    = body or new Block
    @@ -2008,11 +2695,18 @@ 

    Code

    isStatement: -> !!@ctor - jumps: NO
    - - + jumps: NO
    + +
  • +
  • +
    + +
    + +
    +

    Compilation creates a new scope unless explicitly asked to share with the outer scope. Handles splat parameters in the parameter list by peeking at the JavaScript arguments object. If the function is bound with the => @@ -2021,8 +2715,9 @@

    Code

    - -
      compileNode: (o) ->
    +            
    + +
      compileNode: (o) ->
         o.scope         = new Scope o.scope, @body, this
         o.scope.shared  = del(o, 'sharedScope')
         o.indent        += TAB
    @@ -2080,36 +2775,59 @@ 

    Code

    answer.push @makeCode '}' return [@makeCode(@tab), answer...] if @ctor - if @front or (o.level >= LEVEL_ACCESS) then @wrapInBraces answer else answer
    - - + if @front or (o.level >= LEVEL_ACCESS) then @wrapInBraces answer else answer
    + +
  • +
  • +
    + +
    + +
    +

    A list of parameter names, excluding those generated by the compiler.

    - -
      paramNames: ->
    +            
    + +
      paramNames: ->
         names = []
         names.push param.names()... for param in @params
    -    names
    - - + names
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      traverseChildren: (crossScope, func) ->
    -    super(crossScope, func) if crossScope
    - - +
    + +
      traverseChildren: (crossScope, func) ->
    +    super(crossScope, func) if crossScope
    + +
  • +
  • +
    + +
    + +
    +

    Param

    A parameter in a function definition. Beyond a typical Javascript parameter, these parameters can also attach themselves to the context of the function, @@ -2117,8 +2835,9 @@

    Param

    - -
    exports.Param = class Param extends Base
    +            
    + +
    exports.Param = class Param extends Base
       constructor: (@name, @value, @splat) ->
         if (name = @name.unwrapAll().value) in STRICT_PROSCRIBED
           throw SyntaxError "parameter name \"#{name}\" is not allowed"
    @@ -2142,11 +2861,18 @@ 

    Param

    @reference = node isComplex: -> - @name.isComplex()
    - - + @name.isComplex()
    + +
  • +
  • +
    + +
    + +
    +

    Finds the name or names of a Param; useful for detecting duplicates. In a sense, a destructured parameter represents multiple JS parameters, thus this method returns an Array of names. @@ -2157,103 +2883,168 @@

    Param

    - -
      names: (name = @name)->
    +            
    + +
      names: (name = @name)->
         atParam = (obj) ->
           {value} = obj.properties[0].name
    -      return if value.reserved then [] else [value]
    - - + return if value.reserved then [] else [value]
    + +
  • +
  • +
    + +
    + +
    +
    • simple literals foo
    - -
        return [name.value] if name instanceof Literal
    - - +
    + +
        return [name.value] if name instanceof Literal
    + +
  • +
  • +
    + +
    + +
    +
    • at-params @foo
    - -
        return atParam(name) if name instanceof Value
    +            
    + +
        return atParam(name) if name instanceof Value
         names = []
    -    for obj in name.objects
    - - + for obj in name.objects
    + +
  • +
  • +
    + +
    + +
    +
    • assignments within destructured parameters {foo:bar}
    - -
          if obj instanceof Assign
    -        names.push @names(obj.value.unwrap())...
    - - +
    + +
          if obj instanceof Assign
    +        names.push @names(obj.value.unwrap())...
    + +
  • +
  • +
    + +
    + +
    +
    • splats within destructured parameters [xs...]
    - -
          else if obj instanceof Splat
    +            
    + +
          else if obj instanceof Splat
             names.push obj.name.unwrap().value
    -      else if obj instanceof Value
    - - + else if obj instanceof Value
    + +
  • +
  • +
    + +
    + +
    +
    • destructured parameters within destructured parameters [{a}]
    - -
            if obj.isArray() or obj.isObject()
    -          names.push @names(obj.base)...
    - - +
    + +
            if obj.isArray() or obj.isObject()
    +          names.push @names(obj.base)...
    + +
  • +
  • +
    + +
    + +
    +
    • at-params within destructured parameters {@foo}
    - -
            else if obj.this
    -          names.push atParam(obj)...
    - - +
    + +
            else if obj.this
    +          names.push atParam(obj)...
    + +
  • +
  • +
    + +
    + +
    +
    • simple destructured parameters {foo}
    - -
            else names.push obj.base.value
    +            
    + +
            else names.push obj.base.value
           else
             throw SyntaxError "illegal parameter #{obj.compile()}"
    -    names
    - - + names
    + +
  • +
  • +
    + +
    + +
    +

    Splat

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

    - -
    exports.Splat = class Splat extends Base
    +            
    + +
    exports.Splat = class Splat extends Base
     
       children: ['name']
     
    @@ -2268,18 +3059,26 @@ 

    Splat

    compileToFragments: (o) -> @name.compileToFragments o - unwrap: -> @name
    - - + unwrap: -> @name
    + +
  • +
  • +
    + +
    + +
    +

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

    - -
      @compileSplattedArray: (o, list, apply) ->
    +            
    + +
      @compileSplattedArray: (o, list, apply) ->
         index = -1
         continue while (node = list[++index]) and node not instanceof Splat
         return [] if index >= list.length
    @@ -2301,11 +3100,18 @@ 

    Splat

    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(")")
    - - + [].concat list[0].makeCode("["), base, list[index].makeCode("].concat("), concatPart, (last list).makeCode(")")
    + +
  • +
  • +
    + +
    + +
    +

    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 @@ -2313,8 +3119,9 @@

    While

    - -
    exports.While = class While extends Base
    +            
    + +
    exports.While = class While extends Base
       constructor: (condition, options) ->
         @condition = if options?.invert then condition.invert() else condition
         @guard     = options?.guard
    @@ -2338,19 +3145,27 @@ 

    While

    return no unless expressions.length for node in expressions return node if node.jumps loop: yes - no
    - - + no
    + +
  • +
  • +
    + +
    + +
    +

    The main difference from a JavaScript while is that the CoffeeScript while can be used as a part of a larger expression -- while loops may return an array containing the computed result of each iteration.

    - -
      compileNode: (o) ->
    +            
    + +
      compileNode: (o) ->
         o.indent += TAB
         set      = ''
         {body}   = this
    @@ -2370,19 +3185,27 @@ 

    While

    @makeCode(") {"), body, @makeCode("}") if @returns answer.push @makeCode "\n#{@tab}return #{rvar};" - answer
    - - + answer
    + +
  • +
  • +
    + +
    + +
    +

    Op

    Simple Arithmetic and logical operations. Performs some conversion from CoffeeScript operations into their JavaScript equivalents.

    - -
    exports.Op = class Op extends Base
    +            
    + +
    exports.Op = class Op extends Base
       constructor: (op, first, second, flip ) ->
         return new In first, second if op is 'in'
         if op is 'do'
    @@ -2394,30 +3217,46 @@ 

    Op

    @first = first @second = second @flip = !!flip - return this
    - - + return this
    + +
  • +
  • +
    + +
    + +
    +

    The map of conversions from CoffeeScript to JavaScript symbols.

    - -
      CONVERSIONS =
    +            
    + +
      CONVERSIONS =
         '==': '==='
         '!=': '!=='
    -    'of': 'in'
    - - + 'of': 'in'
    + +
  • +
  • +
    + +
    + +
    +

    The map of invertible operators.

    - -
      INVERSIONS =
    +            
    + +
      INVERSIONS =
         '!==': '==='
         '===': '!=='
     
    @@ -2429,18 +3268,26 @@ 

    Op

    not @second isComplex: -> - not (@isUnary() and (@operator in ['+', '-'])) or @first.isComplex()
    - - + not (@isUnary() and (@operator in ['+', '-'])) or @first.isComplex()
    + +
  • +
  • +
    + +
    + +
    +

    Am I capable of Python-style comparison chaining?

    - -
      isChainable: ->
    +            
    + +
      isChainable: ->
         @operator in ['<', '>', '>=', '<=', '===', '!==']
     
       invert: ->
    @@ -2490,18 +3337,26 @@ 

    Op

    call compileNode: (o) -> - isChain = @isChainable() and @first.isChainable()
    - - + isChain = @isChainable() and @first.isChainable()
    + +
  • +
  • +
    + +
    + +
    +

    In chains, there's no need to wrap bare obj literals in parens, as the chained expression is wrapped.

    - -
        @first.front = @front unless isChain
    +            
    + +
        @first.front = @front unless isChain
         if @operator is 'delete' and o.scope.check(@first.unwrapAll().value)
           throw SyntaxError 'delete operand may not be argument or var'
         if @operator in ['--', '++'] and @first.unwrapAll().value in STRICT_PROSCRIBED
    @@ -2511,11 +3366,18 @@ 

    Op

    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
    - - + if o.level <= LEVEL_OP then answer else @wrapInBraces answer
    + +
  • +
  • +
    + +
    + +
    +

    Mimic Python's chained comparisons when multiple comparison operators are used sequentially. For example: @@ -2523,8 +3385,9 @@

    Op

    bin/coffee -e 'console.log 50 < 65 > 10'
     true
    - -
      compileChain: (o) ->
    +            
    + +
      compileChain: (o) ->
         [@first.second, shared] = @first.second.cache o
         fst = @first.compileToFragments o, LEVEL_OP
         fragments = fst.concat @makeCode(" #{if @invert then '&&' else '||'} "),
    @@ -2538,17 +3401,25 @@ 

    Op

    else fst = @first ref = fst - new If(new Existence(fst), ref, type: 'if').addElse(@second).compileToFragments o
    - - + new If(new Existence(fst), ref, type: 'if').addElse(@second).compileToFragments o
    + +
  • +
  • +
    + +
    + +
    +

    Compile a unary Op.

    - -
      compileUnary: (o) ->
    +            
    + +
      compileUnary: (o) ->
         parts = []
         op = @operator
         parts.push [@makeCode op]
    @@ -2567,15 +3438,23 @@ 

    Op

    @joinFragmentArrays parts, '' toString: (idt) -> - super idt, @constructor.name + ' ' + @operator
    - - + super idt, @constructor.name + ' ' + @operator
    + +
  • +
  • +
    + +
    + +
    +

    In

    - -
    exports.In = class In extends Base
    +            
    + +
    exports.In = class In extends Base
       constructor: (@object, @array) ->
     
       children: ['object', 'array']
    @@ -2586,17 +3465,25 @@ 

    In

    if @array instanceof Value and @array.isArray() for obj in @array.base.objects when obj instanceof Splat hasSplat = yes - break
    - - + break
    + +
  • +
  • +
    + +
    + +
    +

    compileOrTest only if we have an array literal with no splats

    - -
          return @compileOrTest o unless hasSplat
    +            
    + +
          return @compileOrTest o unless hasSplat
         @compileLoopTest o
     
       compileOrTest: (o) ->
    @@ -2618,18 +3505,26 @@ 

    In

    if o.level < LEVEL_LIST then fragments else @wrapInBraces fragments toString: (idt) -> - super idt, @constructor.name + if @negated then '!' else ''
    - - + super idt, @constructor.name + if @negated then '!' else ''
    + +
  • +
  • +
    + +
    + +
    +

    Try

    A classic try/catch/finally block.

    - -
    exports.Try = class Try extends Base
    +            
    + +
    exports.Try = class Try extends Base
       constructor: (@attempt, @error, @recovery, @ensure) ->
     
       children: ['attempt', 'recovery', 'ensure']
    @@ -2641,18 +3536,26 @@ 

    Try

    makeReturn: (res) -> @attempt = @attempt .makeReturn res if @attempt @recovery = @recovery.makeReturn res if @recovery - this
    - - + this
    + +
  • +
  • +
    + +
    + +
    +

    Compilation is more or less as you would expect -- the finally clause is optional, the catch is not.

    - -
      compileNode: (o) ->
    +            
    + +
      compileNode: (o) ->
         o.indent  += TAB
         tryPart   = @attempt.compileToFragments o, LEVEL_TOP
     
    @@ -2675,41 +3578,64 @@ 

    Try

    [].concat @makeCode("#{@tab}try {\n"), tryPart, - @makeCode("\n#{@tab}}"), catchPart, ensurePart
    - - + @makeCode("\n#{@tab}}"), catchPart, ensurePart
    + +
  • +
  • +
    + +
    + +
    +

    Throw

    Simple node to throw an exception.

    - -
    exports.Throw = class Throw extends Base
    +            
    + +
    exports.Throw = class Throw extends Base
       constructor: (@expression) ->
     
       children: ['expression']
     
       isStatement: YES
    -  jumps:       NO
    - - + jumps: NO
    + +
  • +
  • +
    + +
    + +
    +

    A Throw is already a return, of sorts...

    - -
      makeReturn: THIS
    +            
    + +
      makeReturn: THIS
     
       compileNode: (o) ->
    -    [].concat @makeCode(@tab + "throw "), (@expression.compileToFragments o), @makeCode(";")
    - - + [].concat @makeCode(@tab + "throw "), (@expression.compileToFragments o), @makeCode(";")
    + +
  • +
  • +
    + +
    + +
    +

    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 @@ -2717,8 +3643,9 @@

    Existence

    - -
    exports.Existence = class Existence extends Base
    +            
    + +
    exports.Existence = class Existence extends Base
       constructor: (@expression) ->
     
       children: ['expression']
    @@ -2731,22 +3658,37 @@ 

    Existence

    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
    - - + else
    + +
  • +
  • +
    + +
    + +
    +

    do not use strict equality here; it will break existing code

    - -
          code = "#{code} #{if @negated then '==' else '!='} null"
    -    [@makeCode(if o.level <= LEVEL_COND then code else "(#{code})")]
    - - +
    + +
          code = "#{code} #{if @negated then '==' else '!='} null"
    +    [@makeCode(if o.level <= LEVEL_COND then code else "(#{code})")]
    + +
  • +
  • +
    + +
    + +
    +

    Parens

    An extra set of parentheses, specified explicitly in the source. At one time we tried to clean up the results by detecting and removing redundant @@ -2757,8 +3699,9 @@

    Parens

    - -
    exports.Parens = class Parens extends Base
    +            
    + +
    exports.Parens = class Parens extends Base
       constructor: (@body) ->
     
       children: ['body']
    @@ -2774,11 +3717,18 @@ 

    Parens

    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
    - - + 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 @@ -2791,8 +3741,9 @@

    For

    - -
    exports.For = class For extends While
    +            
    + +
    exports.For = class For extends While
       constructor: (body, source) ->
         {@source, @guard, @step, @name, @index} = source
         @body    = Block.wrap [body]
    @@ -2806,11 +3757,18 @@ 

    For

    throw SyntaxError 'cannot pattern match over range loops' if @range and @pattern @returns = false - children: ['body', 'source', 'guard', 'step']
    - - + 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 @@ -2818,8 +3776,9 @@

    For

    - -
      compileNode: (o) ->
    +            
    + +
      compileNode: (o) ->
         body      = Block.wrap [@body]
         lastJumps = last(body.expressions)?.jumps()
         @returns  = no if lastJumps and lastJumps instanceof Return
    @@ -2910,18 +3869,26 @@ 

    For

    [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
    - - + defs
    + +
  • +
  • +
    + +
    + +
    +

    Switch

    A JavaScript switch statement. Converts into a returnable expression on-demand.

    - -
    exports.Switch = class Switch extends Base
    +            
    + +
    exports.Switch = class Switch extends Base
       constructor: (@subject, @cases, @otherwise) ->
     
       children: ['subject', 'cases', 'otherwise']
    @@ -2957,11 +3924,18 @@ 

    Switch

    if @otherwise and @otherwise.expressions.length fragments.push @makeCode(idt1 + "default:\n"), (@otherwise.compileToFragments o, LEVEL_TOP)..., @makeCode("\n") fragments.push @makeCode @tab + '}' - fragments
    - - + fragments
    + +
  • +
  • +
    + +
    + +
    +

    If

    If/else statements. Acts as an expression by pushing down requested returns to the last line of each clause. @@ -2972,8 +3946,9 @@

    If

    - -
    exports.If = class If extends Base
    +            
    + +
    exports.If = class If extends Base
       constructor: (condition, @body, options = {}) ->
         @condition = if options.type is 'unless' then condition.invert() else condition
         @elseBody  = null
    @@ -2983,34 +3958,50 @@ 

    If

    children: ['condition', 'body', 'elseBody'] bodyNode: -> @body?.unwrap() - elseBodyNode: -> @elseBody?.unwrap()
    - - + elseBodyNode: -> @elseBody?.unwrap()
    + +
  • +
  • +
    + +
    + +
    +

    Rewrite a chain of Ifs to add a default case as the final else.

    - -
      addElse: (elseBody) ->
    +            
    + +
      addElse: (elseBody) ->
         if @isChain
           @elseBodyNode().addElse elseBody
         else
           @isChain  = elseBody instanceof If
           @elseBody = @ensureBlock elseBody
    -    this
    - - + 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) ->
    +            
    + +
      isStatement: (o) ->
         o?.level is LEVEL_TOP or
           @bodyNode().isStatement(o) or @elseBodyNode()?.isStatement(o)
     
    @@ -3026,18 +4017,26 @@ 

    If

    this ensureBlock: (node) -> - if node instanceof Block then node else new Block [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) ->
    +            
    + +
      compileStatement: (o) ->
         child    = del o, 'chainChild'
         exeq     = del o, 'isExistentialEquals'
     
    @@ -3056,17 +4055,25 @@ 

    If

    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
    - - + answer
    + +
  • +
  • +
    + +
    + +
    +

    Compile the If as a conditional operator.

    - -
      compileExpression: (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')]
    @@ -3074,17 +4081,32 @@ 

    If

    if o.level >= LEVEL_COND then @wrapInBraces fragments else fragments unfoldSoak: -> - @soak and this
    - - + @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. @@ -3094,20 +4116,29 @@

    Closure

    - -
    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) ->
    +            
    + +
      wrap: (expressions, statement, noReturn) ->
         return expressions if expressions.jumps()
         func = new Code [], Block.wrap [expressions]
         args = []
    @@ -3128,96 +4159,160 @@ 

    Closure

    literalThis: (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)
    - - + (node instanceof Call and node.isSuper)
    + +
  • +
  • +
    + +
    + +
    +

    Unfold a node's child if soak, then tuck the node under created If

    - -
    unfoldSoak = (o, parent, name) ->
    +            
    + +
    unfoldSoak = (o, parent, name) ->
       return unless ifn = parent[name].unfoldSoak o
       parent[name] = ifn.body
       ifn.body = new Value parent
    -  ifn
    - - + ifn
    + +
  • +
  • +
    + +
    + +
    +

    Constants

    - - +
    + +
  • +
  • +
    + +
    + +
    + - -
    UTILITIES =
    - - +
    + +
    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: -> """
    +            
    + +
      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; }
    -  """
    - - + """
    + +
  • +
  • +
    + +
    + +
    +

    Discover if an item is in an array.

    - -
      indexOf: -> """
    +            
    + +
      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'
    - - +
    + +
      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_TOP    = 1  # ...;
     LEVEL_PAREN  = 2  # (...)
     LEVEL_LIST   = 3  # [...]
     LEVEL_COND   = 4  # ... ? x : y
     LEVEL_OP     = 5  # !...
    -LEVEL_ACCESS = 6  # ...[0]
    - - +LEVEL_ACCESS = 6 # ...[0]
    + +
  • +
  • +
    + +
    + +
    +

    Tabs are two spaces for pretty printing.

    - -
    TAB = '  '
    +            
    + +
    TAB = '  '
     
     IDENTIFIER_STR = "[$A-Za-z_\\x7f-\\uffff][$\\w\\x7f-\\uffff]*"
     IDENTIFIER = /// ^ #{IDENTIFIER_STR} $ ///
    @@ -3236,32 +4331,56 @@ 

    Constants

    | (#{IDENTIFIER_STR}) $ -///
    - - +///
    + +
  • +
  • +
    + +
    + +
    +

    Is a literal value a string?

    - -
    IS_STRING = /^['"]/
    - - +
    + +
    IS_STRING = /^['"]/
    + +
  • +
  • +
    + +
    + +
    +

    Utility Functions

    - - +
    + +
  • +
  • +
    + +
    + +
    +

    Helper for ensuring that utility functions are assigned at the top level.

    - -
    utility = (name) ->
    +            
    + +
    utility = (name) ->
       ref = "__#{name}"
       Scope.root.assign ref, UTILITIES[name]()
       ref
    @@ -3270,11 +4389,11 @@ 

    Utility Functions

    code = code.replace /\n/g, '$&' + tab code.replace /\s+$/, '' -
    +
    + +
  • - -
    h
    -
    +
    diff --git a/documentation/docs/optparse.html b/documentation/docs/optparse.html index 0992db845c..30aabec55e 100644 --- a/documentation/docs/optparse.html +++ b/documentation/docs/optparse.html @@ -4,129 +4,111 @@ optparse.coffee - + -
    -
    - -
    - -

    optparse.coffee

    + - - -

    A simple OptionParser class to parse option flags from the command-line. +

  • +
    + +
    + +
    +

    A simple OptionParser class to parse option flags from the command-line. Use it like so:

    @@ -137,12 +119,20 @@

    Table of Contents

    - -
    exports.OptionParser = class OptionParser
    - - +
    + +
    exports.OptionParser = class OptionParser
    + +
  • +
  • +
    + +
    + +
    +

    Initialize with a list of valid options, in the form:

    @@ -151,13 +141,21 @@

    Table of Contents

    - -
      constructor: (rules, @banner) ->
    -    @rules = buildRules rules
    - - +
    + +
      constructor: (rules, @banner) ->
    +    @rules = buildRules rules
    + +
  • +
  • +
    + +
    + +
    +

    Parse the list of arguments, populating an options object with all of the specified options, and return it. Options after the first non-option argument are treated as arguments. options.arguments will be an array @@ -167,8 +165,9 @@

    Table of Contents

    - -
      parse: (args) ->
    +            
    + +
      parse: (args) ->
         options = arguments: []
         skippingArgument = no
         originalArgs = args
    @@ -181,18 +180,26 @@ 

    Table of Contents

    pos = originalArgs.indexOf '--' options.arguments = options.arguments.concat originalArgs[(pos + 1)..] break - isOption = !!(arg.match(LONG_FLAG) or arg.match(SHORT_FLAG))
    - - + isOption = !!(arg.match(LONG_FLAG) or arg.match(SHORT_FLAG))
    + +
  • +
  • +
    + +
    + +
    +

    the CS option parser is a little odd; options after the first non-option argument are treated as non-option arguments themselves

    - -
          seenNonOptionArg = options.arguments.length > 0
    +            
    + +
          seenNonOptionArg = options.arguments.length > 0
           unless seenNonOptionArg
             matchedRule = no
             for rule in @rules
    @@ -207,18 +214,26 @@ 

    Table of Contents

    throw new Error "unrecognized option: #{arg}" if isOption and not matchedRule if seenNonOptionArg or not isOption options.arguments.push arg - options
    - - + options
    + +
  • +
  • +
    + +
    + +
    +

    Return the help text for this OptionParser, listing and describing all of the valid options, for --help and such.

    - -
      help: ->
    +            
    + +
      help: ->
         lines = []
         lines.unshift "#{@banner}\n" if @banner
         for rule in @rules
    @@ -226,51 +241,83 @@ 

    Table of Contents

    spaces = if spaces > 0 then Array(spaces + 1).join(' ') else '' letPart = if rule.shortFlag then rule.shortFlag + ', ' else ' ' lines.push ' ' + letPart + rule.longFlag + spaces + rule.description - "\n#{ lines.join('\n') }\n"
    - - + "\n#{ lines.join('\n') }\n"
    + +
  • +
  • +
    + +
    + +
    +

    Helpers

    - - +
    + +
  • +
  • +
    + +
    + +
    +

    Regex matchers for option flags.

    - -
    LONG_FLAG  = /^(--\w[\w\-]*)/
    +            
    + +
    LONG_FLAG  = /^(--\w[\w\-]*)/
     SHORT_FLAG = /^(-\w)$/
     MULTI_FLAG = /^-(\w{2,})/
    -OPTIONAL   = /\[(\w+(\*?))\]/
    - - +OPTIONAL = /\[(\w+(\*?))\]/
    + +
  • +
  • +
    + +
    + +
    +

    Build and return the list of option rules. If the optional short-flag is unspecified, leave it out by padding with null.

    - -
    buildRules = (rules) ->
    +            
    + +
    buildRules = (rules) ->
       for tuple in rules
         tuple.unshift null if tuple.length < 3
    -    buildRule tuple...
    - - + buildRule tuple...
    + +
  • +
  • +
    + +
    + +
    +

    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 = {}) ->
    +            
    + +
    buildRule = (shortFlag, longFlag, description, options = {}) ->
       match     = longFlag.match(OPTIONAL)
       longFlag  = longFlag.match(LONG_FLAG)[1]
       {
    @@ -280,17 +327,25 @@ 

    Helpers

    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.

    - -
    normalizeArguments = (args) ->
    +            
    + +
    normalizeArguments = (args) ->
       args = args[..]
       result = []
       for arg in args
    @@ -300,11 +355,11 @@ 

    Helpers

    result.push arg result -
    +
    + +
  • - -
    h
    -
    +
    diff --git a/documentation/docs/repl.html b/documentation/docs/repl.html index ea0a4d5585..1f3754be30 100644 --- a/documentation/docs/repl.html +++ b/documentation/docs/repl.html @@ -4,178 +4,193 @@ repl.coffee - + -
    -
    - -
    - -

    repl.coffee

    - - - -
    -

    Table of Contents

    -
      - - -
    1. - - browser.coffee - -
    2. - - -
    3. - - cake.coffee - -
    4. - - -
    5. - - coffee-script.coffee - -
    6. - - -
    7. - - command.coffee - -
    8. - - -
    9. - - grammar.coffee - -
    10. - - -
    11. - - helpers.coffee - -
    12. - - -
    13. - - index.coffee - -
    14. - - -
    15. - - lexer.coffee - -
    16. - - -
    17. - - nodes.coffee - -
    18. - - -
    19. - - optparse.coffee - -
    20. - - -
    21. - - repl.coffee - -
    22. - - -
    23. - - rewriter.coffee - -
    24. - - -
    25. - - scope.litcoffee - -
    26. - - -
    27. - - sourcemap.coffee - -
    28. - -
    + - - +
  • +
    +

    repl.coffee

    +
    +
  • -
    vm = require 'vm'
    +        
  • +
    + +
    + +
    + +
    + +
    vm = require 'vm'
     nodeREPL = require 'repl'
     CoffeeScript = require './coffee-script'
     {merge} = require './helpers'
     
     replDefaults =
       prompt: 'coffee> ',
    -  eval: (input, context, filename, cb) ->
    - - + eval: (input, context, filename, cb) ->
  • + + +
  • +
    + +
    + +
    +

    XXX: multiline hack

    - -
        input = input.replace /\uFF00/g, '\n'
    - - +
    + +
        input = input.replace /\uFF00/g, '\n'
    + +
  • +
  • +
    + +
    + +
    +

    strip single-line comments

    - -
        input = input.replace /(^|[\r\n]+)(\s*)##?(?:[^#\r\n][^\r\n]*|)($|[\r\n])/, '$1$2$3'
    - - +
    + +
        input = input.replace /(^|[\r\n]+)(\s*)##?(?:[^#\r\n][^\r\n]*|)($|[\r\n])/, '$1$2$3'
    + +
  • +
  • +
    + +
    + +
    +

    empty command

    - -
        return cb null if /^(\s*|\(\s*\))$/.test input
    - - +
    + +
        return cb null if /^(\s*|\(\s*\))$/.test input
    + +
  • +
  • +
    + +
    + +
    +

    TODO: fix #1829: pass in-scope vars and avoid accidentally shadowing them by omitting those declarations

    - -
        try
    +            
    + +
        try
           js = CoffeeScript.compile "_=(#{input}\n)", {filename, bare: yes}
           cb null, vm.runInContext(js, context, filename)
         catch err
    @@ -188,17 +203,25 @@ 

    Table of Contents

    enabled: off initialPrompt: repl.prompt.replace(/^[^> ]*/, (x) -> x.replace /./g, '-') prompt: repl.prompt.replace(/^[^> ]*>?/, (x) -> x.replace /./g, '.') - buffer: ''
    - - + buffer: ''
    + +
  • +
  • +
    + +
    + +
    +

    Proxy node's line listener

    - -
      nodeLineListener = rli.listeners('line')[0]
    +            
    + +
      nodeLineListener = rli.listeners('line')[0]
       rli.removeListener 'line', nodeLineListener
       rli.on 'line', (cmd) ->
         if multiline.enabled
    @@ -207,66 +230,106 @@ 

    Table of Contents

    rli.prompt true else nodeLineListener cmd - return
    - - + return
    + +
  • +
  • +
    + +
    + +
    +

    Handle Ctrl-v

    - -
      inputStream.on 'keypress', (char, key) ->
    +            
    + +
      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
    - - + if multiline.enabled
    + +
  • +
  • +
    + +
    + +
    +

    allow arbitrarily switching between modes any time before multiple lines are entered

    - -
          unless multiline.buffer.match /\n/
    +            
    + +
          unless multiline.buffer.match /\n/
             multiline.enabled = not multiline.enabled
             rli.setPrompt repl.prompt
             rli.prompt true
    -        return
    - - + return
    + +
  • +
  • +
    + +
    + +
    +

    no-op unless the current line is empty

    - -
          return if rli.line? and not rli.line.match /^\s*$/
    - - +
    + +
          return if rli.line? and not rli.line.match /^\s*$/
    + +
  • +
  • +
    + +
    + +
    +

    eval, print, loop

    - -
          multiline.enabled = not multiline.enabled
    +            
    + +
          multiline.enabled = not multiline.enabled
           rli.line = ''
           rli.cursor = 0
           rli.output.cursorTo 0
    -      rli.output.clearLine 1
    - - + rli.output.clearLine 1
    + +
  • +
  • +
    + +
    + +
    +

    XXX: multiline hack

    - -
          multiline.buffer = multiline.buffer.replace /\n/g, '\uFF00'
    +            
    + +
          multiline.buffer = multiline.buffer.replace /\n/g, '\uFF00'
           rli.emit 'line', multiline.buffer
           multiline.buffer = ''
         else
    @@ -283,11 +346,11 @@ 

    Table of Contents

    addMultilineHandler repl repl -
    +
    + +
  • - -
    h
    -
    +
    diff --git a/documentation/docs/rewriter.html b/documentation/docs/rewriter.html index 44f3e6f324..e6a1bb668f 100644 --- a/documentation/docs/rewriter.html +++ b/documentation/docs/rewriter.html @@ -4,129 +4,111 @@ rewriter.coffee - + -
    -
    - -
    - -

    rewriter.coffee

    - - - -
    -

    Table of Contents

    -
      - - -
    1. - - browser.coffee - -
    2. - - -
    3. - - cake.coffee - -
    4. - - -
    5. - - coffee-script.coffee - -
    6. - - -
    7. - - command.coffee - -
    8. - - -
    9. - - grammar.coffee - -
    10. - - -
    11. - - helpers.coffee - -
    12. - - -
    13. - - index.coffee - -
    14. - - -
    15. - - lexer.coffee - -
    16. - - -
    17. - - nodes.coffee - -
    18. - - -
    19. - - optparse.coffee - -
    20. - - -
    21. - - repl.coffee - -
    22. - - -
    23. - - rewriter.coffee - -
    24. - - -
    25. - - scope.litcoffee - -
    26. - - -
    27. - - sourcemap.coffee - -
    28. - -
    + - - -

    The CoffeeScript language has a good deal of optional syntax, implicit syntax, +

  • +
    + +
    + +
    +

    The CoffeeScript language has a good deal of optional syntax, implicit syntax, 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 @@ -138,26 +120,42 @@

    Table of Contents

    - -
    generate = (tag, value) ->
    +            
    + +
    generate = (tag, value) ->
         tok = [tag, value]
         tok.generated = yes
    -    tok
    - - + tok
    + +
  • +
  • +
    + +
    + +
    +

    The Rewriter class is used by the Lexer, directly against its internal array of tokens.

    - -
    class exports.Rewriter
    - - +
    + +
    class exports.Rewriter
    + +
  • +
  • +
    + +
    + +
    +

    Helpful snippet for debugging: console.log (t[0] + '/' + t[1] for t in @tokens).join ' ' @@ -170,8 +168,9 @@

    Table of Contents

    - -
      rewrite: (@tokens) ->
    +            
    + +
      rewrite: (@tokens) ->
         @removeLeadingNewlines()
         @removeMidExpressionNewlines()
         @closeOpenCalls()
    @@ -180,11 +179,18 @@ 

    Table of Contents

    @tagPostfixConditionals() @addImplicitBracesAndParens() @addLocationDataToGeneratedTokens() - @tokens
    - - + @tokens
    + +
  • +
  • +
    + +
    + +
    +

    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 @@ -193,8 +199,9 @@

    Table of Contents

    - -
      scanTokens: (block) ->
    +            
    + +
      scanTokens: (block) ->
         {tokens} = this
         i = 0
         i += block.call this, token, i, tokens while token = tokens[i]
    @@ -211,47 +218,71 @@ 

    Table of Contents

    else if token[0] in EXPRESSION_END levels -= 1 i += 1 - i - 1
    - - + i - 1
    + +
  • +
  • +
    + +
    + +
    +

    Leading newlines would introduce an ambiguity in the grammar, so we dispatch them here.

    - -
      removeLeadingNewlines: ->
    +            
    + +
      removeLeadingNewlines: ->
         break for [tag], i in @tokens when tag isnt 'TERMINATOR'
    -    @tokens.splice 0, i if i
    - - + @tokens.splice 0, i if i
    + +
  • +
  • +
    + +
    + +
    +

    Some blocks occur in the middle of expressions -- when we're expecting this, remove their trailing newlines.

    - -
      removeMidExpressionNewlines: ->
    +            
    + +
      removeMidExpressionNewlines: ->
         @scanTokens (token, i, tokens) ->
           return 1 unless token[0] is 'TERMINATOR' and @tag(i + 1) in EXPRESSION_CLOSE
           tokens.splice i, 1
    -      0
    - - + 0
    + +
  • +
  • +
    + +
    + +
    +

    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.

    - -
      closeOpenCalls: ->
    +            
    + +
      closeOpenCalls: ->
         condition = (token, i) ->
           token[0] in [')', 'CALL_END'] or
           token[0] is 'OUTDENT' and @tag(i - 1) is ')'
    @@ -261,18 +292,26 @@ 

    Table of Contents

    @scanTokens (token, i) -> @detectEnd i + 1, condition, action if token[0] is 'CALL_START' - 1
    - - + 1
    + +
  • +
  • +
    + +
    + +
    +

    The lexer has tagged the opening parenthesis of an indexing operation call. Match it with its paired close.

    - -
      closeOpenIndexes: ->
    +            
    + +
      closeOpenIndexes: ->
         condition = (token, i) ->
           token[0] in [']', 'INDEX_END']
     
    @@ -281,50 +320,74 @@ 

    Table of Contents

    @scanTokens (token, i) -> @detectEnd i + 1, condition, action if token[0] is 'INDEX_START' - 1
    - - + 1
    + +
  • +
  • +
    + +
    + +
    +

    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)

    - -
      matchTags: (i, pattern...) ->
    +            
    + +
      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
    - - + yes
    + +
  • +
  • +
    + +
    + +
    +

    yes iff standing in front of something looking like @: or :, skipping over 'HERECOMMENT's

    - -
      looksObjectish: (j) ->
    -    @matchTags(j, '@', null, ':') or @matchTags(j, null, ':')
    - - +
    + +
      looksObjectish: (j) ->
    +    @matchTags(j, '@', null, ':') or @matchTags(j, null, ':')
    + +
  • +
  • +
    + +
    + +
    +

    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.

    - -
      findTagsBackwards: (i, tags) ->
    +            
    + +
      findTagsBackwards: (i, tags) ->
         backStack = []
         while i >= 0 and (backStack.length or
               @tag(i) not in tags and
    @@ -333,69 +396,109 @@ 

    Table of Contents

    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
    - - + @tag(i) in tags
    + +
  • +
  • +
    + +
    + +
    +

    Look for signs of implicit calls and objects in the token stream and add them.

    - -
      addImplicitBracesAndParens: ->
    - - +
    + +
      addImplicitBracesAndParens: ->
    + +
  • +
  • +
    + +
    + +
    +

    Track current balancing depth (both implicit and explicit) on stack.

    - -
        stack = []
    +            
    + +
        stack = []
     
         @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
    - - + startIdx = i
    + +
  • +
  • +
    + +
    + +
    +

    Helper function, used for keeping track of the number of tokens consumed and spliced, when returning for getting a new token.

    - -
          forward   = (n) -> i - startIdx + n
    - - +
    + +
          forward   = (n) -> i - startIdx + n
    + +
  • +
  • +
    + +
    + +
    +

    Helper functions

    - -
          inImplicit        = -> stackTop()?[2]?.ours
    +            
    + +
          inImplicit        = -> stackTop()?[2]?.ours
           inImplicitCall    = -> inImplicit() and stackTop()?[0] is '('
    -      inImplicitObject  = -> inImplicit() and stackTop()?[0] is '{'
    - - + inImplicitObject = -> inImplicit() and stackTop()?[0] is '{'
    + +
  • +
  • +
    + +
    + +
    +

    Unclosed control statement inside implicit parens (like class declaration or if-conditionals)

    - -
          inImplicitControl = -> inImplicit and stackTop()?[0] is 'CONTROL'
    +            
    + +
          inImplicitControl = -> inImplicit and stackTop()?[0] is 'CONTROL'
     
           startImplicitCall = (j) ->
             idx = j ? i
    @@ -418,60 +521,92 @@ 

    Table of Contents

    j = j ? i stack.pop() tokens.splice j, 0, generate '}', '}' - i += 1
    - - + i += 1
    + +
  • +
  • +
    + +
    + +
    +

    Don't end an implicit call on next indent if any of these are in an argument

    - -
          if inImplicitCall() and tag in ['IF', 'TRY', 'FINALLY', 'CATCH',
    +            
    + +
          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 is 'INDENT' and inImplicit()
    + +
  • +
  • +
    + +
    + +
    +

    An INDENT closes an implicit call unless 1. We have seen a CONTROL argument on the line. 2. The last token before the indent is part of the list below

    - -
            if prevTag not in ['=>', '->', '[', '(', ',', '{', 'TRY', 'ELSE', '=']
    +            
    + +
            if prevTag not in ['=>', '->', '[', '(', ',', '{', 'TRY', 'ELSE', '=']
               endImplicitCall() while inImplicitCall()
             stack.pop() if inImplicitControl()
             stack.push [tag, i]
    -        return forward(1)
    - - + return forward(1)
    + +
  • +
  • +
    + +
    + +
    +

    Straightforward start of explicit expression

    - -
          if tag in EXPRESSION_START
    +            
    + +
          if tag in EXPRESSION_START
             stack.push [tag, i]
    -        return forward(1)
    - - + return forward(1)
    + +
  • +
  • +
    + +
    + +
    +

    Close all implicit expressions inside of explicitly closed expressions.

    - -
          if tag in EXPRESSION_END
    +            
    + +
          if tag in EXPRESSION_END
             while inImplicit()
               if inImplicitCall()
                 endImplicitCall()
    @@ -479,29 +614,44 @@ 

    Table of Contents

    endImplicitObject() else stack.pop() - stack.pop()
    - - + stack.pop()
    + +
  • +
  • +
    + +
    + +
    +

    Recognize standard implicit calls like f a, f() b, f? c, h[0] d etc.

    - -
          if (tag in IMPLICIT_FUNC and token.spaced or
    +            
    + +
          if (tag in IMPLICIT_FUNC and token.spaced 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)
    - - + return forward(2)
    + +
  • +
  • +
    + +
    + +
    +

    Implicit call taking an implicit indented object as first argument. f a: b @@ -523,57 +673,89 @@

    Table of Contents

    - -
          if @matchTags(i, IMPLICIT_FUNC, 'INDENT', null, ':') and
    +            
    + +
          if @matchTags(i, IMPLICIT_FUNC, '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)
    - - + return forward(3)
    + +
  • +
  • +
    + +
    + +
    +

    Implicit objects start here

    - -
          if tag is ':'
    - - +
    + +
          if tag is ':'
    + +
  • +
  • +
    + +
    + +
    +

    Go back to the (implicit) start of the object

    - -
            if @tag(i - 2) is '@' then s = i - 2 else s = i - 1
    +            
    + +
            if @tag(i - 2) is '@' then s = i - 2 else s = i - 1
             s -= 2 while @tag(s - 2) is 'HERECOMMENT'
     
    -        startsLine = s is 0 or @tag(s - 1) in LINEBREAKS or tokens[s - 1].newLine
    - - + startsLine = s is 0 or @tag(s - 1) in LINEBREAKS or tokens[s - 1].newLine
    + +
  • +
  • +
    + +
    + +
    +

    Are we just continuing an already declared object?

    - -
            if stackTop()
    +            
    + +
            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)
    - - + return forward(2)
    + +
  • +
  • +
    + +
    + +
    +

    End implicit calls when chaining method calls like e.g.: f -> @@ -584,8 +766,9 @@

    Table of Contents

    - -
          if prevTag is 'OUTDENT' and inImplicitCall() and tag in ['.', '?.', '::', '?::']
    +            
    + +
          if prevTag is 'OUTDENT' and inImplicitCall() and tag in ['.', '?.', '::', '?::']
             endImplicitCall()
             return forward(1)
     
    @@ -593,50 +776,81 @@ 

    Table of Contents

    if tag in IMPLICIT_END while inImplicit() - [stackTag, stackIdx, {sameLine, startsLine}] = stackTop()
    - - + [stackTag, stackIdx, {sameLine, startsLine}] = stackTop()
    + +
  • +
  • +
    + +
    + +
    +

    Close implicit calls when reached end of argument list

    - -
              if inImplicitCall() and prevTag isnt ','
    -            endImplicitCall()
    - - +
    + +
              if inImplicitCall() and prevTag isnt ','
    +            endImplicitCall()
    + +
  • +
  • +
    + +
    + +
    +

    Close implicit objects such as: return a: 1, b: 2 unless true

    - -
              else if inImplicitObject() and sameLine and not startsLine
    -            endImplicitObject()
    - - +
    + +
              else if inImplicitObject() and sameLine and not startsLine
    +            endImplicitObject()
    + +
  • +
  • +
    + +
    + +
    +

    Close implicit objects when at end of line, line didn't end with a comma and the implicit object didn't start the line or the next line doesn't look like the continuation of an object.

    - -
              else if inImplicitObject() and tag is 'TERMINATOR' and prevTag isnt ',' and
    +            
    + +
              else if inImplicitObject() and tag is 'TERMINATOR' and prevTag isnt ',' and
                       not (startsLine and @looksObjectish(i + 1))
                 endImplicitObject()
               else
    -            break
    - - + break
    + +
  • +
  • +
    + +
    + +
    +

    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: @@ -653,13 +867,21 @@

    Table of Contents

    - -
          if tag is ',' and not @looksObjectish(i + 1) and inImplicitObject() and
    -         (nextTag isnt 'TERMINATOR' or not @looksObjectish(i + 2))
    - - +
    + +
          if tag is ',' and not @looksObjectish(i + 1) and inImplicitObject() and
    +         (nextTag isnt 'TERMINATOR' or not @looksObjectish(i + 2))
    + +
  • +
  • +
    + +
    + +
    +

    When nextTag is OUTDENT the comma is insignificant and should just be ignored so embed it in the implicit object. @@ -670,21 +892,30 @@

    Table of Contents

    - -
            offset = if nextTag is 'OUTDENT' then 1 else 0
    +            
    + +
            offset = if nextTag is 'OUTDENT' then 1 else 0
             while inImplicitObject()
               endImplicitObject i + offset
    -      return forward(1)
    - - + return forward(1)
    + +
  • +
  • +
    + +
    + +
    +

    Add location data to all tokens generated by the rewriter.

    - -
      addLocationDataToGeneratedTokens: ->
    +            
    + +
      addLocationDataToGeneratedTokens: ->
         @scanTokens (token, i, tokens) ->
           return 1 if     token[2]
           return 1 unless token.generated or token.explicit
    @@ -694,11 +925,18 @@ 

    Table of Contents

    first_column: last_column last_line: last_line last_column: last_column - 1
    - - + 1
    + +
  • +
  • +
    + +
    + +
    +

    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, @@ -706,8 +944,9 @@

    Table of Contents

    - -
      addImplicitIndentation: ->
    +            
    + +
      addImplicitIndentation: ->
         starter = indent = outdent = null
     
         condition = (token, i) ->
    @@ -737,18 +976,26 @@ 

    Table of Contents

    @detectEnd i + 2, condition, action tokens.splice i, 1 if tag is 'THEN' return 1 - return 1
    - - + return 1
    + +
  • +
  • +
    + +
    + +
    +

    Tag postfix conditionals as such, so that we can parse them with a different precedence.

    - -
      tagPostfixConditionals: ->
    +            
    + +
      tagPostfixConditionals: ->
     
         original = null
     
    @@ -763,50 +1010,82 @@ 

    Table of Contents

    return 1 unless token[0] is 'IF' original = token @detectEnd i + 1, condition, action - 1
    - - + 1
    + +
  • +
  • +
    + +
    + +
    +

    Generate the indentation tokens, based on another token on the same line.

    - -
      indentation: (token, implicit = no) ->
    +            
    + +
      indentation: (token, 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
    - - + generate: generate
    + +
  • +
  • +
    + +
    + +
    +

    Look up a tag by token index.

    - -
      tag: (i) -> @tokens[i]?[0]
    - - +
    + +
      tag: (i) -> @tokens[i]?[0]
    + +
  • +
  • +
    + +
    + +
    +

    Constants

    - - +
    + +
  • +
  • +
    + +
    + +
    +

    List of the token pairs that must be balanced.

    - -
    BALANCED_PAIRS = [
    +            
    + +
    BALANCED_PAIRS = [
       ['(', ')']
       ['[', ']']
       ['{', '}']
    @@ -814,117 +1093,189 @@ 

    Constants

    ['CALL_START', 'CALL_END'] ['PARAM_START', 'PARAM_END'] ['INDEX_START', 'INDEX_END'] -]
    - - +]
    + +
  • +
  • +
    + +
    + +
    +

    The inverse mappings of BALANCED_PAIRS we're trying to fix up, so we can look things up from either end.

    - -
    exports.INVERSES = INVERSES = {}
    - - +
    + +
    exports.INVERSES = INVERSES = {}
    + +
  • +
  • +
    + +
    + +
    +

    The tokens that signal the start/end of a balanced pair.

    - -
    EXPRESSION_START = []
    +            
    + +
    EXPRESSION_START = []
     EXPRESSION_END   = []
     
     for [left, rite] in BALANCED_PAIRS
       EXPRESSION_START.push INVERSES[rite] = left
    -  EXPRESSION_END  .push INVERSES[left] = rite
    - - + EXPRESSION_END .push INVERSES[left] = rite
    + +
  • +
  • +
    + +
    + +
    +

    Tokens that indicate the close of a clause of an expression.

    - -
    EXPRESSION_CLOSE = ['CATCH', 'WHEN', 'ELSE', 'FINALLY'].concat EXPRESSION_END
    - - +
    + +
    EXPRESSION_CLOSE = ['CATCH', 'WHEN', 'ELSE', 'FINALLY'].concat EXPRESSION_END
    + +
  • +
  • +
    + +
    + +
    +

    Tokens that, if followed by an IMPLICIT_CALL, indicate a function invocation.

    - -
    IMPLICIT_FUNC    = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@', 'THIS']
    - - +
    + +
    IMPLICIT_FUNC    = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@', 'THIS']
    + +
  • +
  • +
    + +
    + +
    +

    If preceded by an IMPLICIT_FUNC, indicates a function invocation.

    - -
    IMPLICIT_CALL    = [
    +            
    + +
    IMPLICIT_CALL    = [
       'IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'CLASS'
       'IF', 'TRY', 'SWITCH', 'THIS', 'BOOL', 'NULL', 'UNDEFINED', 'UNARY', 'SUPER'
       '@', '->', '=>', '[', '(', '{', '--', '++'
     ]
     
    -IMPLICIT_UNSPACED_CALL = ['+', '-']
    - - +IMPLICIT_UNSPACED_CALL = ['+', '-']
    + +
  • +
  • +
    + +
    + +
    +

    Tokens indicating that the implicit call must enclose a block of expressions.

    - -
    IMPLICIT_BLOCK   = ['->', '=>', '{', '[', ',']
    - - +
    + +
    IMPLICIT_BLOCK   = ['->', '=>', '{', '[', ',']
    + +
  • +
  • +
    + +
    + +
    +

    Tokens that always mark the end of an implicit call for single-liners.

    - -
    IMPLICIT_END     = ['POST_IF', 'FOR', 'WHILE', 'UNTIL', 'WHEN', 'BY',
    -  'LOOP', 'TERMINATOR']
    - - +
    + +
    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']
    - - +
    + +
    SINGLE_LINERS    = ['ELSE', '->', '=>', 'TRY', 'FINALLY', 'THEN']
    +SINGLE_CLOSERS   = ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN']
    + +
  • +
  • +
    + +
    + +
    +

    Tokens that end a line.

    - -
    LINEBREAKS       = ['TERMINATOR', 'INDENT', 'OUTDENT']
    +            
    + +
    LINEBREAKS       = ['TERMINATOR', 'INDENT', 'OUTDENT']
     
    -
    +
    + +
  • - -
    h
    -
    +
    diff --git a/documentation/docs/scope.html b/documentation/docs/scope.html index a6866f8a14..7970e6a232 100644 --- a/documentation/docs/scope.html +++ b/documentation/docs/scope.html @@ -4,129 +4,111 @@ scope.litcoffee - + -
    -
    - -
    - -

    scope.litcoffee

    - - - -
    -

    Table of Contents

    -
      - - -
    1. - - browser.coffee - -
    2. - - -
    3. - - cake.coffee - -
    4. - - -
    5. - - coffee-script.coffee - -
    6. - - -
    7. - - command.coffee - -
    8. - - -
    9. - - grammar.coffee - -
    10. - - -
    11. - - helpers.coffee - -
    12. - - -
    13. - - index.coffee - -
    14. - - -
    15. - - lexer.coffee - -
    16. - - -
    17. - - nodes.coffee - -
    18. - - -
    19. - - optparse.coffee - -
    20. - - -
    21. - - repl.coffee - -
    22. - - -
    23. - - rewriter.coffee - -
    24. - - -
    25. - - scope.litcoffee - -
    26. - - -
    27. - - sourcemap.coffee - -
    28. - -
    + - - +
  • +
    +

    scope.litcoffee

    +
    +
  • -

    The Scope class regulates lexical scoping within CoffeeScript. As you + + +

  • +
    + +
    + +
    +

    The Scope class regulates lexical scoping within CoffeeScript. As you 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 @@ -139,25 +121,41 @@

    Table of Contents

    - -
    {extend, last} = require './helpers'
    +            
    + +
    {extend, last} = require './helpers'
     
    -exports.Scope = class Scope
    - - +exports.Scope = class Scope
    + +
  • +
  • +
    + +
    + +
    +

    The root is the top-level Scope object for a given file.

    - -
      @root: null
    - - +
    + +
      @root: null
    + +
  • +
  • +
    + +
    + +
    +

    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 @@ -166,31 +164,47 @@

    Table of Contents

    - -
      constructor: (@parent, @expressions, @method) ->
    +            
    + +
      constructor: (@parent, @expressions, @method) ->
         @variables = [{name: 'arguments', type: 'arguments'}]
         @positions = {}
    -    Scope.root = this unless @parent
    - - + Scope.root = this unless @parent
    + +
  • +
  • +
    + +
    + +
    +

    Adds a new variable or overrides an existing one.

    - -
      add: (name, type, immediate) ->
    +            
    + +
      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
    - - + @positions[name] = @variables.push({name, type}) - 1
    + +
  • +
  • +
    + +
    + +
    +

    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. @@ -200,156 +214,237 @@

    Table of Contents

    - -
      namedMethod: ->
    +            
    + +
      namedMethod: ->
         return @method if @method?.name or !@parent
    -    @parent.namedMethod()
    - - + @parent.namedMethod()
    + +
  • +
  • +
    + +
    + +
    +

    Look up a variable name in lexical scope, and declare it if it does not already exist.

    - -
      find: (name) ->
    +            
    + +
      find: (name) ->
         return yes if @check name
         @add name, 'var'
    -    no
    - - + no
    + +
  • +
  • +
    + +
    + +
    +

    Reserve a variable name as originating from a function parameter for this scope. No var required for internal references.

    - -
      parameter: (name) ->
    +            
    + +
      parameter: (name) ->
         return if @shared and @parent.check name, yes
    -    @add name, 'param'
    - - + @add name, 'param'
    + +
  • +
  • +
    + +
    + +
    +

    Just check to see if a variable has already been declared, without reserving, walks up to the root scope.

    - -
      check: (name) ->
    -    !!(@type(name) or @parent?.check(name))
    - - +
    + +
      check: (name) ->
    +    !!(@type(name) or @parent?.check(name))
    + +
  • +
  • +
    + +
    + +
    +

    Generate a temporary variable name at the given index.

    - -
      temporary: (name, index) ->
    +            
    + +
      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'
    - - + '_' + (index + parseInt name, 36).toString(36).replace /\d/g, 'a'
    + +
  • +
  • +
    + +
    + +
    +

    Gets the type of a variable.

    - -
      type: (name) ->
    +            
    + +
      type: (name) ->
         return v.type for v in @variables when v.name is name
    -    null
    - - + null
    + +
  • +
  • +
    + +
    + +
    +

    If we need to store an intermediate result, find an available name for a compiler-generated variable. _var, _var2, and so on...

    - -
      freeVariable: (name, reserve=true) ->
    +            
    + +
      freeVariable: (name, reserve=true) ->
         index = 0
         index++ while @check((temp = @temporary name, index))
         @add temp, 'var', yes if reserve
    -    temp
    - - + temp
    + +
  • +
  • +
    + +
    + +
    +

    Ensure that an assignment is made at the top of this scope (or at the top-level scope, if requested).

    - -
      assign: (name, value) ->
    +            
    + +
      assign: (name, value) ->
         @add name, {value, assigned: yes}, yes
    -    @hasAssignments = yes
    - - + @hasAssignments = yes
    + +
  • +
  • +
    + +
    + +
    +

    Does this scope have any declared variables?

    - -
      hasDeclarations: ->
    -    !!@declaredVariables().length
    - - +
    + +
      hasDeclarations: ->
    +    !!@declaredVariables().length
    + +
  • +
  • +
    + +
    + +
    +

    Return the list of variables first declared in this scope.

    - -
      declaredVariables: ->
    +            
    + +
      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()
    - - + realVars.sort().concat tempVars.sort()
    + +
  • +
  • +
    + +
    + +
    +

    Return the list of assignments that are supposed to be made at the top of this scope.

    - -
      assignedVariables: ->
    +            
    + +
      assignedVariables: ->
         "#{v.name} = #{v.type.value}" for v in @variables when v.type.assigned
     
    -
    +
    + +
  • - -
    h
    -
    +
    diff --git a/documentation/docs/sourcemap.html b/documentation/docs/sourcemap.html index 609d7f076b..e60be6db56 100644 --- a/documentation/docs/sourcemap.html +++ b/documentation/docs/sourcemap.html @@ -4,169 +4,176 @@ sourcemap.coffee - + -
    -
    - -
    - -

    sourcemap.coffee

    - - - -
    -

    Table of Contents

    -
      - - -
    1. - - browser.coffee - -
    2. - - -
    3. - - cake.coffee - -
    4. - - -
    5. - - coffee-script.coffee - -
    6. - - -
    7. - - command.coffee - -
    8. - - -
    9. - - grammar.coffee - -
    10. - - -
    11. - - helpers.coffee - -
    12. - - -
    13. - - index.coffee - -
    14. - - -
    15. - - lexer.coffee - -
    16. - - -
    17. - - nodes.coffee - -
    18. - - -
    19. - - optparse.coffee - -
    20. - - -
    21. - - repl.coffee - -
    22. - - -
    23. - - rewriter.coffee - -
    24. - - -
    25. - - scope.litcoffee - -
    26. - - -
    27. - - sourcemap.coffee - -
    28. - -
    + - - -

    Hold data about mappings for one line of generated source code. + +

  • +
    + +
    + +
    +

    Hold data about mappings for one line of generated source code.

    - -
    class LineMapping
    -  constructor: (@generatedLine) ->
    - - +
    + +
    class LineMapping
    +  constructor: (@generatedLine) ->
    + +
  • +
  • +
    + +
    + +
    +

    columnMap keeps track of which columns we've already mapped.

    - -
        @columnMap = {}
    - - +
    + +
        @columnMap = {}
    + +
  • +
  • +
    + +
    + +
    +

    columnMappings is an array of all column mappings, sorted by generated-column.

    - -
        @columnMappings = []
    +            
    + +
        @columnMappings = []
     
       addMapping: (generatedColumn, [sourceLine, sourceColumn], options={}) ->
    -    if @columnMap[generatedColumn] and options.noReplace
    - - + if @columnMap[generatedColumn] and options.noReplace
    + +
  • +
  • +
    + +
    + +
    +

    We already have a mapping for this column.

    - -
          return
    +            
    + +
          return
     
         @columnMap[generatedColumn] = {
           generatedLine: @generatedLine
    @@ -187,11 +194,18 @@ 

    Table of Contents

    else lastColumnMapping = columnMapping if lastColumnMapping - answer = [lastColumnMapping.sourceLine, lastColumnMapping.sourceColumn]
    - - + answer = [lastColumnMapping.sourceLine, lastColumnMapping.sourceColumn]
    + +
  • +
  • +
    + +
    + +
    +

    SourceMap

    Maps locations in a generated source file back to locations in the original source file. @@ -203,23 +217,39 @@

    SourceMap

    - -
    class exports.SourceMap
    -  constructor: () ->
    - - +
    + +
    class exports.SourceMap
    +  constructor: () ->
    + +
  • +
  • +
    + +
    + +
    +

    generatedLines is an array of LineMappings, one per generated line.

    - -
        @generatedLines = []
    - - +
    + +
        @generatedLines = []
    + +
  • +
  • +
    + +
    + +
    +

    Adds a mapping to this SourceMap.

    @@ -231,45 +261,69 @@

    SourceMap

    - -
      addMapping: (sourceLocation, generatedLocation, options={}) ->
    +            
    + +
      addMapping: (sourceLocation, generatedLocation, options={}) ->
         [generatedLine, generatedColumn] = generatedLocation
     
         lineMapping = @generatedLines[generatedLine]
         if not lineMapping
           lineMapping = @generatedLines[generatedLine] = new LineMapping(generatedLine)
     
    -    lineMapping.addMapping generatedColumn, sourceLocation, options
    - - + lineMapping.addMapping generatedColumn, sourceLocation, options
    + +
  • +
  • +
    + +
    + +
    +

    Returns [sourceLine, sourceColumn], or null if no mapping could be found.

    - -
      getSourcePosition: ([generatedLine, generatedColumn]) ->
    +            
    + +
      getSourcePosition: ([generatedLine, generatedColumn]) ->
         answer = null
         lineMapping = @generatedLines[generatedLine]
    -    if not lineMapping
    - - + if not lineMapping
    + +
  • +
  • +
    + +
    + +
    +

    TODO: Search backwards for the line?

    - -
        else
    +            
    + +
        else
           answer = lineMapping.getSourcePosition generatedColumn
     
    -    answer
    - - + answer
    + +
  • +
  • +
    + +
    + +
    +

    fn will be called once for every recorded mapping, in the order in which they occur in the generated source. fn will be passed an object with four properties: sourceLine, sourceColumn, generatedLine, and @@ -277,16 +331,24 @@

    SourceMap

    - -
      forEachMapping: (fn) ->
    +            
    + +
      forEachMapping: (fn) ->
         for lineMapping, generatedLineNumber in @generatedLines
           if lineMapping
             for columnMapping in lineMapping.columnMappings
    -          fn(columnMapping)
    - - + fn(columnMapping)
    + +
  • +
  • +
    + +
    + +
    +

    generateV3SourceMap

    Builds a V3 source map from a SourceMap object. Returns the generated JSON as a string. @@ -294,8 +356,9 @@

    generateV3SourceMap

    - -
    exports.generateV3SourceMap = (sourceMap, sourceFile=null, generatedFile=null) ->
    +            
    + +
    exports.generateV3SourceMap = (sourceMap, sourceFile=null, generatedFile=null) ->
       writingGeneratedLine = 0
       lastGeneratedColumnWritten = 0
       lastSourceLineWritten = 0
    @@ -309,23 +372,38 @@ 

    generateV3SourceMap

    lastGeneratedColumnWritten = 0 needComma = no mappings += ";" - writingGeneratedLine++
    - - + writingGeneratedLine++
    + +
  • +
  • +
    + +
    + +
    +

    Write a comma if we've already written a segment on this line.

    - -
        if needComma
    +            
    + +
        if needComma
           mappings += ","
    -      needComma = no
    - - + needComma = no
    + +
  • +
  • +
    + +
    + +
    +

    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. @@ -343,52 +421,85 @@

    generateV3SourceMap

    - -
        mappings += exports.vlqEncodeValue(mapping.generatedColumn - lastGeneratedColumnWritten)
    -    lastGeneratedColumnWritten = mapping.generatedColumn
    - - +
    + +
        mappings += exports.vlqEncodeValue(mapping.generatedColumn - lastGeneratedColumnWritten)
    +    lastGeneratedColumnWritten = mapping.generatedColumn
    + +
  • +
  • +
    + +
    + +
    +

    Add the index into the sources list

    - -
        mappings += exports.vlqEncodeValue(0)
    - - +
    + +
        mappings += exports.vlqEncodeValue(0)
    + +
  • +
  • +
    + +
    + +
    +

    Add the source start-line

    - -
        mappings += exports.vlqEncodeValue(mapping.sourceLine - lastSourceLineWritten)
    -    lastSourceLineWritten = mapping.sourceLine
    - - +
    + +
        mappings += exports.vlqEncodeValue(mapping.sourceLine - lastSourceLineWritten)
    +    lastSourceLineWritten = mapping.sourceLine
    + +
  • +
  • +
    + +
    + +
    +

    Add the source start-column

    - -
        mappings += exports.vlqEncodeValue(mapping.sourceColumn - lastSourceColumnWritten)
    -    lastSourceColumnWritten = mapping.sourceColumn
    - - +
    + +
        mappings += exports.vlqEncodeValue(mapping.sourceColumn - lastSourceColumnWritten)
    +    lastSourceColumnWritten = mapping.sourceColumn
    + +
  • +
  • +
    + +
    + +
    +

    TODO: Do we care about symbol names for CoffeeScript? Probably not.

    - -
        needComma = yes
    +            
    + +
        needComma = yes
     
       answer = {
         version: 3
    @@ -399,26 +510,42 @@ 

    generateV3SourceMap

    mappings } - return JSON.stringify answer, null, 2
    - - + return JSON.stringify answer, null, 2
    + +
  • +
  • +
    + +
    + +
    +

    Load a SourceMap from a JSON string. Returns the SourceMap object.

    - -
    exports.loadV3SourceMap = (sourceMap) ->
    -  todo()
    - - +
    + +
    exports.loadV3SourceMap = (sourceMap) ->
    +  todo()
    + +
  • +
  • +
    + +
    + +
    +

    Base64 encoding helpers

    - -
    BASE64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
    +            
    + +
    BASE64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
     MAX_BASE64_VALUE = BASE64_CHARS.length - 1
     
     encodeBase64Char = (value) ->
    @@ -432,11 +559,18 @@ 

    Base64 encoding helpers

    value = BASE64_CHARS.indexOf char if value == -1 throw new Error "Invalid Base 64 character: #{char}" - value
    - - + value
    + +
  • +
  • +
    + +
    + +
    +

    Base 64 VLQ encoding/decoding helpers

    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 @@ -447,52 +581,85 @@

    Base 64 VLQ encoding/decoding helpers

    - -
    VLQ_SHIFT      = 5
    +            
    + +
    VLQ_SHIFT      = 5
     VLQ_CONTINUATION_BIT = 1 << VLQ_SHIFT # 0010 0000
    -VLQ_VALUE_MASK       = VLQ_CONTINUATION_BIT - 1 # 0001 1111
    - - +VLQ_VALUE_MASK = VLQ_CONTINUATION_BIT - 1 # 0001 1111
    + +
  • +
  • +
    + +
    + +
    +

    Encode a value as Base 64 VLQ.

    - -
    exports.vlqEncodeValue = (value) ->
    - - +
    + +
    exports.vlqEncodeValue = (value) ->
    + +
  • +
  • +
    + +
    + +
    +

    Least significant bit represents the sign.

    - -
      signBit = if value < 0 then 1 else 0
    - - +
    + +
      signBit = if value < 0 then 1 else 0
    + +
  • +
  • +
    + +
    + +
    +

    Next bits are the actual value

    - -
      valueToEncode = (Math.abs(value) << 1) + signBit
    +            
    + +
      valueToEncode = (Math.abs(value) << 1) + signBit
     
    -  answer = ""
    - - + answer = ""
    + +
  • +
  • +
    + +
    + +
    +

    Make sure we encode at least one character, even if valueToEncode is 0.

    - -
      while valueToEncode || !answer
    +            
    + +
      while valueToEncode || !answer
         nextVlqChunk = valueToEncode & VLQ_VALUE_MASK
         valueToEncode = valueToEncode >> VLQ_SHIFT
     
    @@ -501,11 +668,18 @@ 

    Base 64 VLQ encoding/decoding helpers

    answer += encodeBase64Char(nextVlqChunk) - return answer
    - - + return answer
    + +
  • +
  • +
    + +
    + +
    +

    Decode a Base 64 VLQ value.

    @@ -514,8 +688,9 @@

    Base 64 VLQ encoding/decoding helpers

    - -
    exports.vlqDecodeValue = (str, offset=0) ->
    +            
    + +
    exports.vlqDecodeValue = (str, offset=0) ->
       position = offset
       done = false
     
    @@ -529,50 +704,74 @@ 

    Base 64 VLQ encoding/decoding helpers

    nextChunkValue = nextVlqChunk & VLQ_VALUE_MASK value += (nextChunkValue << continuationShift) - if !(nextVlqChunk & VLQ_CONTINUATION_BIT)
    - - + if !(nextVlqChunk & VLQ_CONTINUATION_BIT)
    + +
  • +
  • +
    + +
    + +
    +

    We'll be done after this character.

    - -
          done = true
    - - +
    + +
          done = true
    + +
  • +
  • +
    + +
    + +
    +

    Bits are encoded least-significant first (opposite of MIDI VLQ). Increase the continuationShift, so the next byte will end up where it should in the value.

    - -
        continuationShift += VLQ_SHIFT
    +            
    + +
        continuationShift += VLQ_SHIFT
     
    -  consumed = position - offset
    - - + consumed = position - offset
    + +
  • +
  • +
    + +
    + +
    +

    Least significant bit represents the sign.

    - -
      signBit = value & 1
    +            
    + +
      signBit = value & 1
       value = value >> 1
     
       if signBit then value = -value
     
       return [value, consumed]
     
    -
    +
    + +
  • - -
    h
    -
    +