From c2708b45034f1e5c9e00d45475d632e2ee39e9d6 Mon Sep 17 00:00:00 2001
From: Jack Allnutt Hello ' + escape((interp = name) == null ? '' : interp) + '\n Hello ' + escape((interp = name) == null ? '' : interp) + '\n wahoo! foo bar baz rawr..... #{something} foo asdf
- asdf
- asdfasdfaf
- asdf
- asd
- .
- .
foo
-bar
- -Jade also supports unbuffered comments, by simply adding a hyphen: - - //- will not output within markup - p foo - p bar - -outputting - -foo
-bar
- -### Block Comments - - A block comment is legal as well: - - body - // - #content - h1 Example - -outputting - - - - - -Jade supports conditional-comments as well, for example: - - body - //if IE - a(href='http://www.mozilla.com/en-US/firefox/') Get Firefox - -outputs: - - - - - - -### Nesting - - Jade supports nesting to define the tags in a natural way: - - ul - li.first - a(href='#') foo - li - a(href='#') bar - li.last - a(href='#') baz - -### Block Expansion - - Block expansion allows you to create terse single-line nested tags, - the following example is equivalent to the nesting example above. - - ul - li.first: a(href='#') foo - li: a(href='#') bar - li.last: a(href='#') baz - - -### Attributes - -Jade currently supports '(' and ')' as attribute delimiters. - - a(href='/login', title='View login page') Login - -When a value is `undefined` or `null` the attribute is _not_ added, -so this is fine, it will not compile 'something="null"'. - - div(something=null) - -Boolean attributes are also supported: - - input(type="checkbox", checked) - -Boolean attributes with code will only output the attribute when `true`: - - input(type="checkbox", checked=someValue) - -Multiple lines work too: - - input(type='checkbox', - name='agreement', - checked) - -Multiple lines without the comma work fine: - - input(type='checkbox' - name='agreement' - checked) - -Funky whitespace? fine: - - - input( - type='checkbox' - name='agreement' - checked) - -Colons work: - - rss(xmlns:atom="atom") - -Suppose we have the `user` local `{ id: 12, name: 'tobi' }` -and we wish to create an anchor tag with `href` pointing to "/user/12" -we could use regular javascript concatenation: - - a(href='/user/' + user.id)= user.name - -or we could use jade's interpolation, which I added because everyone -using Ruby or CoffeeScript seems to think this is legal js..: - - a(href='/user/#{user.id}')= user.name - -The `class` attribute is special-cased when an array is given, -allowing you to pass an array such as `bodyClasses = ['user', 'authenticated']` directly: - - body(class=bodyClasses) - -### HTML - - Inline html is fine, we can use the pipe syntax to - write arbitrary text, in this case some html: - -``` -html - body - |foo bar baz
-``` - - Or we can use the trailing `.` to indicate to Jade that we - only want text in this block, allowing us to omit the pipes: - -``` -html - body. -foo bar baz
-``` - - Both of these examples yield the same result: - -``` -foo bar baz
- -``` - - The same rule applies for anywhere you can have text - in jade, raw html is fine: - -``` -html - body - h1 User #{name} -``` - -### Doctypes - -To add a doctype simply use `!!!`, or `doctype` followed by an optional value: - - !!! - -Will output the _transitional_ doctype, however: - - !!! 5 - -or - - !!! html - -or - - doctype html - -doctypes are case-insensitive, so the following are equivalent: - - doctype Basic - doctype basic - -Will output the _html 5_ doctype. Below are the doctypes -defined by default, which can easily be extended: - -```javascript - var doctypes = exports.doctypes = { - '5': '', - 'xml': '', - 'default': '', - 'transitional': '', - 'strict': '', - 'frameset': '', - '1.1': '', - 'basic': '', - 'mobile': '' - }; -``` - -To alter the default simply change: - -```javascript - jade.doctypes.default = 'whatever you want'; -``` - -## Filters - -Filters are prefixed with `:`, for example `:markdown` and -pass the following block of text to an arbitrary function for processing. View the _features_ -at the top of this document for available filters. - - body - :markdown - Woah! jade _and_ markdown, very **cool** - we can even link to [stuff](http://google.com) - -Renders: - -Woah! jade and markdown, very cool we can even link to stuff
- -Filters may also manipulate the parse tree. For example perhaps I want to -bake conditionals right into jade, we could do so with a filter named _conditionals_. Typically filters work on text blocks, however by passing a regular block our filter can do anything it wants with the tags nested within it. - - body - conditionals: - if role == 'admin' - p You are amazing - else - p Not so amazing - -Not that we no longer prefix with "-" for these code blocks. Examples of -how to manipulate the parse tree can be found at _./examples/conditionals.js_ and _./examples/model.js_, basically we subclass and re-implement visitor methods as needed. There are several interesting use-cases for this functionality above what was shown above such as transparently aggregating / compressing assets to reduce the number of HTTP requests, transparent record error reporting, and more. - -## Code - -Jade currently supports three classifications of executable code. The first -is prefixed by `-`, and is not buffered: - - - var foo = 'bar'; - -This can be used for conditionals, or iteration: - - - for (var key in obj) - p= obj[key] - -Due to Jade's buffering techniques the following is valid as well: - - - if (foo) - ul - li yay - li foo - li worked - - else - p oh no! didnt work - -Hell, even verbose iteration: - - - if (items.length) - ul - - items.forEach(function(item){ - li= item - - }) - -Anything you want! - -Next up we have _escaped_ buffered code, which is used to -buffer a return value, which is prefixed by `=`: - - - var foo = 'bar' - = foo - h1= foo - -Which outputs `barWelcome to my super lame site.
- - - -``` - -## Mixins - - Mixins are converted to regular JavaScript functions in - the compiled template that Jade constructs. Mixins may - take arguments, though not required: - - mixin list - ul - li foo - li bar - li baz - - Utilizing a mixin without args looks similar, just without a block: - - h2 Groceries - mixin list - - Mixins may take one or more arguments as well, the arguments - are regular javascripts expressions, so for example the following: - - mixin pets(pets) - ul.pets - - each pet in pets - li= pet - - mixin profile(user) - .user - h2= user.name - mixin pets(user.pets) - - Would yield something similar to the following html: - -```html -'); - buf.push('Just an example'); - buf.push('
'); - } - return buf.join(""); - } catch (err) { - rethrow(err, __.input, __.filename, __.lineno); - } -} -``` - -When the `compileDebug` option _is_ explicitly `false`, this instrumentation -is stripped, which is very helpful for light-weight client-side templates. Combining Jade's options with the `./runtime.js` file in this repo allows you -to toString() compiled templates and avoid running the entire Jade library on -the client, increasing performance, and decreasing the amount of JavaScript -required. - -```js -function anonymous(locals) { - var attrs = jade.attrs, escape = jade.escape; - var buf = []; - with (locals || {}) { - var interp; - var title = 'yay' - buf.push(''); - buf.push('Just an example'); - buf.push('
'); - } - return buf.join(""); -} -``` - -## Example Makefile - - Below is an example Makefile used to compile _pages/*.jade_ - into _pages/*.html_ files by simply executing `make`. - -```make -JADE = $(shell find pages/*.jade) -HTML = $(JADE:.jade=.html) - -all: $(HTML) - -%.html: %.jade - jade < $< > $@ - -clean: - rm -f $(HTML) - -.PHONY: clean -``` - -this can be combined with the `watch(1)` command to produce -a watcher-like behaviour: - - $ watch make - -## jade(1) - -``` - -Usage: jade [options] [dir|file ...] - -Options: - - -h, --help output usage information - -v, --version output the version number - -o, --obj browser.coffee | |
---|---|
Override exported methods for non-Node.js engines. | CoffeeScript = require './coffee-script' |
Use standard JavaScript | CoffeeScript.eval = (code, options) ->
- eval CoffeeScript.compile code, options |
Running code does not provide access to this scope. | CoffeeScript.run = (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? |
Load a remote script from the current domain via XHR. | CoffeeScript.load = (url, options) ->
- xhr = new (window.ActiveXObject or XMLHttpRequest)('Microsoft.XMLHTTP')
- xhr.open 'GET', url, true
- xhr.overrideMimeType 'text/plain' if 'overrideMimeType' of xhr
- xhr.onreadystatechange = ->
- CoffeeScript.run xhr.responseText, options if xhr.readyState is 4
- xhr.send null |
Activate CoffeeScript in the browser by having it compile and evaluate
-all script tags with a content-type of | processScripts = ->
- for script in document.getElementsByTagName 'script'
- if script.type is 'text/coffeescript'
- if script.src
- CoffeeScript.load script.src
- else
- setTimeout -> CoffeeScript.run script.innerHTML
- null
-if window.addEventListener
- addEventListener 'DOMContentLoaded', processScripts, false
-else
- attachEvent 'onload', processScripts
-
- |
cake.coffee | |
---|---|
Running | |
External dependencies. | fs = require 'fs'
-path = require 'path'
-helpers = require('./helpers').helpers
-optparse = require './optparse'
-CoffeeScript = require './coffee-script' |
Keep track of the list of defined tasks, the accepted options, and so on. | tasks = {}
-options = {}
-switches = []
-oparse = null |
Mixin the top-level Cake functions for Cakefiles to use directly. | 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) ->
- [action, description] = [description, action] unless 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] |
Invoke another task in the current Cakefile. | invoke: (name) ->
- missingTask name unless tasks[name]
- tasks[name].action options |
Run | exports.run = ->
- path.exists 'Cakefile', (exists) ->
- throw new Error("Cakefile not found in #{process.cwd()}") unless exists
- args = process.argv[2...process.argv.length]
- CoffeeScript.run fs.readFileSync('Cakefile').toString(), fileName: 'Cakefile'
- oparse = new optparse.OptionParser switches
- return printTasks() unless args.length
- options = oparse.parse(args)
- invoke arg for arg in options.arguments |
Display the list of Cake tasks in a format similar to | printTasks = ->
- puts ''
- for all name, task of tasks
- spaces = 20 - name.length
- spaces = if spaces > 0 then Array(spaces + 1).join(' ') else ''
- desc = if task.description then "# #{task.description}" else ''
- puts "cake #{name}#{spaces} #{desc}"
- puts oparse.help() if switches.length |
Print an error and exit when attempting to all an undefined task. | missingTask = (task) ->
- puts "No such task: \"#{task}\""
- process.exit 1
-
- |
coffee-script.coffee | |
---|---|
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 tokenzing, parsing, and compiling source -CoffeeScript into JavaScript. - -If included on a webpage, it will automatically sniff out, compile, and
-execute all scripts present in | path = require 'path'
-{Lexer} = require './lexer'
-{parser} = require './parser' |
TODO: Remove registerExtension when fully deprecated | if require.extensions
- fs = require 'fs'
- require.extensions['.coffee'] = (module, filename) ->
- content = compile fs.readFileSync filename, 'utf8'
- module.filename = "#{filename} (compiled)"
- module._compile content, module.filename
-else if require.registerExtension
- require.registerExtension '.coffee', (content) -> compile content |
The current CoffeeScript version number. | exports.VERSION = '0.9.4' |
Compile a string of CoffeeScript code to JavaScript, using the Coffee/Jison -compiler. | exports.compile = compile = (code, options) ->
- options or= {}
- try
- (parser.parse lexer.tokenize code).compile options
- catch err
- err.message = "In #{options.fileName}, #{err.message}" if options.fileName
- throw err |
Tokenize a string of CoffeeScript code, and return the array of tokens. | exports.tokens = (code) ->
- lexer.tokenize code |
Tokenize and parse a string of CoffeeScript code, and return the AST. You can
-then compile it by calling | exports.nodes = (code) ->
- parser.parse lexer.tokenize code |
Compile and execute a string of CoffeeScript (on the server), correctly
-setting | exports.run = (code, options) -> |
We want the root module. | root = module
- while root.parent
- root = root.parent |
Set the filename | root.filename = __filename = "#{options.fileName} (compiled)" |
Clear the module cache | root.moduleCache = {} if root.moduleCache |
Compile | root._compile exports.compile(code, options), root.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) ->
- __filename = options.fileName
- __dirname = path.dirname __filename
- eval exports.compile(code, options) |
Instantiate a Lexer for our use here. | 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 =
- lex: ->
- token = @tokens[@pos] or [""]
- @pos += 1
- this.yylineno = token[2]
- this.yytext = token[1]
- token[0]
- setInput: (tokens) ->
- @tokens = tokens
- @pos = 0
- upcomingInput: -> ""
-
-parser.yy = require './nodes'
-
- |
command.coffee | |
---|---|
The | |
External dependencies. | fs = require 'fs'
-path = require 'path'
-optparse = require './optparse'
-CoffeeScript = require './coffee-script'
-{helpers} = require './helpers'
-{spawn, exec} = require 'child_process'
-{EventEmitter} = require 'events' |
Allow CoffeeScript to emit Node.js events, and add it to global scope. | helpers.extend CoffeeScript, new EventEmitter
-global.CoffeeScript = CoffeeScript |
The help banner that is printed when | BANNER = '''
- coffee compiles CoffeeScript source files into JavaScript.
-
- Usage:
- coffee path/to/script.coffee
- ''' |
The list of all the valid option flags that | SWITCHES = [
- ['-c', '--compile', 'compile to JavaScript and save as .js files']
- ['-i', '--interactive', 'run an interactive CoffeeScript REPL']
- ['-o', '--output [DIR]', 'set the directory for compiled JavaScript']
- ['-w', '--watch', 'watch scripts for changes, and recompile']
- ['-p', '--print', 'print the compiled JavaScript to stdout']
- ['-l', '--lint', 'pipe the compiled JavaScript through JSLint']
- ['-s', '--stdio', 'listen for and compile scripts over stdio']
- ['-e', '--eval', 'compile a string from the command line']
- ['-r', '--require [FILE*]', 'require a library before executing your script']
- [ '--no-wrap', 'compile without the top-level function wrapper']
- ['-t', '--tokens', 'print the tokens that the lexer produces']
- ['-n', '--nodes', 'print the parse tree that Jison produces']
- ['-v', '--version', 'display CoffeeScript version']
- ['-h', '--help', 'display this help message']
-] |
Top-level objects shared by all the functions. | opts = {}
-sources = []
-optionParser = null |
Run | exports.run = ->
- parseOptions()
- return usage() if opts.help
- return version() if opts.version
- return require './repl' if opts.interactive
- return compileStdio() if opts.stdio
- return compileScript 'console', sources[0] if opts.eval
- return require './repl' unless sources.length
- separator = sources.indexOf '--'
- flags = []
- if separator >= 0
- flags = sources[(separator + 1)...sources.length]
- sources = sources[0...separator]
- if opts.run
- flags = sources[1..sources.length].concat flags
- sources = [sources[0]]
- process.ARGV = process.argv = flags
- compileScripts() |
Asynchronously read in each CoffeeScript in a list of source files and -compile them. If a directory is passed, recursively compile all -'.coffee' extension source files in it and all subdirectories. | compileScripts = ->
- for source in sources
- base = source
- compile = (source, topLevel) ->
- path.exists source, (exists) ->
- throw new Error "File not found: #{source}" unless exists
- fs.stat source, (err, stats) ->
- if stats.isDirectory()
- fs.readdir source, (err, files) ->
- for file in files
- compile path.join(source, file)
- else if topLevel or path.extname(source) is '.coffee'
- fs.readFile source, (err, code) -> compileScript(source, code.toString(), base)
- watch source, base if opts.watch
- compile source, true |
Compile a single source script, containing the given code, according to the
-requested options. If evaluating the script directly sets | compileScript = (file, input, base) ->
- o = opts
- options = compileOptions file
- if o.require
- require(if helpers.starts(req, '.') then fs.realpathSync(req) else req) for req in o.require
- try
- t = task = {file, input, options}
- CoffeeScript.emit 'compile', task
- if o.tokens then printTokens CoffeeScript.tokens t.input
- else if o.nodes then puts CoffeeScript.nodes(t.input).toString()
- else if o.run then CoffeeScript.run t.input, t.options
- else
- t.output = CoffeeScript.compile t.input, t.options
- CoffeeScript.emit 'success', task
- if o.print then print t.output
- else if o.compile then writeJs t.file, t.output, base
- else if o.lint then lint t.output
- catch err |
Avoid using 'error' as it is a special event -- if there is no handler, -node will print a stack trace and exit the program. | CoffeeScript.emit 'failure', err, task
- return if CoffeeScript.listeners('failure').length
- return puts err.message if o.watch
- error err.stack
- process.exit 1 |
Attach the appropriate listeners to compile scripts incoming over stdin, -and write them back to stdout. | compileStdio = ->
- code = ''
- stdin = process.openStdin()
- stdin.on 'data', (buffer) ->
- code += buffer.toString() if buffer
- stdin.on 'end', ->
- compileScript 'stdio', code |
Watch a source CoffeeScript file using | watch = (source, base) ->
- fs.watchFile source, {persistent: true, interval: 500}, (curr, prev) ->
- return if curr.size is prev.size and curr.mtime.getTime() is prev.mtime.getTime()
- fs.readFile source, (err, code) ->
- throw err if err
- compileScript(source, code.toString(), base) |
Write out a JavaScript source file with the compiled code. By default, files
-are written out in | writeJs = (source, js, base) ->
- filename = path.basename(source, path.extname(source)) + '.js'
- srcDir = path.dirname source
- baseDir = srcDir.substring base.length
- dir = if opts.output then path.join opts.output, baseDir else srcDir
- jsPath = path.join dir, filename
- compile = ->
- js = ' ' if js.length <= 0
- fs.writeFile jsPath, js, (err) ->
- puts "Compiled #{source}" if opts.compile and opts.watch
- path.exists dir, (exists) ->
- if exists then compile() else exec "mkdir -p #{dir}", compile |
Pipe compiled JS through JSLint (requires a working | lint = (js) ->
- printIt = (buffer) -> puts 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() |
Pretty-print a stream of tokens. | printTokens = (tokens) ->
- strings = for token in tokens
- [tag, value] = [token[0], token[1].toString().replace(/\n/, '\\n')]
- "[#{tag} #{value}]"
- puts strings.join(' ') |
Use the OptionParser module to extract all options from
- | parseOptions = ->
- optionParser = new optparse.OptionParser SWITCHES, BANNER
- o = opts = optionParser.parse(process.argv[2...process.argv.length])
- o.compile or= !!o.output
- o.run = not (o.compile or o.print or o.lint)
- o.print = !! (o.print or (o.eval or o.stdio and o.compile))
- sources = o.arguments |
The compile-time options to pass to the CoffeeScript compiler. | compileOptions = (fileName) ->
- o = {fileName}
- o.noWrap = opts['no-wrap']
- o |
Print the | usage = ->
- puts optionParser.help()
- process.exit 0 |
Print the | version = ->
- puts "CoffeeScript version #{CoffeeScript.VERSION}"
- process.exit 0
-
- |
grammar.coffee | |
---|---|
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) -type grammars. To create the Jison parser, we list the pattern to match -on the left-hand side, and the action to take (usually the creation of syntax -tree nodes) on the right. As the parser runs, it -shifts tokens from our token stream, from left to right, and -attempts to match -the token sequence against the rules below. When a match can be made, it -reduces into the nonterminal -(the enclosing name at the top), and we proceed from there. - -If you run the | |
The only dependency is on the Jison.Parser. | Parser = require('jison').Parser |
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*\}/ |
Our handy DSL for Jison grammar generation, thanks to -Tim Caswell. For every rule in the grammar, -we pass the pattern-defining string, the action to run, and extra options, -optionally. If no action is specified, we simply pass the value of the -previous nonterminal. | o = (patternString, action, options) ->
- return [patternString, '$$ = $1;', options] unless action
- action = if match = (action + '').match(unwrap) then match[1] else "(#{action}())"
- action = action.replace /\b(?:[A-Z][a-z]+Node|Expressions)\b/g, 'yy.$&'
- [patternString, "$$ = #{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 -their numeric position, so in this rule: - -
-
-
| grammar = |
The Root is the top-level node in the syntax tree. Since we parse bottom-up, -all parsing must end here. | Root: [
- o "", -> new Expressions
- o "TERMINATOR", -> new Expressions
- o "Body"
- o "Block TERMINATOR"
- ] |
Any list of statements and expressions, seperated by line breaks or semicolons. | Body: [
- o "Line", -> Expressions.wrap [$1]
- o "Body TERMINATOR Line", -> $1.push $3
- o "Body TERMINATOR"
- ] |
Expressions and statements, which make up a line in a body. | Line: [
- o "Expression"
- o "Statement"
- ] |
Pure statements which cannot be expressions. | Statement: [
- o "Return"
- o "Throw"
- o "BREAK", -> new LiteralNode $1
- o "CONTINUE", -> new LiteralNode $1
- o "DEBUGGER", -> new LiteralNode $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. Expressions serve as the building blocks of many other rules, making -them somewhat circular. | Expression: [
- o "Value"
- o "Call"
- o "Code"
- o "Operation"
- o "Assign"
- o "If"
- o "Try"
- o "While"
- o "For"
- o "Switch"
- o "Extends"
- o "Class"
- o "Existence"
- o "Comment"
- ] |
An indented block of expressions. Note that the Rewriter -will convert some postfix forms into blocks for us, by adjusting the -token stream. | Block: [
- o "INDENT Body OUTDENT", -> $2
- o "INDENT OUTDENT", -> new Expressions
- o "TERMINATOR Comment", -> Expressions.wrap [$2]
- ] |
A literal identifier, a variable name or property. | Identifier: [
- o "IDENTIFIER", -> new LiteralNode $1
- ] |
Alphanumerics are separated from the other Literal matchers because -they can also serve as keys in object literals. | AlphaNumeric: [
- o "NUMBER", -> new LiteralNode $1
- o "STRING", -> new LiteralNode $1
- ] |
All of our immediate values. These can (in general), be passed straight -through and printed to JavaScript. | Literal: [
- o "AlphaNumeric"
- o "JS", -> new LiteralNode $1
- o "REGEX", -> new LiteralNode $1
- o "TRUE", -> new LiteralNode true
- o "FALSE", -> new LiteralNode false
- o "YES", -> new LiteralNode true
- o "NO", -> new LiteralNode false
- o "ON", -> new LiteralNode true
- o "OFF", -> new LiteralNode false
- ] |
Assignment of a variable, property, or index to a value. | Assign: [
- o "Assignable = Expression", -> new AssignNode $1, $3
- o "Assignable = INDENT Expression OUTDENT", -> new AssignNode $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: [
- o "Identifier", -> new ValueNode $1
- o "AlphaNumeric"
- o "Identifier : Expression", -> new AssignNode new ValueNode($1), $3, 'object'
- o "AlphaNumeric : Expression", -> new AssignNode new ValueNode($1), $3, 'object'
- o "Identifier : INDENT Expression OUTDENT", -> new AssignNode new ValueNode($1), $4, 'object'
- o "AlphaNumeric : INDENT Expression OUTDENT", -> new AssignNode new ValueNode($1), $4, 'object'
- o "Comment"
- ] |
A return statement from a function body. | Return: [
- o "RETURN Expression", -> new ReturnNode $2
- o "RETURN", -> new ReturnNode new ValueNode new LiteralNode 'null'
- ] |
A block comment. | Comment: [
- o "HERECOMMENT", -> new CommentNode $1
- ] |
Existence: [
- o "Expression ?", -> new ExistenceNode $1
- ] | |
The Code node is the function literal. It's defined by an indented block -of Expressions preceded by a function arrow, with an optional parameter -list. | Code: [
- o "PARAM_START ParamList PARAM_END FuncGlyph Block", -> new CodeNode $2, $5, $4
- o "FuncGlyph Block", -> new CodeNode [], $2, $1
- ] |
CoffeeScript has two different symbols for functions. | FuncGlyph: [
- o "->", -> 'func'
- o "=>", -> 'boundfunc'
- ] |
An optional, trailing comma. | OptComma: [
- o ''
- o ','
- ] |
The list of parameters that a function accepts can be of any length. | ParamList: [
- o "", -> []
- o "Param", -> [$1]
- o "ParamList , Param", -> $1.concat [$3]
- ] |
A single parameter in a function definition can be ordinary, or a splat -that hoovers up the remaining arguments. | Param: [
- o "PARAM", -> new LiteralNode $1
- o "@ PARAM", -> new ParamNode $2, true
- o "PARAM . . .", -> new ParamNode $1, false, true
- o "@ PARAM . . .", -> new ParamNode $2, true, true
- ] |
A splat that occurs outside of a parameter list. | Splat: [
- o "Expression . . .", -> new SplatNode $1
- ] |
Variables and properties that can be assigned to. | SimpleAssignable: [
- o "Identifier", -> new ValueNode $1
- o "Value Accessor", -> $1.push $2
- o "Invocation Accessor", -> new ValueNode $1, [$2]
- o "ThisProperty"
- ] |
Everything that can be assigned to. | Assignable: [
- o "SimpleAssignable"
- o "Array", -> new ValueNode $1
- o "Object", -> new ValueNode $1
- ] |
The types of things that can be treated as values -- assigned to, invoked -as functions, indexed into, named as a class, etc. | Value: [
- o "Assignable"
- o "Literal", -> new ValueNode $1
- o "Parenthetical", -> new ValueNode $1
- o "Range", -> new ValueNode $1
- o "This"
- o "NULL", -> new ValueNode new LiteralNode 'null'
- ] |
The general group of accessors into an object, by property, by prototype -or by array index or slice. | Accessor: [
- o "PROPERTY_ACCESS Identifier", -> new AccessorNode $2
- o "PROTOTYPE_ACCESS Identifier", -> new AccessorNode $2, 'prototype'
- o "::", -> new AccessorNode(new LiteralNode('prototype'))
- o "SOAK_ACCESS Identifier", -> new AccessorNode $2, 'soak'
- o "Index"
- o "Slice", -> new SliceNode $1
- ] |
Indexing into an object or array using bracket notation. | Index: [
- o "INDEX_START Expression INDEX_END", -> new IndexNode $2
- o "INDEX_SOAK Index", -> $2.soakNode = yes; $2
- o "INDEX_PROTO Index", -> $2.proto = yes; $2
- ] |
In CoffeeScript, an object literal is simply a list of assignments. | Object: [
- o "{ AssignList OptComma }", -> new ObjectNode $2
- ] |
Assignment of properties within an object literal can be separated by -comma, as in JavaScript, or simply by newline. | AssignList: [
- o "", -> []
- o "AssignObj", -> [$1]
- o "AssignList , AssignObj", -> $1.concat [$3]
- o "AssignList OptComma TERMINATOR AssignObj", -> $1.concat [$4]
- o "AssignList OptComma INDENT AssignList OptComma OUTDENT", -> $1.concat $4
- ] |
Class definitions have optional bodies of prototype property assignments, -and optional references to the superclass. | Class: [
- o "CLASS SimpleAssignable", -> new ClassNode $2
- o "CLASS SimpleAssignable EXTENDS Value", -> new ClassNode $2, $4
- o "CLASS SimpleAssignable INDENT ClassBody OUTDENT", -> new ClassNode $2, null, $4
- o "CLASS SimpleAssignable EXTENDS Value INDENT ClassBody OUTDENT", -> new ClassNode $2, $4, $6
- o "CLASS INDENT ClassBody OUTDENT", -> new ClassNode '__temp__', null, $3
- ] |
Assignments that can happen directly inside a class declaration. | ClassAssign: [
- o "AssignObj", -> $1
- o "ThisProperty : Expression", -> new AssignNode new ValueNode($1), $3, 'this'
- o "ThisProperty : INDENT Expression OUTDENT", -> new AssignNode new ValueNode($1), $4, 'this'
- ] |
A list of assignments to a class. | ClassBody: [
- o "", -> []
- o "ClassAssign", -> [$1]
- o "ClassBody TERMINATOR ClassAssign", -> $1.concat $3
- o "{ ClassBody }", -> $2
- ] |
The two flavors of function call: normal, and object instantiation with | Call: [
- o "Invocation"
- o "NEW Invocation", -> $2.newInstance()
- o "NEW Value", -> (new CallNode($2, [])).newInstance()
- ] |
Extending an object by setting its prototype chain to reference a parent -object. | Extends: [
- o "SimpleAssignable EXTENDS Value", -> new ExtendsNode $1, $3
- ] |
Ordinary function invocation, or a chained series of calls. | Invocation: [
- o "Value OptFuncExist Arguments", -> new CallNode $1, $3, $2
- o "Invocation OptFuncExist Arguments", -> new CallNode $1, $3, $2
- o "SUPER", -> new CallNode 'super', [new SplatNode(new LiteralNode('arguments'))]
- o "SUPER Arguments", -> new CallNode 'super', $2
- ] |
An optional existence check on a function. | OptFuncExist: [
- o "", -> no
- o "FUNC_EXIST", -> yes
- ] |
The list of arguments to a function call. | Arguments: [
- o "CALL_START CALL_END", -> []
- o "CALL_START ArgList OptComma CALL_END", -> $2
- ] |
A reference to the this current object. | This: [
- o "THIS", -> new ValueNode new LiteralNode 'this'
- o "@", -> new ValueNode new LiteralNode 'this'
- ]
-
- RangeDots: [
- o ". .", -> 'inclusive'
- o ". . .", -> 'exclusive'
- ] |
A reference to a property on this. | ThisProperty: [
- o "@ Identifier", -> new ValueNode new LiteralNode('this'), [new AccessorNode($2)]
- ] |
The CoffeeScript range literal. | Range: [
- o "[ Expression RangeDots Expression ]", -> new RangeNode $2, $4, $3
- ] |
The slice literal. | Slice: [
- o "INDEX_START Expression RangeDots Expression INDEX_END", -> new RangeNode $2, $4, $3
- o "INDEX_START Expression RangeDots INDEX_END", -> new RangeNode $2, null, $3
- o "INDEX_START RangeDots Expression INDEX_END", -> new RangeNode null, $3, $2
- ] |
The array literal. | Array: [
- o "[ ]", -> new ArrayNode []
- o "[ ArgList OptComma ]", -> new ArrayNode $2
- ] |
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: [
- 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 Expressions or Splats. | Arg: [
- o "Expression"
- o "Splat"
- ] |
Just simple, comma-separated, required arguments (no fancy syntax). We need -this to be separate from the ArgList for use in Switch blocks, where -having the newlines wouldn't make sense. | SimpleArgs: [
- o "Expression"
- o "SimpleArgs , Expression", ->
- if $1 instanceof Array then $1.concat([$3]) else [$1].concat([$3])
- ] |
The variants of try/catch/finally exception handling blocks. | Try: [
- o "TRY Block Catch", -> new TryNode $2, $3[0], $3[1]
- o "TRY Block FINALLY Block", -> new TryNode $2, null, null, $4
- o "TRY Block Catch FINALLY Block", -> new TryNode $2, $3[0], $3[1], $5
- ] |
A catch clause names its error and runs a block of code. | Catch: [
- o "CATCH Identifier Block", -> [$2, $3]
- ] |
Throw an exception object. | Throw: [
- o "THROW Expression", -> new ThrowNode $2
- ] |
Parenthetical expressions. Note that the Parenthetical is a Value, -not an Expression, so if you need to use an expression in a place -where only values are accepted, wrapping it in parentheses will always do -the trick. | Parenthetical: [
- o "( Line )", -> new ParentheticalNode $2
- o "( )", -> new ParentheticalNode new LiteralNode ''
- ] |
The condition portion of a while loop. | WhileSource: [
- o "WHILE Expression", -> new WhileNode $2
- o "WHILE Expression WHEN Expression", -> new WhileNode $2, guard: $4
- o "UNTIL Expression", -> new WhileNode $2, invert: true
- o "UNTIL Expression WHEN Expression", -> new WhileNode $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: [
- o "WhileSource Block", -> $1.addBody $2
- o "Statement WhileSource", -> $2.addBody Expressions.wrap [$1]
- o "Expression WhileSource", -> $2.addBody Expressions.wrap [$1]
- o "Loop", -> $1
- ]
-
- Loop: [
- o "LOOP Block", -> new WhileNode(new LiteralNode 'true').addBody $2
- o "LOOP Expression", -> new WhileNode(new LiteralNode 'true').addBody Expressions.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: [
- o "Statement ForBody", -> new ForNode $1, $2, $2.vars[0], $2.vars[1]
- o "Expression ForBody", -> new ForNode $1, $2, $2.vars[0], $2.vars[1]
- o "ForBody Block", -> new ForNode $2, $1, $1.vars[0], $1.vars[1]
- ]
-
- ForBody: [
- o "FOR Range", -> source: new ValueNode($2), vars: []
- o "ForStart ForSource", -> $2.raw = $1.raw; $2.vars = $1; $2
- ]
-
- ForStart: [
- o "FOR ForVariables", -> $2
- o "FOR ALL ForVariables", -> $3.raw = true; $3
- ] |
An array of all accepted values for a variable inside the loop. This -enables support for pattern matching. | ForValue: [
- o "Identifier"
- o "Array", -> new ValueNode $1
- o "Object", -> new ValueNode $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: [
- o "ForValue", -> [$1]
- o "ForValue , ForValue", -> [$1, $3]
- ] |
The source of a comprehension is an array or object with an optional guard -clause. If it's an array comprehension, you can also choose to step through -in fixed-size increments. | ForSource: [
- o "IN Expression", -> source: $2
- o "OF Expression", -> source: $2, object: true
- o "IN Expression WHEN Expression", -> source: $2, guard: $4
- o "OF Expression WHEN Expression", -> source: $2, guard: $4, object: true
- o "IN Expression BY Expression", -> source: $2, step: $4
- o "IN Expression WHEN Expression BY Expression", -> source: $2, guard: $4, step: $6
- o "IN Expression BY Expression WHEN Expression", -> source: $2, step: $4, guard: $6
- ]
-
- Switch: [
- o "SWITCH Expression INDENT Whens OUTDENT", -> new SwitchNode $2, $4
- o "SWITCH Expression INDENT Whens ELSE Block OUTDENT", -> new SwitchNode $2, $4, $6
- o "SWITCH INDENT Whens OUTDENT", -> new SwitchNode null, $3
- o "SWITCH INDENT Whens ELSE Block OUTDENT", -> new SwitchNode null, $3, $5
- ]
-
- Whens: [
- o "When"
- o "Whens When", -> $1.concat $2
- ] |
An individual When clause, with action. | When: [
- o "LEADING_WHEN SimpleArgs Block", -> [[$2, $3]]
- o "LEADING_WHEN SimpleArgs Block TERMINATOR", -> [[$2, $3]]
- ] |
The most basic form of if is a condition and an action. The following -if-related rules are broken up along these lines in order to avoid -ambiguity. | IfBlock: [
- o "IF Expression Block", -> new IfNode $2, $3
- o "UNLESS Expression Block", -> new IfNode $2, $3, invert: true
- o "IfBlock ELSE IF Expression Block", -> $1.addElse (new IfNode($4, $5)).forceStatement()
- o "IfBlock ELSE Block", -> $1.addElse $3
- ] |
The full complement of if expressions, including postfix one-liner -if and unless. | If: [
- o "IfBlock"
- o "Statement POST_IF Expression", -> new IfNode $3, Expressions.wrap([$1]), statement: true
- o "Expression POST_IF Expression", -> new IfNode $3, Expressions.wrap([$1]), statement: true
- o "Statement POST_UNLESS Expression", -> new IfNode $3, Expressions.wrap([$1]), statement: true, invert: true
- o "Expression POST_UNLESS Expression", -> new IfNode $3, Expressions.wrap([$1]), statement: true, invert: true
- ] |
Arithmetic and logical operators, working on one or more operands. -Here they are grouped by order of precedence. The actual precedence rules -are defined at the bottom of the page. It would be shorter if we could -combine most of these rules into a single generic Operand OpSymbol Operand --type rule, but in order to make the precedence binding possible, separate -rules are necessary. | Operation: [
- o "UNARY Expression", -> new OpNode $1, $2
- o("- Expression", (-> new OpNode('-', $2)), {prec: 'UNARY'})
- o("+ Expression", (-> new OpNode('+', $2)), {prec: 'UNARY'})
-
- o "-- Expression", -> new OpNode '--', $2
- o "++ Expression", -> new OpNode '++', $2
- o "Expression --", -> new OpNode '--', $1, null, true
- o "Expression ++", -> new OpNode '++', $1, null, true
-
- o "Expression ? Expression", -> new OpNode '?', $1, $3
- o "Expression + Expression", -> new OpNode '+', $1, $3
- o "Expression - Expression", -> new OpNode '-', $1, $3
- o "Expression == Expression", -> new OpNode '==', $1, $3
- o "Expression != Expression", -> new OpNode '!=', $1, $3
-
- o "Expression MATH Expression", -> new OpNode $2, $1, $3
- o "Expression SHIFT Expression", -> new OpNode $2, $1, $3
- o "Expression COMPARE Expression", -> new OpNode $2, $1, $3
- o "Expression LOGIC Expression", -> new OpNode $2, $1, $3
- o "Value COMPOUND_ASSIGN Expression", -> new OpNode $2, $1, $3
- o "Value COMPOUND_ASSIGN INDENT Expression OUTDENT", -> new OpNode $2, $1, $4
-
- o "Expression IN Expression", -> new InNode $1, $3
- o "Expression OF Expression", -> new OpNode 'in', $1, $3
- o "Expression INSTANCEOF Expression", -> new OpNode 'instanceof', $1, $3
- o "Expression UNARY IN Expression", -> new OpNode $2, new InNode $1, $4
- o "Expression UNARY OF Expression", -> new OpNode $2, new ParentheticalNode new OpNode 'in', $1, $4
- o "Expression UNARY INSTANCEOF Expression", -> new OpNode $2, new ParentheticalNode new OpNode 'instanceof', $1, $4
- ] |
Precedence | |
Operators at the top of this list have higher precedence than the ones lower
-down. Following these rules is what makes
-
-And not: - - | operators = [
- ["right", '?', 'NEW']
- ["left", 'CALL_START', 'CALL_END']
- ["nonassoc", '++', '--']
- ["right", 'UNARY']
- ["left", 'MATH']
- ["left", '+', '-']
- ["left", 'SHIFT']
- ["left", 'COMPARE']
- ["left", 'INSTANCEOF']
- ["left", '==', '!=']
- ["left", 'LOGIC']
- ["right", 'COMPOUND_ASSIGN']
- ["left", '.']
- ["nonassoc", 'INDENT', 'OUTDENT']
- ["right", 'WHEN', 'LEADING_WHEN', 'IN', 'OF', 'BY', 'THROW']
- ["right", 'IF', 'UNLESS', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS', 'EXTENDS']
- ["right", '=', ':', 'RETURN']
- ["right", '->', '=>', 'UNLESS', 'POST_IF', 'POST_UNLESS']
-] |
Wrapping Up | |
Finally, now what we have our grammar and our operators, we can create -our Jison.Parser. We do this by processing all of our rules, recording all -terminals (every symbol which does not appear as the name of a rule above) -as "tokens". | tokens = []
-for name, alternatives of grammar
- grammar[name] = for alt in alternatives
- for token in alt[0].split ' '
- tokens.push token unless grammar[token]
- alt[1] = "return #{alt[1]}" if name is 'Root'
- alt |
Initialize the Parser with our list of terminal tokens, our grammar -rules, and the name of the root. Reverse the operators because Jison orders -precedence from low to high, and we have it high to low -(as in Yacc). | exports.parser = new Parser
- tokens: tokens.join ' '
- bnf: grammar
- operators: operators.reverse()
- startSymbol: 'Root'
-
- |
helpers.coffee | |
---|---|
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. | helpers = exports.helpers = {} |
Cross-browser indexOf, so that IE can join the party. | helpers.indexOf = indexOf = (array, item, from) ->
- return array.indexOf item, from if array.indexOf
- for other, index in array
- if other is item and (not from or (from <= index))
- return index
- -1 |
Does a list include a value? | helpers.include = include = (list, value) ->
- indexOf(list, value) >= 0 |
Peek at the beginning of a given string to see if it matches a sequence. | helpers.starts = starts = (string, literal, start) ->
- string.substring(start, (start or 0) + literal.length) is literal |
Peek at the end of a given string to see if it matches a sequence. | helpers.ends = ends = (string, literal, back) ->
- start = string.length - literal.length - (back ? 0)
- string.substring(start, start + literal.length) is literal |
Trim out all falsy values from an array. | helpers.compact = compact = (array) -> item for item in array when item |
Count the number of occurences of a character in a string. | helpers.count = count = (string, letter) ->
- num = 0
- pos = indexOf string, letter
- while pos isnt -1
- num += 1
- pos = indexOf string, letter, pos + 1
- num |
Merge objects, returning a fresh copy with attributes from both sides.
-Used every time | helpers.merge = merge = (options, overrides) ->
- fresh = {}
- (fresh[key] = val) for all key, val of options
- (fresh[key] = val) for all key, val of overrides if overrides
- fresh |
Extend a source object with the properties of another object (shallow copy).
-We use this to simulate Node's deprecated | helpers.extend = extend = (object, properties) ->
- (object[key] = val) for all key, val of properties |
Return a completely flattened version of an array. Handy for getting a
-list of | helpers.flatten = flatten = (array) ->
- memo = []
- for item in array
- if item instanceof Array then memo = memo.concat(item) else memo.push(item)
- memo |
Delete a key from an object, returning the value. Useful when a node is -looking for a particular method in an options hash. | helpers.del = del = (obj, key) ->
- val = obj[key]
- delete obj[key]
- val
-
- |
index.coffee | |
---|---|
Loader for CoffeeScript as a Node.js library. | (exports[key] = val) for key, val of require './coffee-script'
-
- |
lexer.coffee | |
---|---|
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: - -
-
-Which is a format that can be fed directly into Jison. | {Rewriter} = require './rewriter' |
Import the helpers we need. | {include, count, starts, compact} = require('./helpers').helpers |
The Lexer Class | |
The Lexer class reads a stream of CoffeeScript and divvys 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 |
tokenize is the Lexer's main method. Scan by attempting to match tokens -one at a time, using a regular expression anchored at the start of the -remaining code, or a custom recursive token-matching method -(for interpolations). When the next token has been recorded, we move forward -within the code past the token, and begin again. - -Each tokenizing method is responsible for incrementing Before returning the token stream, run it through the Rewriter -unless explicitly asked not to. | tokenize: (code, options) ->
- code = code.replace /(\r|\s+$)/g, ''
- o = options or {}
- @code = code # The remainder of the source code.
- @i = 0 # Current character position we're parsing.
- @line = o.line or 0 # The current line.
- @indent = 0 # The current indentation level.
- @indebt = 0 # The over-indentation at the current level.
- @outdebt = 0 # The under-outdentation at the current level.
- @indents = [] # The stack of all current indentation levels.
- @tokens = [] # Stream of parsed tokens in the form ['TYPE', value, line]
- while @i < @code.length
- @chunk = @code[@i..]
- @extractNextToken()
- @closeIndentation()
- return @tokens if o.rewrite is off
- (new Rewriter).rewrite @tokens |
At every position, run through this list of attempted matches,
-short-circuiting if any of them succeed. Their order determines precedence:
- | extractNextToken: ->
- return if @identifierToken()
- return if @commentToken()
- return if @whitespaceToken()
- return if @lineToken()
- return if @heredocToken()
- return if @stringToken()
- return if @numberToken()
- return if @regexToken()
- return if @jsToken()
- return @literalToken() |
Tokenizers | |
Matches identifying literals: variables, keywords, method names, etc.
-Check to ensure that JavaScript reserved words aren't being used as
-identifiers. Because CoffeeScript reserves a handful of keywords that are
-allowed in JavaScript, we're careful not to tag them as keywords when
-referenced as property names here, so you can still do | identifierToken: ->
- return false unless id = @match IDENTIFIER, 1
- @i += id.length
- forcedIdentifier = @tagAccessor() or @match ASSIGNED, 1
- tag = 'IDENTIFIER'
- tag = id.toUpperCase() if include(JS_KEYWORDS, id) or (not forcedIdentifier and include(COFFEE_KEYWORDS, id))
- tag = 'LEADING_WHEN' if tag is 'WHEN' and include LINE_BREAK, @tag()
- tag = 'ALL' if id is 'all' and @tag() is 'FOR'
- tag = 'UNARY' if include UNARY, tag
- if include(JS_FORBIDDEN, id)
- if forcedIdentifier
- tag = 'STRING'
- id = "\"#{id}\""
- if forcedIdentifier is 'accessor'
- close_index = true
- @tokens.pop() if @tag() isnt '@'
- @token 'INDEX_START', '['
- else if include(RESERVED, id)
- @identifierError id
- unless forcedIdentifier
- tag = id = CONVERSIONS[id] if include COFFEE_ALIASES, id
- tag = 'LOGIC' if include LOGIC, id
- tag = 'UNARY' if id is '!'
- @token tag, id
- @token ']', ']' if close_index
- true |
Matches numbers, including decimals, hex, and exponential notation. -Be careful not to interfere with ranges-in-progress. | numberToken: ->
- return false unless number = @match NUMBER, 1
- return false if @tag() is '.' and starts number, '.'
- @i += number.length
- @token 'NUMBER', number
- true |
Matches strings, including multi-line strings. Ensures that quotation marks -are balanced within the string's contents, and within nested interpolations. | stringToken: ->
- return false unless starts(@chunk, '"') or starts(@chunk, "'")
- return false unless string =
- @balancedToken(['"', '"'], ['#{', '}']) or
- @balancedToken ["'", "'"]
- @interpolateString string.replace /\n/g, '\\\n'
- @line += count string, "\n"
- @i += string.length
- true |
Matches heredocs, adjusting indentation to the correct level, as heredocs -preserve whitespace, but ignore indentation to the left. | heredocToken: ->
- return false unless match = @chunk.match HEREDOC
- quote = match[1].substr 0, 1
- doc = @sanitizeHeredoc match[2] or match[4] or '', {quote}
- @interpolateString quote + doc + quote, heredoc: yes
- @line += count match[1], "\n"
- @i += match[1].length
- true |
Matches and consumes comments. | commentToken: ->
- return false unless match = @chunk.match(COMMENT)
- @line += count match[1], "\n"
- @i += match[1].length
- if match[2]
- @token 'HERECOMMENT', @sanitizeHeredoc match[2],
- herecomment: true, indent: Array(@indent + 1).join(' ')
- @token 'TERMINATOR', '\n'
- true |
Matches JavaScript interpolated directly into the source via backticks. | jsToken: ->
- return false unless starts @chunk, '`'
- return false unless script = @balancedToken ['`', '`']
- @token 'JS', script.replace JS_CLEANER, ''
- @i += script.length
- true |
Matches regular expression literals. Lexing regular expressions is difficult
-to distinguish from division, so we borrow some basic heuristics from
-JavaScript and Ruby, borrow slash balancing from | regexToken: ->
- return false unless first = @chunk.match REGEX_START
- return false if first[1] is ' ' and @tag() not in ['CALL_START', '=']
- return false if include NOT_REGEX, @tag()
- return false unless regex = @balancedToken ['/', '/']
- return false unless end = @chunk.substr(regex.length).match REGEX_END
- regex += flags = end[2] if end[2]
- if regex.match REGEX_INTERPOLATION
- str = regex.substring(1).split('/')[0]
- str = str.replace REGEX_ESCAPE, (escaped) -> '\\' + escaped
- @tokens = @tokens.concat [['(', '('], ['NEW', 'new'], ['IDENTIFIER', 'RegExp'], ['CALL_START', '(']]
- @interpolateString "\"#{str}\"", escapeQuotes: yes
- @tokens.splice @tokens.length, 0, [',', ','], ['STRING', "\"#{flags}\""] if flags
- @tokens.splice @tokens.length, 0, [')', ')'], [')', ')']
- else
- @token 'REGEX', regex
- @i += regex.length
- true |
Matches a token in which which the passed delimiter pairs must be correctly -balanced (ie. strings, JS literals). | balancedToken: (delimited...) ->
- @balancedString @chunk, delimited |
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: - -
-
-Keeps track of the level of indentation, because a single outdent token -can close multiple indents, so we need to know how far in we happen to be. | lineToken: ->
- return false unless indent = @match MULTI_DENT, 1
- @line += count indent, "\n"
- @i += indent.length
- prev = @prev(2)
- size = indent.match(LAST_DENTS).reverse()[0].match(LAST_DENT)[1].length
- nextCharacter = @match NEXT_CHARACTER, 1
- noNewlines = nextCharacter is '.' or nextCharacter is ',' or @unfinished()
- if size - @indebt is @indent
- return @suppressNewlines() if noNewlines
- return @newlineToken indent
- else if size > @indent
- if noNewlines
- @indebt = size - @indent
- return @suppressNewlines()
- diff = size - @indent + @outdebt
- @token 'INDENT', diff
- @indents.push diff
- @outdebt = @indebt = 0
- else
- @indebt = 0
- @outdentToken @indent - size, noNewlines
- @indent = size
- true |
Record an outdent token or multiple tokens, if we happen to be moving back -inwards past several recorded indents. | outdentToken: (moveOut, noNewlines, close) ->
- while moveOut > 0
- len = @indents.length - 1
- if @indents[len] is undefined
- moveOut = 0
- else if @indents[len] is @outdebt
- moveOut -= @outdebt
- @outdebt = 0
- else if @indents[len] < @outdebt
- @outdebt -= @indents[len]
- moveOut -= @indents[len]
- else
- dent = @indents.pop()
- dent -= @outdebt
- moveOut -= dent
- @outdebt = 0
- @token 'OUTDENT', dent
- @outdebt -= moveOut if dent
- @token 'TERMINATOR', "\n" unless @tag() is 'TERMINATOR' or noNewlines
- true |
Matches and consumes non-meaningful whitespace. Tag the previous token -as being "spaced", because there are some cases where it makes a difference. | whitespaceToken: ->
- return false unless space = @match WHITESPACE, 1
- prev = @prev()
- prev.spaced = true if prev
- @i += space.length
- true |
Generate a newline token. Consecutive newlines get merged together. | newlineToken: (newlines) ->
- @token 'TERMINATOR', "\n" unless @tag() is 'TERMINATOR'
- true |
Use a | suppressNewlines: ->
- @tokens.pop() if @value() is "\\"
- true |
We treat all other single characters as a token. Eg.: | literalToken: ->
- match = @chunk.match OPERATOR
- value = match and match[1]
- space = match and match[2]
- @tagParameters() if value and value.match CODE
- value or= @chunk.substr 0, 1
- @i += value.length
- spaced = (prev = @prev()) and prev.spaced
- tag = value
- if value is '='
- @assignmentError() if include JS_FORBIDDEN, @value()
- if @value() in ['or', 'and']
- @tokens.splice(@tokens.length - 1, 1, ['COMPOUND_ASSIGN', CONVERSIONS[@value()] + '=', prev[2]])
- return true
- if value is ';' then tag = 'TERMINATOR'
- else if include(LOGIC, value) then tag = 'LOGIC'
- else if include(MATH, value) then tag = 'MATH'
- else if include(COMPARE, value) then tag = 'COMPARE'
- else if include(COMPOUND_ASSIGN, value) then tag = 'COMPOUND_ASSIGN'
- else if include(UNARY, value) then tag = 'UNARY'
- else if include(SHIFT, value) then tag = 'SHIFT'
- else if include(CALLABLE, @tag()) and not spaced
- if value is '('
- prev[0] = 'FUNC_EXIST' if prev[0] is '?'
- tag = 'CALL_START'
- else if value is '['
- tag = 'INDEX_START'
- @tag 1, 'INDEX_SOAK' if @tag() is '?'
- @tag 1, 'INDEX_PROTO' if @tag() is '::'
- @token tag, value
- true |
Token Manipulators | |
As we consume a new | tagAccessor: ->
- return false if (not prev = @prev()) or (prev and prev.spaced)
- accessor = if prev[1] is '::'
- @tag 1, 'PROTOTYPE_ACCESS'
- else if prev[1] is '.' and not (@value(2) is '.')
- if @tag(2) is '?'
- @tag(1, 'SOAK_ACCESS')
- @tokens.splice(-2, 1)
- else
- @tag 1, 'PROPERTY_ACCESS'
- else
- prev[0] is '@'
- if accessor then 'accessor' else false |
Sanitize a heredoc or herecomment by escaping internal double quotes and -erasing all external indentation on the left-hand side. | sanitizeHeredoc: (doc, options) ->
- indent = options.indent
- return doc if options.herecomment and not include doc, '\n'
- unless options.herecomment
- while (match = HEREDOC_INDENT.exec(doc)) isnt null
- attempt = if match[2]? then match[2] else match[3]
- indent = attempt if not indent? or attempt.length < indent.length
- indent or= ''
- doc = doc.replace(new RegExp("^" + indent, 'gm'), '')
- return doc if options.herecomment
- doc = doc.replace(/^\n/, '')
- doc.replace(MULTILINER, "\\n")
- .replace(new RegExp(options.quote, 'g'), "\\#{options.quote}") |
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: ->
- return if @tag() isnt ')'
- i = 0
- loop
- i += 1
- tok = @prev i
- return if not tok
- switch tok[0]
- when 'IDENTIFIER' then tok[0] = 'PARAM'
- when ')' then tok[0] = 'PARAM_END'
- when '(', 'CALL_START' then return tok[0] = 'PARAM_START'
- true |
Close up all remaining open blocks at the end of the file. | closeIndentation: ->
- @outdentToken @indent |
The error for when you try to use a forbidden word in JavaScript as -an identifier. | identifierError: (word) ->
- throw new Error "SyntaxError: Reserved word \"#{word}\" on line #{@line + 1}" |
The error for when you try to assign to a reserved word in JavaScript, -like "function" or "default". | assignmentError: ->
- throw new Error "SyntaxError: Reserved word \"#{@value()}\" on line #{@line + 1} can't be assigned" |
Matches a balanced group such as a single or double-quoted string. Pass in -a series of delimiters, all of which must be nested correctly within the -contents of the string. This method allows us to have strings within -interpolations within strings, ad infinitum. | balancedString: (str, delimited, options) ->
- options or= {}
- slash = delimited[0][0] is '/'
- levels = []
- i = 0
- while i < str.length
- if levels.length and starts str, '\\', i
- i += 1
- else
- for pair in delimited
- [open, close] = pair
- if levels.length and starts(str, close, i) and levels[levels.length - 1] is pair
- levels.pop()
- i += close.length - 1
- i += 1 unless levels.length
- break
- else if starts str, open, i
- levels.push(pair)
- i += open.length - 1
- break
- break if not levels.length or slash and starts str, '\n', i
- i += 1
- if levels.length
- return false if slash
- throw new Error "SyntaxError: Unterminated #{levels.pop()[0]} starting on line #{@line + 1}"
- if not i then false else str.substring(0, i) |
Expand variables and expressions inside double-quoted strings using -ECMA Harmony's interpolation syntax -for substitution of bare variables as well as arbitrary expressions. - -
-
-If it encounters an interpolation, this method will recursively create a -new Lexer, tokenize the interpolated contents, and merge them into the -token stream. | interpolateString: (str, options) ->
- options or= {}
- if str.length < 3 or not starts str, '"'
- @token 'STRING', str
- else
- lexer = new Lexer
- tokens = []
- quote = str.substring 0, 1
- [i, pi] = [1, 1]
- while i < str.length - 1
- if starts str, '\\', i
- i += 1
- else if expr = @balancedString(str.substring(i), [['#{', '}']])
- tokens.push ['STRING', quote + str.substring(pi, i) + quote] if pi < i
- inner = expr.substring(2, expr.length - 1)
- if inner.length
- inner = inner.replace new RegExp('\\\\' + quote, 'g'), quote if options.heredoc
- nested = lexer.tokenize "(#{inner})", line: @line
- (tok[0] = ')') for tok, idx in nested when tok[0] is 'CALL_END'
- nested.pop()
- tokens.push ['TOKENS', nested]
- else
- tokens.push ['STRING', quote + quote]
- i += expr.length - 1
- pi = i + 1
- i += 1
- tokens.push ['STRING', quote + str.substring(pi, i) + quote] if pi < i and pi < str.length - 1
- tokens.unshift ['STRING', '""'] unless tokens[0][0] is 'STRING'
- interpolated = tokens.length > 1
- @token '(', '(' if interpolated
- for token, i in tokens
- [tag, value] = token
- if tag is 'TOKENS'
- @tokens = @tokens.concat value
- else if tag is 'STRING' and options.escapeQuotes
- escaped = value.substring(1, value.length - 1).replace(/"/g, '\\"')
- @token tag, "\"#{escaped}\""
- else
- @token tag, value
- @token '+', '+' if i < tokens.length - 1
- @token ')', ')' if interpolated
- tokens |
Helpers | |
Add a token to the results, taking note of the line number. | token: (tag, value) ->
- @tokens.push [tag, value, @line] |
Peek at a tag in the current token stream. | tag: (index, newTag) ->
- return unless tok = @prev index
- return tok[0] = newTag if newTag?
- tok[0] |
Peek at a value in the current token stream. | value: (index, val) ->
- return unless tok = @prev index
- return tok[1] = val if val?
- tok[1] |
Peek at a previous token, entire. | prev: (index) ->
- @tokens[@tokens.length - (index or 1)] |
Attempt to match a string against the current chunk, returning the indexed
-match if successful, and | match: (regex, index) ->
- return false unless m = @chunk.match regex
- if m then m[index] else false |
Are we in the midst of an unfinished expression? | unfinished: ->
- prev = @prev(2)
- @value() and @value().match and @value().match(NO_NEWLINE) and
- prev and (prev[0] isnt '.') and not @value().match(CODE) and
- not @chunk.match ASSIGNED |
Constants | |
Keywords that CoffeeScript shares in common with JavaScript. | JS_KEYWORDS = [
- "if", "else",
- "true", "false",
- "new", "return",
- "try", "catch", "finally", "throw",
- "break", "continue",
- "for", "in", "while",
- "delete", "instanceof", "typeof",
- "switch", "super", "extends", "class",
- "this", "null", "debugger"
-] |
CoffeeScript-only keywords, which we're more relaxed about allowing. They can't -be used standalone, but you can reference them as an attached property. | COFFEE_ALIASES = ["and", "or", "is", "isnt", "not"]
-COFFEE_KEYWORDS = COFFEE_ALIASES.concat [
- "then", "unless", "until", "loop",
- "yes", "no", "on", "off",
- "of", "by", "where", "when"
-] |
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 = [
- "case", "default", "do", "function", "var", "void", "with",
- "const", "let", "enum", "export", "import", "native",
- "__hasProp", "__extends", "__slice"
-] |
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 |
Token matching regexes. | IDENTIFIER = /^([a-zA-Z\$_](\w|\$)*)/
-NUMBER = /^(((\b0(x|X)[0-9a-fA-F]+)|((\b[0-9]+(\.[0-9]+)?|\.[0-9]+)(e[+\-]?[0-9]+)?)))\b/i
-HEREDOC = /^("{6}|'{6}|"{3}([\s\S]*?)\n?([ \t]*)"{3}|'{3}([\s\S]*?)\n?([ \t]*)'{3})/
-OPERATOR = /^(-[\-=>]?|\+[+=]?|[*&|\/%=<>^:!?]+)([ \t]*)/
-WHITESPACE = /^([ \t]+)/
-COMMENT = /^(###([^#][\s\S]*?)(###[ \t]*\n|(###)?$)|(\s*#(?!##[^#])[^\n]*)+)/
-CODE = /^((-|=)>)/
-MULTI_DENT = /^((\n([ \t]*))+)(\.)?/
-LAST_DENTS = /\n([ \t]*)/g
-LAST_DENT = /\n([ \t]*)/ |
Regex-matching-regexes. | REGEX_START = /^\/([^\/])/
-REGEX_INTERPOLATION = /([^\\]#\{.*[^\\]\})/
-REGEX_END = /^(([imgy]{1,4})\b|\W|$)/
-REGEX_ESCAPE = /\\[^\$]/g |
Token cleaning regexes. | JS_CLEANER = /(^`|`$)/g
-MULTILINER = /\n/g
-NO_NEWLINE = /^([+\*&|\/\-%=<>!.\\][<>=&|]*|and|or|is|isnt|not|delete|typeof|instanceof)$/
-HEREDOC_INDENT = /(\n+([ \t]*)|^([ \t]+))/g
-ASSIGNED = /^\s*(([a-zA-Z\$_@]\w*|["'][^\r\n]+?["']|\d+)[ \t]*?[:=][^:=])/
-NEXT_CHARACTER = /^\s*(\S)/ |
Compound assignment tokens. | COMPOUND_ASSIGN = ['-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?=', '<<=', '>>=', '>>>=', '&=', '^=', '|='] |
Unary tokens. | UNARY = ['UMINUS', 'UPLUS', '!', '!!', '~', 'TYPEOF', 'DELETE'] |
Logical tokens. | LOGIC = ['&', '|', '^', '&&', '||'] |
Bit-shifting tokens. | SHIFT = ['<<', '>>', '>>>'] |
Comparison tokens. | COMPARE = ['<=', '<', '>', '>='] |
Mathmatical tokens. | MATH = ['*', '/', '%'] |
Tokens which a regular expression will never immediately follow, but which -a division operator might. - -See: http://www.mozilla.org/js/language/js20-2002-04/rationale/syntax.html#regular-expressions - -Our list is shorter, due to sans-parentheses method calls. | NOT_REGEX = ['NUMBER', 'REGEX', '++', '--', 'FALSE', 'NULL', 'TRUE', ']'] |
Tokens which could legitimately be invoked or indexed. A opening -parentheses or bracket following these tokens will be recorded as the start -of a function invocation or indexing operation. | CALLABLE = ['IDENTIFIER', 'SUPER', ')', ']', '}', 'STRING', '@', 'THIS', '?', '::'] |
Tokens that, when immediately preceding a | LINE_BREAK = ['INDENT', 'OUTDENT', 'TERMINATOR'] |
Conversions from CoffeeScript operators into JavaScript ones. | CONVERSIONS =
- 'and': '&&'
- 'or': '||'
- 'is': '=='
- 'isnt': '!='
- 'not': '!'
- '===': '=='
-
- |
nodes.coffee | |
---|---|
| {Scope} = require './scope' |
Import the helpers we plan to use. | {compact, flatten, merge, del, include, indexOf, starts, ends} = require('./helpers').helpers |
BaseNode | |
The BaseNode is the abstract base class for all nodes in the syntax tree.
-Each subclass implements the | exports.BaseNode = class BaseNode
-
- constructor: ->
- @tags = {} |
Common logic for determining whether to wrap this node in a closure before -compiling it, or to compile directly. We need to wrap if this node is a -statement, and it's not a pureStatement, and we're not at -the top level of a block (which would be unnecessary), and we haven't -already been asked to return the result (because statements know how to -return results). - -If a Node is topSensitive, that means that it needs to compile differently -depending on whether it's being used as part of a larger expression, or is a -top-level statement within the function body. | compile: (o) ->
- @options = merge o or {}
- @tab = o.indent
- del @options, 'chainRoot' unless this instanceof AccessorNode or this instanceof IndexNode
- top = if @topSensitive() then @options.top else del @options, 'top'
- closure = @isStatement(o) and not @isPureStatement() and not top and
- not @options.asStatement and this not instanceof CommentNode and
- not @containsPureStatement()
- o.scope.startLevel() if not o.keepLevel
- code = if closure then @compileClosure(@options) else @compileNode(@options)
- o.scope.endLevel() if not o.keepLevel
- code |
Statements converted into expressions via closure-wrapping share a scope -object with their parent closure, to preserve the expected lexical scope. | compileClosure: (o) ->
- @tab = o.indent
- o.sharedScope = o.scope
- ClosureNode.wrap(this).compile 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. | compileReference: (o, options) ->
- options or= {}
- pair = if not (@containsType(CallNode) or
- (this instanceof ValueNode and (not (@base instanceof LiteralNode) or @hasProperties())))
- [this, this]
- else if this instanceof ValueNode and options.assignment
- this.cacheIndexes(o)
- else
- reference = literal o.scope.freeVariable 'ref'
- compiled = new AssignNode reference, this
- [compiled, reference]
- return [pair[0].compile(o), pair[1].compile(o)] if options.precompile
- pair |
Convenience method to grab the current indentation level, plus tabbing in. | idt: (tabs) ->
- idt = @tab or ''
- num = (tabs or 0) + 1
- idt += TAB while num -= 1
- idt |
Construct a node that returns the current node's result. -Note that this is overridden for smarter behavior for -many statement nodes (eg IfNode, ForNode)... | makeReturn: ->
- new ReturnNode this |
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: (block) ->
- contains = false
- @traverseChildren false, (node) ->
- if block(node)
- contains = true
- return false
- contains |
Is this node of a certain type, or does it contain the type? | containsType: (type) ->
- this instanceof type or @contains (n) -> n instanceof type |
Convenience for the most common use of contains. Does the node contain -a pure statement? | containsPureStatement: ->
- @isPureStatement() or @contains (n) -> n.isPureStatement and n.isPureStatement() |
Perform an in-order traversal of the AST. Crosses scope boundaries. | traverse: (block) -> @traverseChildren true, block |
| toString: (idt, override) ->
- idt or= ''
- children = (child.toString idt + TAB for child in @collectChildren()).join('')
- '\n' + idt + (override or @class) + children
-
- eachChild: (func) ->
- return unless @children
- for attr in @children when this[attr]
- for child in flatten [this[attr]]
- return if func(child) is false
-
- collectChildren: ->
- nodes = []
- @eachChild (node) -> nodes.push node
- nodes
-
- traverseChildren: (crossScope, func) ->
- @eachChild (child) ->
- func.apply(this, arguments)
- child.traverseChildren(crossScope, func) if child instanceof BaseNode |
Default implementations of the common node properties and methods. Nodes -will override these with custom logic, if needed. | class: 'BaseNode'
- children: []
-
- unwrap : -> this
- isStatement : -> no
- isPureStatement : -> no
- topSensitive : -> no |
Expressions | |
The expressions body is the list of expressions that forms the body of an
-indented block of code -- the implementation of a function, a clause in an
- | exports.Expressions = class Expressions extends BaseNode
-
- class: 'Expressions'
- children: ['expressions']
- isStatement: -> yes
-
- constructor: (nodes) ->
- super()
- @expressions = compact flatten nodes or [] |
Tack an expression on to the end of this expression list. | push: (node) ->
- @expressions.push(node)
- this |
Add an expression at the beginning of this expression list. | unshift: (node) ->
- @expressions.unshift(node)
- this |
If this Expressions consists of just a single node, unwrap it by pulling -it back out. | unwrap: ->
- if @expressions.length is 1 then @expressions[0] else this |
Is this an empty block of code? | empty: ->
- @expressions.length is 0 |
An Expressions node does not return its entire body, rather it -ensures that the final expression is returned. | makeReturn: ->
- idx = @expressions.length - 1
- last = @expressions[idx]
- last = @expressions[idx -= 1] if last instanceof CommentNode
- return this if not last or last instanceof ReturnNode
- @expressions[idx] = last.makeReturn()
- this |
An Expressions is the only node that can serve as the root. | compile: (o) ->
- o or= {}
- if o.scope then super(o) else @compileRoot(o)
-
- compileNode: (o) ->
- (@compileExpression(node, merge(o)) for node in @expressions).join("\n") |
If we happen to be the top-level Expressions, wrap everything in -a safety closure, unless requested not to. -It would be better not to generate them in the first place, but for now, -clean up obvious double-parentheses. | compileRoot: (o) ->
- o.indent = @tab = if o.noWrap then '' else TAB
- o.scope = new Scope(null, this, null)
- code = @compileWithDeclarations(o)
- code = code.replace(TRAILING_WHITESPACE, '')
- if o.noWrap then code else "(function() {\n#{code}\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) ->
- code = @compileNode(o)
- code = "#{@tab}var #{o.scope.compiledAssignments()};\n#{code}" if o.scope.hasAssignments(this)
- code = "#{@tab}var #{o.scope.compiledDeclarations()};\n#{code}" if not o.globals and o.scope.hasDeclarations(this)
- code |
Compiles a single expression within the expressions 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. | compileExpression: (node, o) ->
- @tab = o.indent
- compiledNode = node.compile merge o, top: true
- if node.isStatement(o) then compiledNode else "#{@idt()}#{compiledNode};" |
Wrap up the given nodes as an Expressions, unless it already happens -to be one. | Expressions.wrap = (nodes) ->
- return nodes[0] if nodes.length is 1 and nodes[0] instanceof Expressions
- new Expressions(nodes) |
LiteralNode | |
Literals are static values that can be passed through directly into
-JavaScript without translation, such as: strings, numbers,
- | exports.LiteralNode = class LiteralNode extends BaseNode
-
- class: 'LiteralNode'
-
- constructor: (@value) ->
- super()
-
- makeReturn: ->
- if @isStatement() then this else super() |
Break and continue must be treated as pure statements -- they lose their -meaning when wrapped in a closure. | isStatement: ->
- @value is 'break' or @value is 'continue' or @value is 'debugger'
- isPureStatement: LiteralNode::isStatement
-
- compileNode: (o) ->
- idt = if @isStatement(o) then @idt() else ''
- end = if @isStatement(o) then ';' else ''
- idt + @value + end
-
- toString: (idt) ->
- '"' + @value + '"' |
ReturnNode | |
A | exports.ReturnNode = class ReturnNode extends BaseNode
-
- class: 'ReturnNode'
- isStatement: -> yes
- isPureStatement: -> yes
- children: ['expression']
-
- constructor: (@expression) ->
- super()
-
- makeReturn: ->
- this
-
- compile: (o) ->
- expr = @expression.makeReturn()
- return expr.compile o unless expr instanceof ReturnNode
- super o
-
- compileNode: (o) ->
- o.asStatement = true if @expression.isStatement(o)
- "#{@tab}return #{@expression.compile(o)};" |
ValueNode | |
A value, variable or literal or parenthesized, indexed or dotted into, -or vanilla. | exports.ValueNode = class ValueNode extends BaseNode
-
- class: 'ValueNode'
- children: ['base', 'properties'] |
A ValueNode has a base and a list of property accesses. | constructor: (@base, @properties) ->
- super()
- @properties or= [] |
Add a property access to the list. | push: (prop) ->
- @properties.push(prop)
- this
-
- hasProperties: ->
- !!@properties.length |
Some boolean checks for the benefit of other nodes. | isArray: ->
- @base instanceof ArrayNode and not @hasProperties()
-
- isObject: ->
- @base instanceof ObjectNode and not @hasProperties()
-
- isSplice: ->
- @hasProperties() and @properties[@properties.length - 1] instanceof SliceNode
-
- makeReturn: ->
- if @hasProperties() then super() else @base.makeReturn() |
The value can be unwrapped as its inner node, if there are no attached -properties. | unwrap: ->
- if @properties.length then this else @base |
Values are considered to be statements if their base is a statement. | isStatement: (o) ->
- @base.isStatement and @base.isStatement(o) and not @hasProperties()
-
- isNumber: ->
- @base instanceof LiteralNode and @base.value.match NUMBER |
If the value node has indexes containing function calls, and the value node -needs to be used twice, in compound assignment ... then we need to cache -the value of the indexes. | cacheIndexes: (o) ->
- copy = new ValueNode @base, @properties[0..]
- if @base instanceof CallNode
- [@base, copy.base] = @base.compileReference o
- for prop, i in copy.properties
- if prop instanceof IndexNode and prop.contains((n) -> n instanceof CallNode)
- [index, indexVar] = prop.index.compileReference o
- this.properties[i] = new IndexNode index
- copy.properties[i] = new IndexNode indexVar
- [this, copy] |
Override compile to unwrap the value when possible. | compile: (o) ->
- if not o.top or @properties.length then super(o) else @base.compile(o) |
We compile a value to JavaScript by compiling and joining each property.
-Things get much more insteresting if the chain of properties has soak
-operators | compileNode: (o) ->
- only = del o, 'onlyFirst'
- op = @tags.operation
- props = if only then @properties[0...@properties.length - 1] else @properties
- o.chainRoot or= this
- for prop in props
- hasSoak = yes if prop.soakNode
- if hasSoak and @containsType CallNode
- [me, copy] = @cacheIndexes o
- @base.parenthetical = yes if @parenthetical and not props.length
- baseline = @base.compile o
- baseline = "(#{baseline})" if @hasProperties() and (@base instanceof ObjectNode or @isNumber())
- complete = @last = baseline
-
- for prop, i in props
- @source = baseline
- if prop.soakNode
- if @base.containsType(CallNode) and i is 0
- temp = o.scope.freeVariable 'ref'
- complete = "(#{ baseline = temp } = (#{complete}))"
- complete = if i is 0
- "(typeof #{complete} === \"undefined\" || #{baseline} === null) ? undefined : "
- else
- "#{complete} == null ? undefined : "
- complete += (baseline += prop.compile(o))
- else
- part = prop.compile(o)
- if hasSoak and prop.containsType CallNode
- baseline += copy.properties[i].compile o
- else
- baseline += part
- complete += part
- @last = part
-
- if op and @wrapped then "(#{complete})" else complete |
CommentNode | |
CoffeeScript passes through block comments as JavaScript block comments -at the same position. | exports.CommentNode = class CommentNode extends BaseNode
-
- class: 'CommentNode'
- isStatement: -> yes
-
- constructor: (@comment) ->
- super()
-
- makeReturn: ->
- this
-
- compileNode: (o) ->
- @tab + '/*' + @comment.replace(/\r?\n/g, '\n' + @tab) + '*/' |
CallNode | |
Node for a function invocation. Takes care of converting | exports.CallNode = class CallNode extends BaseNode
-
- class: 'CallNode'
- children: ['variable', 'args']
-
- constructor: (variable, @args, @exist) ->
- super()
- @isNew = false
- @isSuper = variable is 'super'
- @variable = if @isSuper then null else variable
- @args or= []
- @first = @last = ''
- @compileSplatArguments = (o) ->
- SplatNode.compileSplattedArray.call(this, @args, o) |
Tag this invocation as creating a new instance. | newInstance: ->
- @isNew = true
- this
-
- prefix: ->
- if @isNew then 'new ' else '' |
Grab the reference to the superclass' implementation of the current method. | superReference: (o) ->
- throw new Error "cannot call super outside of a function" unless o.scope.method
- methname = o.scope.method.name
- meth = if o.scope.method.proto
- "#{o.scope.method.proto}.__super__.#{methname}"
- else if methname
- "#{methname}.__super__.constructor"
- else throw new Error "cannot call super on an anonymous function." |
Compile a vanilla function call. | compileNode: (o) ->
- o.chainRoot = this unless o.chainRoot
- op = @tags.operation
- if @exist
- if @variable instanceof ValueNode and @variable.properties[@variable.properties.length - 1] instanceof AccessorNode
- methodAccessor = @variable.properties.pop()
- [first, meth] = @variable.compileReference o
- @first = new ValueNode(first, [methodAccessor]).compile o
- @meth = new ValueNode(meth, [methodAccessor]).compile o
- else
- [@first, @meth] = @variable.compileReference o, precompile: yes
- @first = "(typeof #{@first} === \"function\" ? "
- @last = " : undefined)"
- else if @variable
- @meth = @variable.compile o
- for arg in @args when arg instanceof SplatNode
- code = @compileSplat(o)
- if not code
- args = for arg in @args
- arg.parenthetical = true
- arg.compile o
- code = if @isSuper
- @compileSuper(args.join(', '), o)
- else
- "#{@first}#{@prefix()}#{@meth}(#{ args.join(', ') })#{@last}"
- if op and @variable and @variable.wrapped then "(#{code})" else code |
| compileSuper: (args, o) ->
- "#{@superReference(o)}.call(this#{ if args.length then ', ' else '' }#{args})" |
If you call a function with a splat, it's converted into a JavaScript
- | compileSplat: (o) ->
- meth = @meth or @superReference(o)
- obj = @variable and @variable.source or 'this'
- if obj.match(/\(/)
- temp = o.scope.freeVariable 'ref'
- obj = temp
- meth = "(#{temp} = #{ @variable.source })#{ @variable.last }"
- if @isNew
- mentionsArgs = no
- for arg in @args
- arg.contains (n) -> mentionsArgs or= n instanceof LiteralNode and (n.value is 'arguments')
- utility 'extends'
- a = o.scope.freeVariable 'ctor'
- b = o.scope.freeVariable 'ref'
- c = o.scope.freeVariable 'result'
- """
-
-#DIVIDER
- """
- else
- "#{@first}#{@prefix()}#{meth}.apply(#{obj}, #{ @compileSplatArguments(o) })#{@last}" |
{@first}(function() { -{@idt(1)}var ctor = function(){}; -{@idt(1)}__extends(ctor, #{a} = #{meth}); -{@idt(1)}return typeof (#{c} = #{a}.apply(#{b} = new ctor, #{ @compileSplatArguments(o) })) === "object" ? #{c} : #{b}; -{@tab}}).#{ if mentionsArgs then 'apply(this, arguments)' else 'call(this)'}#{@last} | |
ExtendsNode | exports.ExtendsNode = class ExtendsNode extends BaseNode
-
- class: 'ExtendsNode'
- children: ['child', 'parent']
-
- constructor: (@child, @parent) ->
- super() |
Node to extend an object's prototype with an ancestor object.
-After | compileNode: (o) ->
- ref = new ValueNode literal utility 'extends'
- (new CallNode ref, [@child, @parent]).compile o |
Hooks one constructor into another's prototype chain. | |
AccessorNode | exports.AccessorNode = class AccessorNode extends BaseNode
-
- class: 'AccessorNode'
- children: ['name']
-
- constructor: (@name, tag) ->
- super()
- @prototype = if tag is 'prototype' then '.prototype' else ''
- @soakNode = tag is 'soak'
-
- compileNode: (o) ->
- name = @name.compile o
- o.chainRoot.wrapped or= @soakNode
- namePart = if name.match(IS_STRING) then "[#{name}]" else ".#{name}"
- @prototype + namePart |
A | |
IndexNode | exports.IndexNode = class IndexNode extends BaseNode
-
- class: 'IndexNode'
- children: ['index']
-
- constructor: (@index) ->
- super()
-
- compileNode: (o) ->
- o.chainRoot.wrapped or= @soakNode
- idx = @index.compile o
- prefix = if @proto then '.prototype' else ''
- "#{prefix}[#{idx}]" |
A | |
RangeNode | exports.RangeNode = class RangeNode extends BaseNode
-
- class: 'RangeNode'
- children: ['from', 'to']
-
- constructor: (@from, @to, tag) ->
- super()
- @exclusive = tag is 'exclusive'
- @equals = if @exclusive then '' else '=' |
A range literal. Ranges can be used to extract portions (slices) of arrays, -to specify a range for comprehensions, or as a value, to be expanded into the -corresponding array of integers at runtime. | compileVariables: (o) ->
- o = merge(o, top: true)
- [@from, @fromVar] = @from.compileReference o, precompile: yes
- [@to, @toVar] = @to.compileReference o, precompile: yes
- [@fromNum, @toNum] = [@fromVar.match(SIMPLENUM), @toVar.match(SIMPLENUM)]
- parts = []
- parts.push @from if @from isnt @fromVar
- parts.push @to if @to isnt @toVar
- if parts.length then "#{parts.join('; ')}; " 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. | compileNode: (o) ->
- return @compileArray(o) unless o.index
- return @compileSimple(o) if @fromNum and @toNum
- idx = del o, 'index'
- step = del o, 'step'
- vars = "#{idx} = #{@fromVar}"
- intro = "(#{@fromVar} <= #{@toVar} ? #{idx}"
- compare = "#{intro} <#{@equals} #{@toVar} : #{idx} >#{@equals} #{@toVar})"
- stepPart = if step then step.compile(o) else '1'
- incr = if step then "#{idx} += #{stepPart}" else "#{intro} += #{stepPart} : #{idx} -= #{stepPart})"
- "#{vars}; #{compare}; #{incr}" |
When compiled normally, the range returns the contents of the for loop -needed to iterate over the values in the range. Used by comprehensions. | compileSimple: (o) ->
- [from, to] = [parseInt(@fromNum, 10), parseInt(@toNum, 10)]
- idx = del o, 'index'
- step = del o, 'step'
- step and= "#{idx} += #{step.compile(o)}"
- if from <= to
- "#{idx} = #{from}; #{idx} <#{@equals} #{to}; #{step or "#{idx}++"}"
- else
- "#{idx} = #{from}; #{idx} >#{@equals} #{to}; #{step or "#{idx}--"}" |
Compile a simple range comprehension, with integers. | compileArray: (o) ->
- idt = @idt 1
- vars = @compileVariables merge o, indent: idt
- if @fromNum and @toNum and Math.abs(+@fromNum - +@toNum) <= 20
- range = [+@fromNum..+@toNum]
- range.pop() if @exclusive
- return "[#{ range.join(', ') }]"
- i = o.scope.freeVariable 'i'
- result = o.scope.freeVariable 'result'
- pre = "\n#{idt}#{result} = []; #{vars}"
- if @fromNum and @toNum
- o.index = i
- body = @compileSimple o
- else
- clause = "#{@fromVar} <= #{@toVar} ?"
- body = "var #{i} = #{@fromVar}; #{clause} #{i} <#{@equals} #{@toVar} : #{i} >#{@equals} #{@toVar}; #{clause} #{i} += 1 : #{i} -= 1"
- post = "{ #{result}.push(#{i}); }\n#{idt}return #{result};\n#{o.indent}"
- "(function() {#{pre}\n#{idt}for (#{body})#{post}}).call(this)" |
When used as a value, expand the range into the equivalent array. | |
SliceNode | exports.SliceNode = class SliceNode extends BaseNode
-
- class: 'SliceNode'
- children: ['range']
-
- constructor: (@range) ->
- super()
-
- compileNode: (o) ->
- from = if @range.from then @range.from.compile(o) else '0'
- to = if @range.to then @range.to.compile(o) else ''
- to += if not to or @range.exclusive then '' else ' + 1'
- to = ', ' + to if to
- ".slice(#{from}#{to})" |
An array slice literal. Unlike JavaScript's | |
ObjectNode | exports.ObjectNode = class ObjectNode extends BaseNode
-
- class: 'ObjectNode'
- children: ['properties']
-
- topSensitive: -> true
-
- constructor: (props) ->
- super()
- @objects = @properties = props or []
-
- compileNode: (o) ->
- top = del o, 'top'
- o.indent = @idt 1
- nonComments = prop for prop in @properties when (prop not instanceof CommentNode)
- lastNoncom = nonComments[nonComments.length - 1]
- props = for prop, i in @properties
- join = ",\n"
- join = "\n" if (prop is lastNoncom) or (prop instanceof CommentNode)
- join = '' if i is @properties.length - 1
- indent = if prop instanceof CommentNode then '' else @idt 1
- prop = new AssignNode prop, prop, 'object' unless prop instanceof AssignNode or prop instanceof CommentNode
- indent + prop.compile(o) + join
- props = props.join('')
- obj = '{' + (if props then '\n' + props + '\n' + @idt() else '') + '}'
- if top then "(#{obj})" else obj |
An object literal, nothing fancy. | |
ArrayNode | exports.ArrayNode = class ArrayNode extends BaseNode
-
- class: 'ArrayNode'
- children: ['objects']
-
- constructor: (@objects) ->
- super()
- @objects or= []
- @compileSplatLiteral = (o) ->
- SplatNode.compileSplattedArray.call(this, @objects, o)
-
- compileNode: (o) ->
- o.indent = @idt 1
- objects = []
- for obj, i in @objects
- code = obj.compile(o)
- if obj instanceof SplatNode
- return @compileSplatLiteral o
- else if obj instanceof CommentNode
- objects.push "\n#{code}\n#{o.indent}"
- else if i is @objects.length - 1
- objects.push code
- else
- objects.push "#{code}, "
- objects = objects.join('')
- if indexOf(objects, '\n') >= 0
- "[\n#{@idt(1)}#{objects}\n#{@tab}]"
- else
- "[#{objects}]" |
An array literal. | |
ClassNode | exports.ClassNode = class ClassNode extends BaseNode
-
- class: 'ClassNode'
- children: ['variable', 'parent', 'properties']
- isStatement: -> yes |
The CoffeeScript class definition. | constructor: (@variable, @parent, @properties) ->
- super()
- @properties or= []
- @returns = false
-
- makeReturn: ->
- @returns = true
- this |
Initialize a ClassNode with its name, an optional superclass, and a -list of prototype property assignments. | compileNode: (o) ->
- @variable = literal o.scope.freeVariable 'ctor' if @variable is '__temp__'
- extension = @parent and new ExtendsNode(@variable, @parent)
- props = new Expressions
- o.top = true
- me = null
- className = @variable.compile o
- constScope = null
-
- if @parent
- applied = new ValueNode(@parent, [new AccessorNode(literal('apply'))])
- constructor = new CodeNode([], new Expressions([
- new CallNode(applied, [literal('this'), literal('arguments')])
- ]))
- else
- constructor = new CodeNode
-
- for prop in @properties
- [pvar, func] = [prop.variable, prop.value]
- if pvar and pvar.base.value is 'constructor' and func instanceof CodeNode
- throw new Error "cannot define a constructor as a bound function." if func.bound
- func.name = className
- func.body.push new ReturnNode literal 'this'
- @variable = new ValueNode @variable
- @variable.namespaced = include func.name, '.'
- constructor = func
- continue
- if func instanceof CodeNode and func.bound
- if prop.context is 'this'
- func.context = className
- else
- func.bound = false
- constScope or= new Scope(o.scope, constructor.body, constructor)
- me or= constScope.freeVariable 'this'
- pname = pvar.compile(o)
- constructor.body.push new ReturnNode literal 'this' if constructor.body.empty()
- constructor.body.unshift literal "this.#{pname} = function(){ return #{className}.prototype.#{pname}.apply(#{me}, arguments); }"
- if pvar
- access = if prop.context is 'this' then pvar.base.properties[0] else new AccessorNode(pvar, 'prototype')
- val = new ValueNode(@variable, [access])
- prop = new AssignNode(val, func)
- props.push prop
-
- constructor.body.unshift literal "#{me} = this" if me
- construct = @idt() + (new AssignNode(@variable, constructor)).compile(merge o, {sharedScope: constScope}) + ';'
- props = if !props.empty() then '\n' + props.compile(o) else ''
- extension = if extension then '\n' + @idt() + extension.compile(o) + ';' else ''
- returns = if @returns then '\n' + new ReturnNode(@variable).compile(o) else ''
- construct + extension + props + returns |
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. | |
AssignNode | exports.AssignNode = class AssignNode extends BaseNode |
The AssignNode is used to assign a local variable to value, or to set the -property of an object -- including within object literals. | PROTO_ASSIGN: /^(\S+)\.prototype/
- LEADING_DOT: /^\.(prototype\.)?/
-
- class: 'AssignNode'
- children: ['variable', 'value']
-
- constructor: (@variable, @value, @context) ->
- super()
-
- topSensitive: ->
- true
-
- isValue: ->
- @variable instanceof ValueNode
-
- makeReturn: ->
- if @isStatement()
- return new Expressions [this, new ReturnNode(@variable)]
- else
- super()
-
- isStatement: ->
- @isValue() and (@variable.isArray() or @variable.isObject()) |
Matchers for detecting prototype assignments. | compileNode: (o) ->
- top = del o, 'top'
- return @compilePatternMatch(o) if @isStatement(o)
- return @compileSplice(o) if @isValue() and @variable.isSplice()
- stmt = del o, 'asStatement'
- name = @variable.compile(o)
- last = if @isValue() then @variable.last.replace(@LEADING_DOT, '') else name
- match = name.match(@PROTO_ASSIGN)
- proto = match and match[1]
- if @value instanceof CodeNode
- @value.name = last if last.match(IDENTIFIER)
- @value.proto = proto if proto
- val = @value.compile o
- return "#{name}: #{val}" if @context is 'object'
- o.scope.find name unless @isValue() and (@variable.hasProperties() or @variable.namespaced)
- val = "#{name} = #{val}"
- return "#{@tab}#{val};" if stmt
- if top or @parenthetical then val else "(#{val})" |
Compile an assignment, delegating to | compilePatternMatch: (o) ->
- valVar = o.scope.freeVariable 'ref'
- value = if @value.isStatement(o) then ClosureNode.wrap(@value) else @value
- assigns = ["#{@tab}#{valVar} = #{ value.compile(o) };"]
- o.top = true
- o.asStatement = true
- splat = false
- for obj, i in @variable.base.objects |
Brief implementation of recursive pattern matching, when assigning array or -object literals to a value. Peeks at their properties to assign inner names. -See the ECMAScript Harmony Wiki -for details. | idx = i
- if @variable.isObject()
- if obj instanceof AssignNode |
A regular array pattern-match. | [obj, idx] = [obj.value, obj.variable.base]
- else |
A regular object pattern-match. | idx = obj
- if not (obj instanceof ValueNode or obj instanceof SplatNode)
- throw new Error 'pattern matching must use only identifiers on the left-hand side.'
- isString = idx.value and idx.value.match IS_STRING
- accessClass = if isString or @variable.isArray() then IndexNode else AccessorNode
- if obj instanceof SplatNode and not splat
- val = literal obj.compileValue o, valVar,
- (oindex = indexOf(@variable.base.objects, obj)),
- (olength = @variable.base.objects.length) - oindex - 1
- splat = true
- else
- idx = literal(if splat then "#{valVar}.length - #{olength - idx}" else idx) if typeof idx isnt 'object'
- val = new ValueNode(literal(valVar), [new accessClass(idx)])
- assigns.push(new AssignNode(obj, val).compile(o))
- code = assigns.join("\n")
- code |
A shorthand | compileSplice: (o) ->
- name = @variable.compile merge o, onlyFirst: true
- l = @variable.properties.length
- range = @variable.properties[l - 1].range
- plus = if range.exclusive then '' else ' + 1'
- from = if range.from then range.from.compile(o) else '0'
- to = if range.to then range.to.compile(o) + ' - ' + from + plus else "#{name}.length"
- val = @value.compile(o)
- "#{name}.splice.apply(#{name}, [#{from}, #{to}].concat(#{val}))" |
Compile the assignment from an array splice literal, using JavaScript's
- | |
CodeNode | exports.CodeNode = class CodeNode extends BaseNode
-
- class: 'CodeNode'
- children: ['params', 'body']
-
- constructor: (@params, @body, tag) ->
- super()
- @params or= []
- @body or= new Expressions
- @bound = tag is 'boundfunc'
- @context = 'this' if @bound |
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 CodeNode -has no children -- they're within the inner scope. | compileNode: (o) ->
- sharedScope = del o, 'sharedScope'
- top = del o, 'top'
- o.scope = sharedScope or new Scope(o.scope, @body, this)
- o.top = true
- o.indent = @idt(1)
- empty = @body.expressions.length is 0
- del o, 'noWrap'
- del o, 'globals'
- splat = undefined
- params = []
- for param, i in @params
- if splat
- if param.attach
- param.assign = new AssignNode new ValueNode literal('this'), [new AccessorNode param.value]
- @body.expressions.splice splat.index + 1, 0, param.assign
- splat.trailings.push param
- else
- if param.attach
- {value} = param
- [param, param.splat] = [literal(o.scope.freeVariable 'arg'), param.splat]
- @body.unshift new AssignNode new ValueNode(literal('this'), [new AccessorNode value]), param
- if param.splat
- splat = new SplatNode param.value
- splat.index = i
- splat.trailings = []
- splat.arglength = @params.length
- @body.unshift(splat)
- else
- params.push param
- params = (param.compile(o) for param in params)
- @body.makeReturn() unless empty
- (o.scope.parameter(param)) for param in params
- code = if @body.expressions.length then "\n#{ @body.compileWithDeclarations(o) }\n" else ''
- func = "function(#{ params.join(', ') }) {#{code}#{ code and @tab }}"
- return "#{utility('bind')}(#{func}, #{@context})" if @bound
- if top then "(#{func})" else func
-
- topSensitive: ->
- true |
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 | traverseChildren: (crossScope, func) -> super(crossScope, func) if crossScope
-
- toString: (idt) ->
- idt or= ''
- children = (child.toString(idt + TAB) for child in @collectChildren()).join('')
- '\n' + idt + children |
Short-circuit traverseChildren method to prevent it from crossing scope boundaries -unless crossScope is true | |
ParamNode | exports.ParamNode = class ParamNode extends BaseNode
-
- class: 'ParamNode'
- children: ['name']
-
- constructor: (@name, @attach, @splat) ->
- super()
- @value = literal @name
-
- compileNode: (o) ->
- @value.compile o
-
- toString: (idt) ->
- if @attach then (literal '@' + @name).toString idt else @value.toString idt |
A parameter in a function definition. Beyond a typical Javascript parameter, -these parameters can also attach themselves to the context of the function, -as well as be a splat, gathering up a group of parameters into an array. | |
SplatNode | exports.SplatNode = class SplatNode extends BaseNode
-
- class: 'SplatNode'
- children: ['name']
-
- constructor: (name) ->
- super()
- name = literal(name) unless name.compile
- @name = name
-
- compileNode: (o) ->
- if @index? then @compileParam(o) else @name.compile(o) |
A splat, either as a parameter to a function, an argument to a call, -or as part of a destructuring assignment. | compileParam: (o) ->
- name = @name.compile(o)
- o.scope.find name
- end = ''
- if @trailings.length
- len = o.scope.freeVariable 'len'
- o.scope.assign len, "arguments.length"
- variadic = o.scope.freeVariable 'result'
- o.scope.assign variadic, len + ' >= ' + @arglength
- end = if @trailings.length then ", #{len} - #{@trailings.length}"
- for trailing, idx in @trailings
- if trailing.attach
- assign = trailing.assign
- trailing = literal o.scope.freeVariable 'arg'
- assign.value = trailing
- pos = @trailings.length - idx
- o.scope.assign(trailing.compile(o), "arguments[#{variadic} ? #{len} - #{pos} : #{@index + idx}]")
- "#{name} = #{utility('slice')}.call(arguments, #{@index}#{end})" |
Compiling a parameter splat means recovering the parameters that succeed -the splat in the parameter list, by slicing the arguments object. | compileValue: (o, name, index, trailings) ->
- trail = if trailings then ", #{name}.length - #{trailings}" else ''
- "#{utility 'slice'}.call(#{name}, #{index}#{trail})" |
A compiling a splat as a destructuring assignment means slicing arguments -from the right-hand-side's corresponding array. | @compileSplattedArray: (list, o) ->
- args = []
- for arg, i in list
- code = arg.compile o
- prev = args[last = args.length - 1]
- if arg not instanceof SplatNode
- if prev and starts(prev, '[') and ends(prev, ']')
- args[last] = "#{prev.substr(0, prev.length - 1)}, #{code}]"
- continue
- else if prev and starts(prev, '.concat([') and ends(prev, '])')
- args[last] = "#{prev.substr(0, prev.length - 2)}, #{code}])"
- continue
- else
- code = "[#{code}]"
- args.push(if i is 0 then code else ".concat(#{code})")
- args.join('') |
Utility function that converts arbitrary number of elements, mixed with -splats, to a proper array | |
WhileNode | exports.WhileNode = class WhileNode extends BaseNode
-
- class: 'WhileNode'
- children: ['condition', 'guard', 'body']
- isStatement: -> yes
-
- constructor: (condition, opts) ->
- super()
- if opts and opts.invert
- condition = new ParentheticalNode condition if condition instanceof OpNode
- condition = new OpNode('!', condition)
- @condition = condition
- @guard = opts and opts.guard
-
- addBody: (body) ->
- @body = body
- this
-
- makeReturn: ->
- @returns = true
- this
-
- topSensitive: ->
- true |
A while loop, the only sort of low-level loop exposed by CoffeeScript. From -it, all other loops can be manufactured. Useful in cases where you need more -flexibility or more speed than a comprehension can provide. | compileNode: (o) ->
- top = del(o, 'top') and not @returns
- o.indent = @idt 1
- o.top = true
- @condition.parenthetical = yes
- cond = @condition.compile(o)
- set = ''
- unless top
- rvar = o.scope.freeVariable 'result'
- set = "#{@tab}#{rvar} = [];\n"
- @body = PushNode.wrap(rvar, @body) if @body
- pre = "#{set}#{@tab}while (#{cond})"
- @body = Expressions.wrap([new IfNode(@guard, @body)]) if @guard
- if @returns
- post = '\n' + new ReturnNode(literal(rvar)).compile(merge(o, indent: @idt()))
- else
- post = ''
- "#{pre} {\n#{ @body.compile(o) }\n#{@tab}}#{post}" |
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. | |
OpNode | exports.OpNode = class OpNode extends BaseNode |
Simple Arithmetic and logical operations. Performs some conversion from -CoffeeScript operations into their JavaScript equivalents. | CONVERSIONS:
- '==': '==='
- '!=': '!==' |
The map of conversions from CoffeeScript to JavaScript symbols. | INVERSIONS:
- '!==': '==='
- '===': '!==' |
The map of invertible operators. | CHAINABLE: ['<', '>', '>=', '<=', '===', '!=='] |
The list of operators for which we perform -Python-style comparison chaining. | ASSIGNMENT: ['||=', '&&=', '?='] |
Our assignment operators that have no JavaScript equivalent. | PREFIX_OPERATORS: ['typeof', 'delete']
-
- class: 'OpNode'
- children: ['first', 'second']
-
- constructor: (@operator, @first, @second, flip) ->
- super()
- @operator = @CONVERSIONS[@operator] or @operator
- @flip = !!flip
- if @first instanceof ValueNode and @first.base instanceof ObjectNode
- @first = new ParentheticalNode @first
- @first.tags.operation = yes
- @second.tags.operation = yes if @second
-
- isUnary: ->
- not @second
-
- isInvertible: ->
- (@operator in ['===', '!==']) and
- not (@first instanceof OpNode) and not (@second instanceof OpNode)
-
-
- isMutator: ->
- ends(@operator, '=') and not (@operator in ['===', '!=='])
-
- isChainable: ->
- include(@CHAINABLE, @operator)
-
- invert: ->
- @operator = @INVERSIONS[@operator]
-
- toString: (idt) ->
- super(idt, @class + ' ' + @operator)
-
- compileNode: (o) ->
- return @compileChain(o) if @isChainable() and @first.unwrap() instanceof OpNode and @first.unwrap().isChainable()
- return @compileAssignment(o) if indexOf(@ASSIGNMENT, @operator) >= 0
- return @compileUnary(o) if @isUnary()
- return @compileExistence(o) if @operator is '?'
- @first = new ParentheticalNode(@first) if @first instanceof OpNode and @first.isMutator()
- @second = new ParentheticalNode(@second) if @second instanceof OpNode and @second.isMutator()
- [@first.compile(o), @operator, @second.compile(o)].join ' ' |
Operators must come before their operands with a space. | compileChain: (o) ->
- shared = @first.unwrap().second
- [@first.second, shared] = shared.compileReference(o) if shared.containsType CallNode
- [first, second, shared] = [@first.compile(o), @second.compile(o), shared.compile(o)]
- "(#{first}) && (#{shared} #{@operator} #{second})" |
Mimic Python's chained comparisons when multiple comparison operators are -used sequentially. For example: - - | compileAssignment: (o) ->
- [first, firstVar] = @first.compileReference o, precompile: yes, assignment: yes
- second = @second.compile o
- second = "(#{second})" if @second instanceof OpNode
- o.scope.find(first) if first.match(IDENTIFIER)
- return "#{first} = #{ ExistenceNode.compileTest(o, literal(firstVar))[0] } ? #{firstVar} : #{second}" if @operator is '?='
- "#{first} #{ @operator.substr(0, 2) } (#{firstVar} = #{second})" |
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. | compileExistence: (o) ->
- [test, ref] = ExistenceNode.compileTest(o, @first)
- "#{test} ? #{ref} : #{ @second.compile(o) }" |
If this is an existence operator, we delegate to | compileUnary: (o) ->
- space = if indexOf(@PREFIX_OPERATORS, @operator) >= 0 then ' ' else ''
- parts = [@operator, space, @first.compile(o)]
- parts = parts.reverse() if @flip
- parts.join('') |
Compile a unary OpNode. | exports.InNode = class InNode extends BaseNode
-
- class: 'InNode'
- children: ['object', 'array']
-
- constructor: (@object, @array) ->
- super()
-
- isArray: ->
- @array instanceof ValueNode and @array.isArray()
-
- compileNode: (o) ->
- [@obj1, @obj2] = @object.compileReference o, precompile: yes
- if @isArray() then @compileOrTest(o) else @compileLoopTest(o)
-
- compileOrTest: (o) ->
- tests = for item, i in @array.base.objects
- "#{item.compile(o)} === #{if i then @obj2 else @obj1}"
- "(#{tests.join(' || ')})"
-
- compileLoopTest: (o) ->
- [@arr1, @arr2] = @array.compileReference o, precompile: yes
- [i, l] = [o.scope.freeVariable('i'), o.scope.freeVariable('len')]
- prefix = if @obj1 isnt @obj2 then @obj1 + '; ' else ''
- "(function(){ #{prefix}for (var #{i}=0, #{l}=#{@arr1}.length; #{i}<#{l}; #{i}++) { if (#{@arr2}[#{i}] === #{@obj2}) return true; } return false; }).call(this)" |
InNode | |
TryNode | exports.TryNode = class TryNode extends BaseNode
-
- class: 'TryNode'
- children: ['attempt', 'recovery', 'ensure']
- isStatement: -> yes
-
- constructor: (@attempt, @error, @recovery, @ensure) ->
- super()
-
- makeReturn: ->
- @attempt = @attempt.makeReturn() if @attempt
- @recovery = @recovery.makeReturn() if @recovery
- this |
A classic try/catch/finally block. | compileNode: (o) ->
- o.indent = @idt 1
- o.top = true
- attemptPart = @attempt.compile(o)
- errorPart = if @error then " (#{ @error.compile(o) }) " else ' '
- catchPart = if @recovery then " catch#{errorPart}{\n#{ @recovery.compile(o) }\n#{@tab}}" else ''
- finallyPart = (@ensure or '') and ' finally {\n' + @ensure.compile(merge(o)) + "\n#{@tab}}"
- "#{@tab}try {\n#{attemptPart}\n#{@tab}}#{catchPart}#{finallyPart}" |
Compilation is more or less as you would expect -- the finally clause -is optional, the catch is not. | |
ThrowNode | exports.ThrowNode = class ThrowNode extends BaseNode
-
- class: 'ThrowNode'
- children: ['expression']
- isStatement: -> yes
-
- constructor: (@expression) ->
- super() |
Simple node to throw an exception. | makeReturn: ->
- return this
-
- compileNode: (o) ->
- "#{@tab}throw #{@expression.compile(o)};" |
A ThrowNode is already a return, of sorts... | |
ExistenceNode | exports.ExistenceNode = class ExistenceNode extends BaseNode
-
- class: 'ExistenceNode'
- children: ['expression']
-
- constructor: (@expression) ->
- super()
-
- compileNode: (o) ->
- test = ExistenceNode.compileTest(o, @expression)[0]
- if @parenthetical then test.substring(1, test.length - 1) else test |
Checks a variable for existence -- not null and not undefined. This is
-similar to | @compileTest: (o, variable) ->
- [first, second] = variable.compileReference o, precompile: yes
- ["(typeof #{first} !== \"undefined\" && #{second} !== null)", second] |
The meat of the ExistenceNode is in this static | |
ParentheticalNode | exports.ParentheticalNode = class ParentheticalNode extends BaseNode
-
- class: 'ParentheticalNode'
- children: ['expression']
-
- constructor: (@expression) ->
- super()
-
- isStatement: (o) ->
- @expression.isStatement(o)
-
- makeReturn: ->
- @expression.makeReturn()
-
- topSensitive: ->
- yes
-
- compileNode: (o) ->
- top = del o, 'top'
- @expression.parenthetical = true
- code = @expression.compile(o)
- return code if top and @expression.isPureStatement o
- if @parenthetical or @isStatement o
- return if top then @tab + code + ';' else code
- "(#{code})" |
An extra set of parentheses, specified explicitly in the source. At one time -we tried to clean up the results by detecting and removing redundant -parentheses, but no longer -- you can put in as many as you please. - -Parentheses are a good way to force any statement to become an expression. | |
ForNode | exports.ForNode = class ForNode extends BaseNode
-
- class: 'ForNode'
- children: ['body', 'source', 'guard']
- isStatement: -> yes
-
- constructor: (@body, source, @name, @index) ->
- super()
- @index or= null
- @source = source.source
- @guard = source.guard
- @step = source.step
- @raw = !!source.raw
- @object = !!source.object
- [@name, @index] = [@index, @name] if @object
- @pattern = @name instanceof ValueNode
- throw new Error('index cannot be a pattern matching expression') if @index instanceof ValueNode
- @returns = false
-
- topSensitive: ->
- true
-
- makeReturn: ->
- @returns = true
- this
-
- compileReturnValue: (val, o) ->
- return '\n' + new ReturnNode(literal(val)).compile(o) if @returns
- return '\n' + val if val
- '' |
CoffeeScript's replacement for the for loop is our array and object -comprehensions, that compile into for loops here. They also act as an -expression, able to return the result of each filtered iteration. - -Unlike Python array comprehensions, they can be multi-line, and you can pass -the current index of the loop as a second parameter. Unlike Ruby blocks, -you can map and filter in a single pass. | compileNode: (o) ->
- topLevel = del(o, 'top') and not @returns
- range = @source instanceof ValueNode and @source.base instanceof RangeNode and not @source.properties.length
- source = if range then @source.base else @source
- codeInBody = @body.contains (n) -> n instanceof CodeNode
- scope = o.scope
- name = (@name and @name.compile(o)) or scope.freeVariable 'i'
- index = @index and @index.compile o
- scope.find(name, immediate: yes) if name and not @pattern and (range or not codeInBody)
- scope.find(index, immediate: yes) if index
- rvar = scope.freeVariable 'result' unless topLevel
- ivar = if codeInBody then scope.freeVariable 'i' else if range then name else index or scope.freeVariable 'i'
- varPart = ''
- guardPart = ''
- body = Expressions.wrap([@body])
- if range
- sourcePart = source.compileVariables(o)
- forPart = source.compile merge o, index: ivar, step: @step
- else
- svar = scope.freeVariable 'ref'
- sourcePart = "#{svar} = #{ @source.compile(o) };"
- if @pattern
- namePart = new AssignNode(@name, literal("#{svar}[#{ivar}]")).compile(merge o, {indent: @idt(1), top: true, keepLevel: yes}) + '\n'
- else
- namePart = "#{name} = #{svar}[#{ivar}]" if name
- unless @object
- lvar = scope.freeVariable 'len'
- stepPart = if @step then "#{ivar} += #{ @step.compile(o) }" else "#{ivar}++"
- forPart = "#{ivar} = 0, #{lvar} = #{svar}.length; #{ivar} < #{lvar}; #{stepPart}"
- sourcePart = (if rvar then "#{rvar} = []; " else '') + sourcePart
- sourcePart = if sourcePart then "#{@tab}#{sourcePart}\n#{@tab}" else @tab
- returnResult = @compileReturnValue(rvar, o)
-
- body = PushNode.wrap(rvar, body) unless topLevel
- if @guard
- body = Expressions.wrap([new IfNode(@guard, body)])
- if codeInBody
- body.unshift literal "var #{name} = #{ivar}" if range
- body.unshift literal "var #{namePart}" if namePart
- body.unshift literal "var #{index} = #{ivar}" if index
- body = ClosureNode.wrap(body, true)
- else
- varPart = (namePart or '') and (if @pattern then namePart else "#{@idt(1)}#{namePart};\n")
- if @object
- forPart = "#{ivar} in #{svar}"
- guardPart = "\n#{@idt(1)}if (!#{utility('hasProp')}.call(#{svar}, #{ivar})) continue;" unless @raw
- body = body.compile(merge(o, {indent: @idt(1), top: true}))
- vars = if range then name else "#{name}, #{ivar}"
- "#{sourcePart}for (#{forPart}) {#{guardPart}\n#{varPart}#{body}\n#{@tab}}#{returnResult}" |
Welcome to the hairiest method in all of CoffeeScript. Handles the inner -loop, filtering, stepping, and result saving for array, object, and range -comprehensions. Some of the generated code can be shared in common, and -some cannot. | |
SwitchNode | exports.SwitchNode = class SwitchNode extends BaseNode
-
- class: 'SwitchNode'
- children: ['subject', 'cases', 'otherwise']
-
- isStatement: -> yes
-
- constructor: (@subject, @cases, @otherwise) ->
- super()
- @tags.subjectless = !@subject
- @subject or= literal 'true'
-
- makeReturn: ->
- pair[1].makeReturn() for pair in @cases
- @otherwise.makeReturn() if @otherwise
- this
-
- compileNode: (o) ->
- idt = o.indent = @idt 2
- o.top = yes
- code = "#{ @tab }switch (#{ @subject.compile o }) {"
- for pair in @cases
- [conditions, block] = pair
- exprs = block.expressions
- for condition in flatten [conditions]
- condition = new OpNode '!!', new ParentheticalNode condition if @tags.subjectless
- code += "\n#{ @idt(1) }case #{ condition.compile o }:"
- code += "\n#{ block.compile o }"
- code += "\n#{ idt }break;" unless exprs[exprs.length - 1] instanceof ReturnNode
- if @otherwise
- code += "\n#{ @idt(1) }default:\n#{ @otherwise.compile o }"
- code += "\n#{ @tab }}"
- code |
A JavaScript switch statement. Converts into a returnable expression on-demand. | |
IfNode | exports.IfNode = class IfNode extends BaseNode
-
- class: 'IfNode'
- children: ['condition', 'body', 'elseBody', 'assigner']
-
- topSensitive: -> true
-
- constructor: (@condition, @body, @tags) ->
- @tags or= {}
- if @tags.invert
- if @condition instanceof OpNode and @condition.isInvertible()
- @condition.invert()
- else
- @condition = new OpNode '!', new ParentheticalNode @condition
- @elseBody = null
- @isChain = false
-
- bodyNode: -> @body?.unwrap()
- elseBodyNode: -> @elseBody?.unwrap()
-
- forceStatement: ->
- @tags.statement = true
- this |
If/else statements. Acts as an expression by pushing down requested returns -to the last line of each clause. - -Single-expression IfNodes are compiled into ternary operators if possible, -because ternaries are already proper expressions, and don't need conversion. | addElse: (elseBody, statement) ->
- if @isChain
- @elseBodyNode().addElse elseBody, statement
- else
- @isChain = elseBody instanceof IfNode
- @elseBody = @ensureExpressions elseBody
- this |
Rewrite a chain of IfNodes to add a default case as the final else. | isStatement: (o) ->
- @statement or= !!((o and o.top) or @tags.statement or @bodyNode().isStatement(o) or (@elseBody and @elseBodyNode().isStatement(o)))
-
- compileCondition: (o) ->
- conditions = flatten [@condition]
- conditions[0].parenthetical = yes if conditions.length is 1
- (cond.compile(o) for cond in conditions).join(' || ')
-
- compileNode: (o) ->
- if @isStatement(o) then @compileStatement(o) else @compileTernary(o)
-
- makeReturn: ->
- if @isStatement()
- @body and= @ensureExpressions(@body.makeReturn())
- @elseBody and= @ensureExpressions(@elseBody.makeReturn())
- this
- else
- new ReturnNode this
-
- ensureExpressions: (node) ->
- if node instanceof Expressions then node else new Expressions [node] |
The IfNode only compiles into a statement if either of its bodies needs -to be a statement. Otherwise a ternary is safe. | compileStatement: (o) ->
- top = del o, 'top'
- child = del o, 'chainChild'
- condO = merge o
- o.indent = @idt 1
- o.top = true
- ifDent = if child or (top and not @isStatement(o)) then '' else @idt()
- comDent = if child then @idt() else ''
- body = @body.compile(o)
- ifPart = "#{ifDent}if (#{ @compileCondition(condO) }) {\n#{body}\n#{@tab}}"
- return ifPart unless @elseBody
- elsePart = if @isChain
- ' else ' + @elseBodyNode().compile(merge(o, {indent: @idt(), chainChild: true}))
- else
- " else {\n#{ @elseBody.compile(o) }\n#{@tab}}"
- "#{ifPart}#{elsePart}" |
Compile the IfNode as a regular if-else statement. Flattened chains -force inner else bodies into statement form. | compileTernary: (o) ->
- @bodyNode().tags.operation = @condition.tags.operation = yes
- @elseBodyNode().tags.operation = yes if @elseBody
- ifPart = @condition.compile(o) + ' ? ' + @bodyNode().compile(o)
- elsePart = if @elseBody then @elseBodyNode().compile(o) else 'null'
- code = "#{ifPart} : #{elsePart}"
- if @tags.operation then "(#{code})" else code |
Compile the IfNode as a ternary operator. | |
Faux-Nodes | |
PushNode | PushNode = exports.PushNode =
- wrap: (array, expressions) ->
- expr = expressions.unwrap()
- return expressions if expr.isPureStatement() or expr.containsPureStatement()
- Expressions.wrap([new CallNode(
- new ValueNode(literal(array), [new AccessorNode(literal('push'))]), [expr]
- )]) |
Faux-nodes are never created by the grammar, but are used during code
-generation to generate other combinations of nodes. The PushNode creates
-the tree for | |
ClosureNode | ClosureNode = exports.ClosureNode = |
A faux-node used to wrap an expressions body in a closure. | wrap: (expressions, statement) ->
- return expressions if expressions.containsPureStatement()
- func = new ParentheticalNode(new CodeNode([], Expressions.wrap([expressions])))
- args = []
- mentionsArgs = expressions.contains (n) ->
- n instanceof LiteralNode and (n.value is 'arguments')
- mentionsThis = expressions.contains (n) ->
- (n instanceof LiteralNode and (n.value is 'this')) or
- (n instanceof CodeNode and n.bound)
- if mentionsArgs or mentionsThis
- meth = literal(if mentionsArgs then 'apply' else 'call')
- args = [literal('this')]
- args.push literal 'arguments' if mentionsArgs
- func = new ValueNode func, [new AccessorNode(meth)]
- call = new CallNode(func, args)
- if statement then Expressions.wrap([call]) else call |
Wrap the expressions body, unless it contains a pure statement,
-in which case, no dice. If the body mentions | UTILITIES = |
Utility Functions | extends: """
- function(child, parent) {
- var ctor = function(){};
- ctor.prototype = parent.prototype;
- child.prototype = new ctor();
- child.prototype.constructor = child;
- if (typeof parent.extended === "function") parent.extended(child);
- child.__super__ = parent.prototype;
- }
- """ |
Correctly set up a prototype chain for inheritance, including a reference
-to the superclass for | bind: """
- function(func, context) {
- return function(){ return func.apply(context, arguments); };
- }
- """ |
Create a function bound to the current value of "this". | hasProp: 'Object.prototype.hasOwnProperty'
- slice: 'Array.prototype.slice' |
Shortcuts to speed up the lookup time for native functions. | |
Constants | TAB = ' ' |
Tabs are two spaces for pretty printing. | TRAILING_WHITESPACE = /[ \t]+$/gm |
Trim out all trailing whitespace, so that the generated code plays nice -with Git. | IDENTIFIER = /^[a-zA-Z\$_](\w|\$)*$/
-NUMBER = /^(((\b0(x|X)[0-9a-fA-F]+)|((\b[0-9]+(\.[0-9]+)?|\.[0-9]+)(e[+\-]?[0-9]+)?)))\b$/i
-SIMPLENUM = /^-?\d+$/ |
Keep these identifier regexes in sync with the Lexer. | IS_STRING = /^['"]/ |
Is a literal value a string? | |
Utility Functions | literal = (name) ->
- new LiteralNode(name) |
Handy helper for a generating LiteralNode. | utility = (name) ->
- ref = "__#{name}"
- Scope.root.assign ref, UTILITIES[name]
- ref
-
- |
Helper for ensuring that utility functions are assigned at the top level. | undefined |
optparse.coffee | |
---|---|
A simple OptionParser class to parse option flags from the command-line. -Use it like so: - -
-
-The first non-option is considered to be the start of the file (and file -option) list, and all subsequent arguments are left unparsed. | exports.OptionParser = class OptionParser |
Initialize with a list of valid options, in the form: - -
-
-Along with an an optional banner for the usage help. | constructor: (rules, banner) ->
- @banner = banner
- @rules = buildRules rules |
Parse the list of arguments, populating an | parse: (args) ->
- options = arguments: []
- args = normalizeArguments args
- for arg, i in args
- isOption = !!(arg.match(LONG_FLAG) or arg.match(SHORT_FLAG))
- matchedRule = no
- for rule in @rules
- if rule.shortFlag is arg or rule.longFlag is arg
- value = if rule.hasArgument then args[i += 1] else true
- options[rule.name] = if rule.isList then (options[rule.name] or []).concat value else value
- matchedRule = yes
- break
- throw new Error "unrecognized option: #{arg}" if isOption and not matchedRule
- if not isOption
- options.arguments = args[i...args.length]
- break
- options |
Return the help text for this OptionParser, listing and describing all
-of the valid options, for | help: ->
- lines = ['Available options:']
- lines.unshift "#{@banner}\n" if @banner
- for rule in @rules
- spaces = 15 - rule.longFlag.length
- 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" |
Helpers | |
Regex matchers for option flags. | LONG_FLAG = /^(--\w[\w\-]+)/
-SHORT_FLAG = /^(-\w)/
-MULTI_FLAG = /^-(\w{2,})/
-OPTIONAL = /\[(\w+(\*?))\]/ |
Build and return the list of option rules. If the optional short-flag is
-unspecified, leave it out by padding with | buildRules = (rules) ->
- for tuple in rules
- tuple.unshift null if tuple.length < 3
- buildRule tuple... |
Build a rule from a | buildRule = (shortFlag, longFlag, description, options) ->
- match = longFlag.match(OPTIONAL)
- longFlag = longFlag.match(LONG_FLAG)[1]
- options or= {}
- {
- name: longFlag.substr 2
- shortFlag: shortFlag
- longFlag: longFlag
- description: description
- hasArgument: !!(match and match[1])
- isList: !!(match and match[2])
- } |
Normalize arguments by expanding merged flags into multiple flags. This allows
-you to have | normalizeArguments = (args) ->
- args = args.slice 0
- result = []
- for arg in args
- if match = arg.match MULTI_FLAG
- result.push '-' + l for l in match[1].split ''
- else
- result.push arg
- result
-
- |
repl.coffee | |
---|---|
A very simple Read-Eval-Print-Loop. Compiles one line at a time to JavaScript -and evaluates it. Good for simple tests, or poking around the Node.js API. -Using it looks like this: - - | |
Require the coffee-script module to get access to the compiler. | CoffeeScript = require './coffee-script'
-helpers = require('./helpers').helpers
-readline = require 'readline' |
Start by opening up stdio. | stdio = process.openStdin() |
Quick alias for quitting the REPL. | helpers.extend global, quit: -> process.exit(0) |
The main REPL function. run is called every time a line of code is entered. -Attempt to evaluate the command. If there's an exception, print it out instead -of exiting. | run = (buffer) ->
- try
- val = CoffeeScript.eval buffer.toString(), noWrap: true, globals: true, fileName: 'repl'
- puts inspect val if val isnt undefined
- catch err
- puts err.stack or err.toString()
- repl.prompt() |
Create the REPL by listening to stdin. | repl = readline.createInterface stdio
-repl.setPrompt 'coffee> '
-stdio.on 'data', (buffer) -> repl.write buffer
-repl.on 'close', -> stdio.destroy()
-repl.on 'line', run
-repl.prompt()
-
- |
rewriter.coffee | |
---|---|
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 -shorthand into the unambiguous long form, add implicit indentation and -parentheses, balance incorrect nestings, and generally clean things up. | |
Import the helpers we need. | {include} = require('./helpers').helpers |
The Rewriter class is used by the Lexer, directly against -its internal array of tokens. | exports.Rewriter = class Rewriter |
Helpful snippet for debugging: - puts (t[0] + '/' + t[1] for t in @tokens).join ' ' | |
Rewrite the token stream in multiple passes, one logical filter at -a time. This could certainly be changed into a single pass through the -stream, with a big ol' efficient switch, but it's much nicer to work with -like this. The order of these passes matters -- indentation must be -corrected before implicit parentheses can be wrapped around blocks of code. | rewrite: (tokens) ->
- @tokens = tokens
- @adjustComments()
- @removeLeadingNewlines()
- @removeMidExpressionNewlines()
- @closeOpenCalls()
- @closeOpenIndexes()
- @addImplicitIndentation()
- @tagPostfixConditionals()
- @addImplicitBraces()
- @addImplicitParentheses()
- @ensureBalance BALANCED_PAIRS
- @rewriteClosingParens()
- @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 -as tokens are inserted and removed, and the stream changes length under -our feet. | scanTokens: (block) ->
- i = 0
- loop
- break unless @tokens[i]
- move = block.call this, @tokens[i], i
- i += move
- true
-
- detectEnd: (i, condition, action) ->
- levels = 0
- loop
- token = @tokens[i]
- return action.call this, token, i if levels is 0 and condition.call this, token, i
- return action.call this, token, i - 1 if not token or levels < 0
- levels += 1 if include EXPRESSION_START, token[0]
- levels -= 1 if include EXPRESSION_END, token[0]
- i += 1
- i - 1 |
Massage newlines and indentations so that comments don't have to be -correctly indented, or appear on a line of their own. | adjustComments: ->
- @scanTokens (token, i) ->
- return 1 unless token[0] is 'HERECOMMENT'
- [before, prev, post, after] = [@tokens[i - 2], @tokens[i - 1], @tokens[i + 1], @tokens[i + 2]]
- if after and after[0] is 'INDENT'
- @tokens.splice i + 2, 1
- if before and before[0] is 'OUTDENT' and post and prev[0] is post[0] is 'TERMINATOR'
- @tokens.splice i - 2, 1
- else
- @tokens.splice i, 0, after
- else if prev and prev[0] not in ['TERMINATOR', 'INDENT', 'OUTDENT']
- if post and post[0] is 'TERMINATOR' and after and after[0] is 'OUTDENT'
- @tokens.splice(i + 2, 0, @tokens.splice(i, 2)...)
- if @tokens[i + 2][0] isnt 'TERMINATOR'
- @tokens.splice i + 2, 0, ['TERMINATOR', "\n", prev[2]]
- else
- @tokens.splice i, 0, ['TERMINATOR', "\n", prev[2]]
- return 2
- return 1 |
Leading newlines would introduce an ambiguity in the grammar, so we -dispatch them here. | removeLeadingNewlines: ->
- @tokens.shift() while @tokens[0] and @tokens[0][0] is 'TERMINATOR' |
Some blocks occur in the middle of expressions -- when we're expecting -this, remove their trailing newlines. | removeMidExpressionNewlines: ->
- @scanTokens (token, i) ->
- return 1 unless include(EXPRESSION_CLOSE, @tag(i + 1)) and token[0] is 'TERMINATOR'
- @tokens.splice i, 1
- return 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: ->
- @scanTokens (token, i) ->
- if token[0] is 'CALL_START'
- condition = (token, i) ->
- (token[0] in [')', 'CALL_END']) or (token[0] is 'OUTDENT' and @tokens[i - 1][0] is ')')
- action = (token, i) ->
- idx = if token[0] is 'OUTDENT' then i - 1 else i
- @tokens[idx][0] = 'CALL_END'
- @detectEnd i + 1, condition, action
- return 1 |
The lexer has tagged the opening parenthesis of an indexing operation call. -Match it with its paired close. | closeOpenIndexes: ->
- @scanTokens (token, i) ->
- if token[0] is 'INDEX_START'
- condition = (token, i) -> token[0] in [']', 'INDEX_END']
- action = (token, i) -> token[0] = 'INDEX_END'
- @detectEnd i + 1, condition, action
- return 1 |
Object literals may be written with implicit braces, for simple cases. -Insert the missing braces here, so that the parser doesn't have to. | addImplicitBraces: ->
- stack = []
- @scanTokens (token, i) ->
- if include EXPRESSION_START, token[0]
- stack.push(if (token[0] is 'INDENT' and (@tag(i - 1) is '{')) then '{' else token[0])
- if include EXPRESSION_END, token[0]
- stack.pop()
- last = stack[stack.length - 1]
- if token[0] is ':' and (not last or last[0] isnt '{')
- stack.push '{'
- idx = if @tag(i - 2) is '@' then i - 2 else i - 1
- idx -= 2 if @tag(idx - 2) is 'HERECOMMENT'
- tok = ['{', '{', token[2]]
- tok.generated = yes
- @tokens.splice idx, 0, tok
- condition = (token, i) ->
- [one, two, three] = @tokens.slice(i + 1, i + 4)
- return false if 'HERECOMMENT' in [@tag(i + 1), @tag(i - 1)]
- ((token[0] in ['TERMINATOR', 'OUTDENT']) and not ((two and two[0] is ':') or (one and one[0] is '@' and three and three[0] is ':'))) or
- (token[0] is ',' and one and (one[0] not in ['IDENTIFIER', 'STRING', '@', 'TERMINATOR', 'OUTDENT']))
- action = (token, i) ->
- @tokens.splice i, 0, ['}', '}', token[2]]
- @detectEnd i + 2, condition, action
- return 2
- return 1 |
Methods may be optionally called without parentheses, for simple cases. -Insert the implicit parentheses here, so that the parser doesn't have to -deal with them. | addImplicitParentheses: ->
- classLine = no
- @scanTokens (token, i) ->
- classLine = yes if token[0] is 'CLASS'
- prev = @tokens[i - 1]
- next = @tokens[i + 1]
- idx = 1
- callObject = not classLine and token[0] is 'INDENT' and next and next.generated and next[0] is '{' and prev and include(IMPLICIT_FUNC, prev[0])
- idx = 2 if callObject
- seenSingle = no
- classLine = no if include(LINEBREAKS, token[0])
- token.call = yes if prev and not prev.spaced and token[0] is '?'
- if prev and (prev.spaced and (include(IMPLICIT_FUNC, prev[0]) or prev.call) and include(IMPLICIT_CALL, token[0]) and
- not (token[0] is 'UNARY' and (@tag(i + 1) in ['IN', 'OF', 'INSTANCEOF']))) or callObject
- @tokens.splice i, 0, ['CALL_START', '(', token[2]]
- condition = (token, i) ->
- return yes if not seenSingle and token.fromThen
- seenSingle = yes if token[0] in ['IF', 'ELSE', 'UNLESS', '->', '=>']
- post = @tokens[i + 1]
- (not token.generated and @tokens[i - 1][0] isnt ',' and include(IMPLICIT_END, token[0]) and
- not (token[0] is 'INDENT' and (include(IMPLICIT_BLOCK, @tag(i - 1)) or @tag(i - 2) is 'CLASS' or (post and post.generated and post[0] is '{')))) or
- token[0] is 'PROPERTY_ACCESS' and @tag(i - 1) is 'OUTDENT'
- action = (token, i) ->
- idx = if token[0] is 'OUTDENT' then i + 1 else i
- @tokens.splice idx, 0, ['CALL_END', ')', token[2]]
- @detectEnd i + idx, condition, action
- prev[0] = 'FUNC_EXIST' if prev[0] is '?'
- return 2
- return 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, -but we need to make sure it's balanced. | addImplicitIndentation: ->
- @scanTokens (token, i) ->
- if token[0] is 'ELSE' and @tag(i - 1) isnt 'OUTDENT'
- @tokens.splice i, 0, @indentation(token)...
- return 2
- if token[0] is 'CATCH' and
- (@tag(i + 2) is 'TERMINATOR' or @tag(i + 2) is 'FINALLY')
- @tokens.splice i + 2, 0, @indentation(token)...
- return 4
- if include(SINGLE_LINERS, token[0]) and @tag(i + 1) isnt 'INDENT' and
- not (token[0] is 'ELSE' and @tag(i + 1) is 'IF')
- starter = token[0]
- [indent, outdent] = @indentation token
- indent.fromThen = true if starter is 'THEN'
- indent.generated = outdent.generated = true
- @tokens.splice i + 1, 0, indent
- condition = (token, i) ->
- (include(SINGLE_CLOSERS, token[0]) and token[1] isnt ';') and
- not (token[0] is 'ELSE' and starter not in ['IF', 'THEN'])
- action = (token, i) ->
- idx = if @tokens[i - 1][0] is ',' then i - 1 else i
- @tokens.splice idx, 0, outdent
- @detectEnd i + 2, condition, action
- @tokens.splice i, 1 if token[0] is 'THEN'
- return 2
- return 1 |
Tag postfix conditionals as such, so that we can parse them with a -different precedence. | tagPostfixConditionals: ->
- @scanTokens (token, i) ->
- if token[0] in ['IF', 'UNLESS']
- original = token
- condition = (token, i) ->
- token[0] in ['TERMINATOR', 'INDENT']
- action = (token, i) ->
- original[0] = 'POST_' + original[0] if token[0] isnt 'INDENT'
- @detectEnd i + 1, condition, action
- return 1
- return 1 |
Ensure that all listed pairs of tokens are correctly balanced throughout -the course of the token stream. | ensureBalance: (pairs) ->
- levels = {}
- openLine = {}
- @scanTokens (token, i) ->
- for pair in pairs
- [open, close] = pair
- levels[open] or= 0
- if token[0] is open
- openLine[open] = token[2] if levels[open] == 0
- levels[open] += 1
- levels[open] -= 1 if token[0] is close
- throw new Error("too many #{token[1]} on line #{token[2] + 1}") if levels[open] < 0
- return 1
- unclosed = key for key, value of levels when value > 0
- if unclosed.length
- open = unclosed[0]
- line = openLine[open] + 1
- throw new Error "unclosed #{open} on line #{line}" |
We'd like to support syntax like this: - -
-
-In order to accomplish this, move outdents that follow closing parens -inwards, safely. The steps to accomplish this are: - -
| rewriteClosingParens: ->
- stack = []
- debt = {}
- (debt[key] = 0) for key, val of INVERSES
- @scanTokens (token, i) ->
- tag = token[0]
- inv = INVERSES[token[0]]
- if include EXPRESSION_START, tag
- stack.push token
- return 1
- else if include EXPRESSION_END, tag
- if debt[inv] > 0
- debt[inv] -= 1
- @tokens.splice i, 1
- return 0
- else
- match = stack.pop()
- mtag = match[0]
- oppos = INVERSES[mtag]
- return 1 if tag is oppos
- debt[mtag] += 1
- val = [oppos, if mtag is 'INDENT' then match[1] else oppos]
- if @tokens[i + 2]?[0] is mtag
- @tokens.splice i + 3, 0, val
- stack.push(match)
- else
- @tokens.splice i, 0, val
- return 1
- else
- return 1 |
Generate the indentation tokens, based on another token on the same line. | indentation: (token) ->
- [['INDENT', 2, token[2]], ['OUTDENT', 2, token[2]]] |
Look up a tag by token index. | tag: (i) ->
- @tokens[i] and @tokens[i][0] |
Constants | |
List of the token pairs that must be balanced. | BALANCED_PAIRS = [['(', ')'], ['[', ']'], ['{', '}'], ['INDENT', 'OUTDENT'],
- ['PARAM_START', 'PARAM_END'], ['CALL_START', 'CALL_END'], ['INDEX_START', 'INDEX_END']] |
The inverse mappings of | INVERSES = {}
-for pair in BALANCED_PAIRS
- INVERSES[pair[0]] = pair[1]
- INVERSES[pair[1]] = pair[0] |
The tokens that signal the start of a balanced pair. | EXPRESSION_START = pair[0] for pair in BALANCED_PAIRS |
The tokens that signal the end of a balanced pair. | EXPRESSION_END = pair[1] for pair in BALANCED_PAIRS |
Tokens that indicate the close of a clause of an expression. | EXPRESSION_CLOSE = ['CATCH', 'WHEN', 'ELSE', 'FINALLY'].concat EXPRESSION_END |
Tokens that, if followed by an | IMPLICIT_FUNC = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@', 'THIS'] |
If preceded by an | IMPLICIT_CALL = [
- 'IDENTIFIER', 'NUMBER', 'STRING', 'JS', 'REGEX', 'NEW', 'PARAM_START', 'CLASS',
- 'IF', 'UNLESS', 'TRY', 'SWITCH', 'THIS', 'NULL', 'UNARY'
- 'TRUE', 'FALSE', 'YES', 'NO', 'ON', 'OFF',
- '@', '->', '=>', '[', '(', '{'
-] |
Tokens indicating that the implicit call must enclose a block of expressions. | IMPLICIT_BLOCK = ['->', '=>', '{', '[', ','] |
Tokens that always mark the end of an implicit call for single-liners. | IMPLICIT_END = ['POST_IF', 'POST_UNLESS', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'TERMINATOR', 'INDENT'] |
Single-line flavors of block expressions that have unclosed endings. -The grammar can't disambiguate them, so we insert the implicit indentation. | SINGLE_LINERS = ['ELSE', "->", "=>", 'TRY', 'FINALLY', 'THEN']
-SINGLE_CLOSERS = ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN'] |
Tokens that end a line. | LINEBREAKS = ['TERMINATOR', 'INDENT', 'OUTDENT']
-
- |
scope.coffee | |
---|---|
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
-variables are new and need to be declared with | |
Import the helpers we plan to use. | {extend} = require('./helpers').helpers
-
-exports.Scope = class Scope |
The top-level Scope object. | @root: null |
Initialize a scope with its parent, for lookups up the chain, -as well as a reference to the Expressions node is belongs to, which is -where it should declare its variables, and a reference to the function that -it wraps. | constructor: (parent, expressions, method) ->
- [@parent, @expressions, @method] = [parent, expressions, method]
- @variables = {}
- if @parent
- @garbage = @parent.garbage
- else
- @garbage = []
- Scope.root = this |
Create a new garbage level | startLevel: ->
- @garbage.push [] |
Return to the previous garbage level and erase referenced temporary -variables in current level from scope. | endLevel: ->
- (@variables[name] = 'reuse') for name in @garbage.pop() when @variables[name] is 'var' |
Look up a variable name in lexical scope, and declare it if it does not -already exist. | find: (name, options) ->
- return true if @check name, options
- @variables[name] = 'var'
- false |
Test variables and return true the first time fn(v, k) returns true | any: (fn) ->
- for v, k of @variables when fn(v, k)
- return true
- return false |
Reserve a variable name as originating from a function parameter for this
-scope. No | parameter: (name) ->
- @variables[name] = 'param' |
Just check to see if a variable has already been declared, without reserving, -walks up to the root scope. | check: (name, options) ->
- immediate = Object::hasOwnProperty.call @variables, name
- return immediate if immediate or (options and options.immediate)
- !!(@parent and @parent.check(name)) |
Generate a temporary variable name at the given index. | temporary: (type, index) ->
- if type.length > 1
- '_' + type + if index > 1 then index else ''
- else
- '_' + (index + parseInt type, 36).toString(36).replace /\d/g, 'a' |
If we need to store an intermediate result, find an available name for a
-compiler-generated variable. | freeVariable: (type) ->
- index = 0
- index++ while @check(temp = @temporary type, index) and @variables[temp] isnt 'reuse'
- @variables[temp] = 'var'
- @garbage[@garbage.length - 1].push temp if @garbage.length
- temp |
Ensure that an assignment is made at the top of this scope -(or at the top-level scope, if requested). | assign: (name, value) ->
- @variables[name] = value: value, assigned: true |
Does this scope reference any variables that need to be declared in the -given function body? | hasDeclarations: (body) ->
- body is @expressions and @any (k, val) -> val is 'var' or val is 'reuse' |
Does this scope reference any assignments that need to be declared at the -top of the given function body? | hasAssignments: (body) ->
- body is @expressions and @any (k, val) -> val.assigned |
Return the list of variables first declared in this scope. | declaredVariables: ->
- (key for key, val of @variables when val is 'var' or val is 'reuse').sort() |
Return the list of assignments that are supposed to be made at the top -of this scope. | assignedVariables: ->
- "#{key} = #{val.value}" for key, val of @variables when val.assigned |
Compile the JavaScript for all of the variable declarations in this scope. | compiledDeclarations: ->
- @declaredVariables().join ', ' |
Compile the JavaScript forall of the variable assignments in this scope. | compiledAssignments: ->
- @assignedVariables().join ', '
-
- |
underscore.coffee | |
---|---|
| |
Underscore.coffee -(c) 2010 Jeremy Ashkenas, DocumentCloud Inc. -Underscore is freely distributable under the terms of the -MIT license. -Portions of Underscore are inspired by or borrowed from -Prototype.js, Oliver Steele's -Functional, and John Resig's -Micro-Templating. -For all details and documentation: -http://documentcloud.github.com/underscore/ | |
Baseline setup | |
Establish the root object, | root = this |
Save the previous value of the | previousUnderscore = root._ |
Establish the object that gets thrown to break out of a loop iteration.
- | breaker = if typeof(StopIteration) is 'undefined' then '__break__' else StopIteration |
Helper function to escape RegExp contents, because JS doesn't have one. | escapeRegExp = (string) -> string.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1') |
Save bytes in the minified (but not gzipped) version: | ArrayProto = Array.prototype
- ObjProto = Object.prototype |
Create quick reference variables for speed access to core prototypes. | slice = ArrayProto.slice
- unshift = ArrayProto.unshift
- toString = ObjProto.toString
- hasOwnProperty = ObjProto.hasOwnProperty
- propertyIsEnumerable = ObjProto.propertyIsEnumerable |
All ECMA5 native implementations we hope to use are declared here. | nativeForEach = ArrayProto.forEach
- nativeMap = ArrayProto.map
- nativeReduce = ArrayProto.reduce
- nativeReduceRight = ArrayProto.reduceRight
- nativeFilter = ArrayProto.filter
- nativeEvery = ArrayProto.every
- nativeSome = ArrayProto.some
- nativeIndexOf = ArrayProto.indexOf
- nativeLastIndexOf = ArrayProto.lastIndexOf
- nativeIsArray = Array.isArray
- nativeKeys = Object.keys |
Create a safe reference to the Underscore object for use below. | _ = (obj) -> new wrapper(obj) |
Export the Underscore object for CommonJS. | if typeof(exports) != 'undefined' then exports._ = _ |
Export Underscore to global scope. | root._ = _ |
Current version. | _.VERSION = '1.1.0' |
Collection Functions | |
The cornerstone, an each implementation. -Handles objects implementing forEach, arrays, and raw objects. | _.each = (obj, iterator, context) ->
- try
- if nativeForEach and obj.forEach is nativeForEach
- obj.forEach iterator, context
- else if _.isNumber obj.length
- iterator.call(context, obj[i], i, obj) for i in [0...obj.length]
- else
- iterator.call(context, val, key, obj) for key, val of obj
- catch e
- throw e if e isnt breaker
- obj |
Return the results of applying the iterator to each element. Use JavaScript -1.6's version of map, if possible. | _.map = (obj, iterator, context) ->
- return obj.map(iterator, context) if nativeMap and obj.map is nativeMap
- results = []
- _.each obj, (value, index, list) ->
- results.push iterator.call context, value, index, list
- results |
Reduce builds up a single result from a list of values. Also known as -inject, or foldl. Uses JavaScript 1.8's version of reduce, if possible. | _.reduce = (obj, iterator, memo, context) ->
- if nativeReduce and obj.reduce is nativeReduce
- iterator = _.bind iterator, context if context
- return obj.reduce iterator, memo
- _.each obj, (value, index, list) ->
- memo = iterator.call context, memo, value, index, list
- memo |
The right-associative version of reduce, also known as foldr. Uses -JavaScript 1.8's version of reduceRight, if available. | _.reduceRight = (obj, iterator, memo, context) ->
- if nativeReduceRight and obj.reduceRight is nativeReduceRight
- iterator = _.bind iterator, context if context
- return obj.reduceRight iterator, memo
- reversed = _.clone(_.toArray(obj)).reverse()
- _.reduce reversed, iterator, memo, context |
Return the first value which passes a truth test. | _.detect = (obj, iterator, context) ->
- result = null
- _.each obj, (value, index, list) ->
- if iterator.call context, value, index, list
- result = value
- _.breakLoop()
- result |
Return all the elements that pass a truth test. Use JavaScript 1.6's -filter, if it exists. | _.filter = (obj, iterator, context) ->
- return obj.filter iterator, context if nativeFilter and obj.filter is nativeFilter
- results = []
- _.each obj, (value, index, list) ->
- results.push value if iterator.call context, value, index, list
- results |
Return all the elements for which a truth test fails. | _.reject = (obj, iterator, context) ->
- results = []
- _.each obj, (value, index, list) ->
- results.push value if not iterator.call context, value, index, list
- results |
Determine whether all of the elements match a truth test. Delegate to -JavaScript 1.6's every, if it is present. | _.every = (obj, iterator, context) ->
- iterator ||= _.identity
- return obj.every iterator, context if nativeEvery and obj.every is nativeEvery
- result = true
- _.each obj, (value, index, list) ->
- _.breakLoop() unless (result = result and iterator.call(context, value, index, list))
- result |
Determine if at least one element in the object matches a truth test. Use -JavaScript 1.6's some, if it exists. | _.some = (obj, iterator, context) ->
- iterator ||= _.identity
- return obj.some iterator, context if nativeSome and obj.some is nativeSome
- result = false
- _.each obj, (value, index, list) ->
- _.breakLoop() if (result = iterator.call(context, value, index, list))
- result |
Determine if a given value is included in the array or object,
-based on | _.include = (obj, target) ->
- return _.indexOf(obj, target) isnt -1 if nativeIndexOf and obj.indexOf is nativeIndexOf
- for key, val of obj
- return true if val is target
- false |
Invoke a method with arguments on every item in a collection. | _.invoke = (obj, method) ->
- args = _.rest arguments, 2
- (if method then val[method] else val).apply(val, args) for val in obj |
Convenience version of a common use case of map: fetching a property. | _.pluck = (obj, key) ->
- _.map(obj, (val) -> val[key]) |
Return the maximum item or (item-based computation). | _.max = (obj, iterator, context) ->
- return Math.max.apply(Math, obj) if not iterator and _.isArray(obj)
- result = computed: -Infinity
- _.each obj, (value, index, list) ->
- computed = if iterator then iterator.call(context, value, index, list) else value
- computed >= result.computed and (result = {value: value, computed: computed})
- result.value |
Return the minimum element (or element-based computation). | _.min = (obj, iterator, context) ->
- return Math.min.apply(Math, obj) if not iterator and _.isArray(obj)
- result = computed: Infinity
- _.each obj, (value, index, list) ->
- computed = if iterator then iterator.call(context, value, index, list) else value
- computed < result.computed and (result = {value: value, computed: computed})
- result.value |
Sort the object's values by a criterion produced by an iterator. | _.sortBy = (obj, iterator, context) ->
- _.pluck(((_.map obj, (value, index, list) ->
- {value: value, criteria: iterator.call(context, value, index, list)}
- ).sort((left, right) ->
- a = left.criteria; b = right.criteria
- if a < b then -1 else if a > b then 1 else 0
- )), 'value') |
Use a comparator function to figure out at what index an object should -be inserted so as to maintain order. Uses binary search. | _.sortedIndex = (array, obj, iterator) ->
- iterator ||= _.identity
- low = 0
- high = array.length
- while low < high
- mid = (low + high) >> 1
- if iterator(array[mid]) < iterator(obj) then low = mid + 1 else high = mid
- low |
Convert anything iterable into a real, live array. | _.toArray = (iterable) ->
- return [] if (!iterable)
- return iterable.toArray() if (iterable.toArray)
- return iterable if (_.isArray(iterable))
- return slice.call(iterable) if (_.isArguments(iterable))
- _.values(iterable) |
Return the number of elements in an object. | _.size = (obj) -> _.toArray(obj).length |
Array Functions | |
Get the first element of an array. Passing | _.first = (array, n, guard) ->
- if n and not guard then slice.call(array, 0, n) else array[0] |
Returns everything but the first entry of the array. Aliased as tail.
-Especially useful on the arguments object. Passing an | _.rest = (array, index, guard) ->
- slice.call(array, if _.isUndefined(index) or guard then 1 else index) |
Get the last element of an array. | _.last = (array) -> array[array.length - 1] |
Trim out all falsy values from an array. | _.compact = (array) -> item for item in array when item |
Return a completely flattened version of an array. | _.flatten = (array) ->
- _.reduce array, (memo, value) ->
- return memo.concat(_.flatten(value)) if _.isArray value
- memo.push value
- memo
- , [] |
Return a version of the array that does not contain the specified value(s). | _.without = (array) ->
- values = _.rest arguments
- val for val in _.toArray(array) when not _.include values, val |
Produce a duplicate-free version of the array. If the array has already -been sorted, you have the option of using a faster algorithm. | _.uniq = (array, isSorted) ->
- memo = []
- for el, i in _.toArray array
- memo.push el if i is 0 || (if isSorted is true then _.last(memo) isnt el else not _.include(memo, el))
- memo |
Produce an array that contains every item shared between all the -passed-in arrays. | _.intersect = (array) ->
- rest = _.rest arguments
- _.select _.uniq(array), (item) ->
- _.all rest, (other) ->
- _.indexOf(other, item) >= 0 |
Zip together multiple lists into a single array -- elements that share -an index go together. | _.zip = ->
- length = _.max _.pluck arguments, 'length'
- results = new Array length
- for i in [0...length]
- results[i] = _.pluck arguments, String i
- results |
If the browser doesn't supply us with indexOf (I'm looking at you, MSIE), -we need this function. Return the position of the first occurence of an -item in an array, or -1 if the item is not included in the array. | _.indexOf = (array, item) ->
- return array.indexOf item if nativeIndexOf and array.indexOf is nativeIndexOf
- i = 0; l = array.length
- while l - i
- if array[i] is item then return i else i++
- -1 |
Provide JavaScript 1.6's lastIndexOf, delegating to the native function, -if possible. | _.lastIndexOf = (array, item) ->
- return array.lastIndexOf(item) if nativeLastIndexOf and array.lastIndexOf is nativeLastIndexOf
- i = array.length
- while i
- if array[i] is item then return i else i--
- -1 |
Generate an integer Array containing an arithmetic progression. A port of -the native Python range function. | _.range = (start, stop, step) ->
- a = arguments
- solo = a.length <= 1
- i = start = if solo then 0 else a[0]
- stop = if solo then a[0] else a[1]
- step = a[2] or 1
- len = Math.ceil((stop - start) / step)
- return [] if len <= 0
- range = new Array len
- idx = 0
- loop
- return range if (if step > 0 then i - stop else stop - i) >= 0
- range[idx] = i
- idx++
- i+= step |
Function Functions | |
Create a function bound to a given object (assigning | _.bind = (func, obj) ->
- args = _.rest arguments, 2
- -> func.apply obj or root, args.concat arguments |
Bind all of an object's methods to that object. Useful for ensuring that -all callbacks defined on an object belong to it. | _.bindAll = (obj) ->
- funcs = if arguments.length > 1 then _.rest(arguments) else _.functions(obj)
- _.each funcs, (f) -> obj[f] = _.bind obj[f], obj
- obj |
Delays a function for the given number of milliseconds, and then calls -it with the arguments supplied. | _.delay = (func, wait) ->
- args = _.rest arguments, 2
- setTimeout((-> func.apply(func, args)), wait) |
Memoize an expensive function by storing its results. | _.memoize = (func, hasher) ->
- memo = {}
- hasher or= _.identity
- ->
- key = hasher.apply this, arguments
- return memo[key] if key of memo
- memo[key] = func.apply this, arguments |
Defers a function, scheduling it to run after the current call stack has -cleared. | _.defer = (func) ->
- _.delay.apply _, [func, 1].concat _.rest arguments |
Returns the first function passed as an argument to the second, -allowing you to adjust arguments, run code before and after, and -conditionally execute the original function. | _.wrap = (func, wrapper) ->
- -> wrapper.apply wrapper, [func].concat arguments |
Returns a function that is the composition of a list of functions, each -consuming the return value of the function that follows. | _.compose = ->
- funcs = arguments
- ->
- args = arguments
- for i in [(funcs.length - 1)..0]
- args = [funcs[i].apply(this, args)]
- args[0] |
Object Functions | |
Retrieve the names of an object's properties. | _.keys = nativeKeys or (obj) ->
- return _.range 0, obj.length if _.isArray(obj)
- key for key, val of obj |
Retrieve the values of an object's properties. | _.values = (obj) ->
- _.map obj, _.identity |
Return a sorted list of the function names available in Underscore. | _.functions = (obj) ->
- _.filter(_.keys(obj), (key) -> _.isFunction(obj[key])).sort() |
Extend a given object with all of the properties in a source object. | _.extend = (obj) ->
- for source in _.rest(arguments)
- (obj[key] = val) for key, val of source
- obj |
Create a (shallow-cloned) duplicate of an object. | _.clone = (obj) ->
- return obj.slice 0 if _.isArray obj
- _.extend {}, obj |
Invokes interceptor with the obj, and then returns obj. -The primary purpose of this method is to "tap into" a method chain, in order to perform operations on intermediate results within the chain. | _.tap = (obj, interceptor) ->
- interceptor obj
- obj |
Perform a deep comparison to check if two objects are equal. | _.isEqual = (a, b) -> |
Check object identity. | return true if a is b |
Different types? | atype = typeof(a); btype = typeof(b)
- return false if atype isnt btype |
Basic equality test (watch out for coercions). | return true if `a == b` |
One is falsy and the other truthy. | return false if (!a and b) or (a and !b) |
One of them implements an | return a.isEqual(b) if a.isEqual |
Check dates' integer values. | return a.getTime() is b.getTime() if _.isDate(a) and _.isDate(b) |
Both are NaN? | return false if _.isNaN(a) and _.isNaN(b) |
Compare regular expressions. | if _.isRegExp(a) and _.isRegExp(b)
- return a.source is b.source and
- a.global is b.global and
- a.ignoreCase is b.ignoreCase and
- a.multiline is b.multiline |
If a is not an object by this point, we can't handle it. | return false if atype isnt 'object' |
Check for different array lengths before comparing contents. | return false if a.length and (a.length isnt b.length) |
Nothing else worked, deep compare the contents. | aKeys = _.keys(a); bKeys = _.keys(b) |
Different object sizes? | return false if aKeys.length isnt bKeys.length |
Recursive comparison of contents. | (return false) for all key, val of a when !(key of b) or !_.isEqual(val, b[key])
- true |
Is a given array or object empty? | _.isEmpty = (obj) ->
- return obj.length is 0 if _.isArray(obj) or _.isString(obj)
- (return false) for key of obj when hasOwnProperty.call(obj, key)
- true |
Is a given value a DOM element? | _.isElement = (obj) -> obj and obj.nodeType is 1 |
Is a given value an array? | _.isArray = nativeIsArray or (obj) -> !!(obj and obj.concat and obj.unshift and not obj.callee) |
Is a given variable an arguments object? | _.isArguments = (obj) -> obj and obj.callee |
Is the given value a function? | _.isFunction = (obj) -> !!(obj and obj.constructor and obj.call and obj.apply) |
Is the given value a string? | _.isString = (obj) -> !!(obj is '' or (obj and obj.charCodeAt and obj.substr)) |
Is a given value a number? | _.isNumber = (obj) -> (obj is +obj) or toString.call(obj) is '[object Number]' |
Is a given value a boolean? | _.isBoolean = (obj) -> obj is true or obj is false |
Is a given value a Date? | _.isDate = (obj) -> !!(obj and obj.getTimezoneOffset and obj.setUTCFullYear) |
Is the given value a regular expression? | _.isRegExp = (obj) -> !!(obj and obj.exec and (obj.ignoreCase or obj.ignoreCase is false)) |
Is the given value NaN -- this one is interesting. | _.isNaN = (obj) -> _.isNumber(obj) and window.isNaN(obj) |
Is a given value equal to null? | _.isNull = (obj) -> obj is null |
Is a given variable undefined? | _.isUndefined = (obj) -> typeof obj is 'undefined' |
Utility Functions | |
Run Underscore.js in noConflict mode, returning the | _.noConflict = ->
- root._ = previousUnderscore
- this |
Keep the identity function around for default iterators. | _.identity = (value) -> value |
Run a function | _.times = (n, iterator, context) ->
- iterator.call(context, i) for i in [0...n] |
Break out of the middle of an iteration. | _.breakLoop = -> throw breaker |
Add your own custom functions to the Underscore object, ensuring that -they're correctly added to the OOP wrapper as well. | _.mixin = (obj) ->
- for name in _.functions(obj)
- addToWrapper name, _[name] = obj[name] |
Generate a unique integer id (unique within the entire client session). -Useful for temporary DOM ids. | idCounter = 0
- _.uniqueId = (prefix) ->
- (prefix or '') + idCounter++ |
By default, Underscore uses ERB-style template delimiters, change the -following template settings to use alternative delimiters. | _.templateSettings = {
- start: '<%'
- end: '%>'
- interpolate: /<%=(.+?)%>/g
- } |
JavaScript templating a-la ERB, pilfered from John Resig's -Secrets of the JavaScript Ninja, page 83. -Single-quote fix from Rick Strahl. -With alterations for arbitrary delimiters, and to preserve whitespace. | _.template = (str, data) ->
- c = _.templateSettings
- endMatch = new RegExp("'(?=[^"+c.end.substr(0, 1)+"]*"+escapeRegExp(c.end)+")","g")
- fn = new Function 'obj',
- 'var p=[],print=function(){p.push.apply(p,arguments);};' +
- 'with(obj||{}){p.push(\'' +
- str.replace(/\r/g, '\\r')
- .replace(/\n/g, '\\n')
- .replace(/\t/g, '\\t')
- .replace(endMatch,"â")
- .split("'").join("\\'")
- .split("â").join("'")
- .replace(c.interpolate, "',$1,'")
- .split(c.start).join("');")
- .split(c.end).join("p.push('") +
- "');}return p.join('');"
- if data then fn(data) else fn |
Aliases | _.forEach = _.each
- _.foldl = _.inject = _.reduce
- _.foldr = _.reduceRight
- _.select = _.filter
- _.all = _.every
- _.any = _.some
- _.contains = _.include
- _.head = _.first
- _.tail = _.rest
- _.methods = _.functions |
Setup the OOP Wrapper | |
If Underscore is called as a function, it returns a wrapped object that -can be used OO-style. This wrapper holds altered versions of all the -underscore functions. Wrapped objects may be chained. | wrapper = (obj) ->
- this._wrapped = obj
- this |
Helper function to continue chaining intermediate results. | result = (obj, chain) ->
- if chain then _(obj).chain() else obj |
A method to easily add functions to the OOP wrapper. | addToWrapper = (name, func) ->
- wrapper.prototype[name] = ->
- args = _.toArray arguments
- unshift.call args, this._wrapped
- result func.apply(_, args), this._chain |
Add all of the Underscore functions to the wrapper object. | _.mixin _ |
Add all mutator Array functions to the wrapper. | _.each ['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], (name) ->
- method = Array.prototype[name]
- wrapper.prototype[name] = ->
- method.apply(this._wrapped, arguments)
- result(this._wrapped, this._chain) |
Add all accessor Array functions to the wrapper. | _.each ['concat', 'join', 'slice'], (name) ->
- method = Array.prototype[name]
- wrapper.prototype[name] = ->
- result(method.apply(this._wrapped, arguments), this._chain) |
Start chaining a wrapped Underscore object. | wrapper::chain = ->
- this._chain = true
- this |
Extracts the result from a wrapped and chained object. | wrapper::value = -> this._wrapped
-
- |
- CoffeeScript is a little language that compiles into JavaScript. Think - of it as JavaScript's less ostentatious kid brother — the same genes, - roughly the same height, but a different sense of style. Apart from a handful of - bonus goodies, statements in CoffeeScript correspond one-to-one with their - equivalent in JavaScript, it's just another way of saying it. -
- -- Disclaimer: - CoffeeScript is just for fun. Until it reaches 1.0, there are no guarantees - that the syntax won't change between versions. That said, - it compiles into clean JavaScript (the good parts) that can use existing - JavaScript libraries seamlessly, and passes through - JSLint without warnings. The compiled - output is pretty-printed and quite readable. -
- -- Latest Version: - 0.9.4 -
- -CoffeeScript on the left, compiled JavaScript output on the right.
- - <%= code_for('overview', 'cubes') %> - -- For a longer CoffeeScript example, check out - Underscore.coffee, a port - of the Underscore.js - library of helper functions. Underscore.coffee can pass the entire Underscore.js - test suite. The CoffeeScript version is faster than the original for a number - of methods (in general, due to the speed of CoffeeScript's array comprehensions), and - after being minified and gzipped, is only 241 bytes larger than the original - JavaScript version. - Additional examples are included in the source repository, inside the - examples folder. -
- -- The CoffeeScript compiler is written in pure CoffeeScript, using a - small DSL - on top of the Jison parser generator, and is available - as a Node.js utility. The core compiler however, - does not depend on Node, and can be run in other server-side-JavaScript environments, - or in the browser (see "Try CoffeeScript", above). -
- -- To install, first make sure you have a working copy of the latest tagged version of - Node.js, currently 0.1.102 or higher. - Then clone the CoffeeScript - source repository - from GitHub, or download the latest - release: 0.9.4. - To install the CoffeeScript compiler system-wide - under /usr/local, open the directory and run: -
- --sudo bin/cake install- -
- Alternatively, if you already have the - Node Package Manager installed, - you can use that to grab the latest CoffeeScript: -
- --sudo npm install coffee-script- -
- Both of these provide the coffee command, which will execute CoffeeScripts - under Node.js by default, but is also used to compile CoffeeScript - .coffee files into JavaScript, or to run an an interactive REPL. - When compiling to JavaScript, coffee writes the output - as .js files in the same directory by default, but output - can be customized with the following options: -
- --c, --compile |
- - Compile a .coffee script into a .js JavaScript file - of the same name. - | -
-i, --interactive |
- - Launch an interactive CoffeeScript session to try short snippets. - More pleasant if wrapped with - rlwrap. - | -
-o, --output [DIR] |
- - Write out all compiled JavaScript files into the specified directory. - Use in conjunction with --compile or --watch. - | -
-w, --watch |
- - Watch the modification times of the coffee-scripts, recompiling as - soon as a change occurs. - | -
-p, --print |
- - Instead of writing out the JavaScript as a file, print it - directly to stdout. - | -
-l, --lint |
-
- If the jsl
- (JavaScript Lint)
- command is installed, use it
- to check the compilation of a CoffeeScript file. (Handy in
- conjunction with --watch) - |
-
-s, --stdio |
-
- Pipe in CoffeeScript to STDIN and get back JavaScript over STDOUT.
- Good for use with processes written in other languages. An example: - cat src/cake.coffee | coffee -sc - |
-
-e, --eval |
-
- Compile and print a little snippet of CoffeeScript directly from the
- command line. For example: coffee -e "puts num for num in [10..1]" - |
-
-r, --require |
- - Load a library before compiling or executing your script. Can be used - to hook in to the compiler (to add Growl notifications, for example). - | -
-b, --bare |
- - Compile the JavaScript without the top-level function safety wrapper. - (Used for CoffeeScript as a Node.js module.) - | -
-t, --tokens |
- - Instead of parsing the CoffeeScript, just lex it, and print out the - token stream: [IDENTIFIER square] [ASSIGN =] [PARAM_START (] ... - | -
-n, --nodes |
-
- Instead of compiling the CoffeeScript, just lex and parse it, and print
- out the parse tree:
--Expressions - Assign - Value "square" - Code "x" - Op * - Value "x" - Value "x"- |
-
- Examples: -
- --coffee -c path/to/script.coffee -coffee --interactive -coffee --watch --lint experimental.coffee -coffee --print app/scripts/*.coffee > concatenation.js- -
- - This reference is structured so that it can be read from top to bottom, - if you like. Later sections use ideas and syntax previously introduced. - Familiarity with JavaScript is assumed. - In all of the following examples, the source CoffeeScript is provided on - the left, and the direct compilation into JavaScript is on the right. - -
- -- - Many of the examples can be run (where it makes sense) by pressing the "run" - button towards the bottom right. You can also paste examples into - "Try CoffeeScript" in the toolbar, and play with them from there. - -
- - Significant Whitespace - CoffeeScript uses Python-style significant whitespace: You don't need to - use semicolons ; to terminate expressions, ending - the line will do just as well. Semicolons can still be used to fit - multiple expressions onto a single line. Instead of using curly braces - { } to delimit blocks of code (like functions, - if-statements, - switch, and try/catch), - use indentation. -
- -
- You don't need to use parentheses to invoke a function if you're passing
- arguments:
print "coffee". Implicit parentheses wrap forwards
- to the end of the line, or block expression.
-
- You can use newlines to break up your expression into smaller pieces, - as long as CoffeeScript can determine that the line hasn't finished yet, - because it ends with an operator or a dot ... seen most commonly - in jQuery-chaining style JavaScript. -
- -- - Functions and Invocation - Functions are defined by a list of parameters, an arrow, and the - function body. The empty function looks like this: -> -
- <%= code_for('functions', 'cube(5)') %> - -- - Objects and Arrays - Object and Array literals look very similar to their JavaScript cousins. - When you spread out each property on a separate line, the commas are - optional. Implicit objects may be created with indentation instead of - brackets, winding up looking quite similar to YAML. -
- <%= code_for('objects_and_arrays', 'song.join(",")') %> -- In JavaScript, you can't use reserved words, like class, as properties - of an object, without quoting them as strings. CoffeeScript notices and quotes - them for you, so you don't have to worry about it (say, when using jQuery). -
- <%= code_for('objects_reserved') %> - -- - Lexical Scoping and Variable Safety - The CoffeeScript compiler takes care to make sure that all of your variables - are properly declared within lexical scope — you never need to write - var yourself. -
- <%= code_for('scope', 'inner') %> -- Notice how all of the variable declarations have been pushed up to - the top of the closest scope, the first time they appear. - outer is not redeclared within the inner function, because it's - already in scope; inner within the function, on the other hand, - should not be able to change the value of the external variable of the same name, and - therefore has a declaration of its own. -
-- This behavior is effectively identical to Ruby's scope for local variables. - Because you don't have direct access to the var keyword, - it's impossible to shadow an outer variable on purpose, you may only refer - to it. So be careful that you're not reusing the name of an external - variable accidentally, if you're writing a deeply nested function. -
-- Although suppressed within this documentation for clarity, all - CoffeeScript output is wrapped in an anonymous function: - (function(){ ... })(); This safety wrapper, combined with the - automatic generation of the var keyword, make it exceedingly difficult - to pollute the global namespace by accident. -
-- If you'd like to create top-level variables for other scripts to use, - attach them as properties on window, or on the exports - object in CommonJS. The existential operator (covered below), gives you a - reliable way to figure out where to add them, if you're targeting both - CommonJS and the browser: root = exports ? this -
- -- - If, Else, Unless, and Conditional Assignment - If/else statements can be written without the use of parentheses and - curly brackets. As with functions and other block expressions, - multi-line conditionals are delimited by indentation. There's also a handy - postfix form, with the if or unless at the end. -
-- CoffeeScript can compile if statements into JavaScript expressions, - using the ternary operator when possible, and closure wrapping otherwise. There - is no explicit ternary statement in CoffeeScript — you simply use - a regular if statement inline. -
- <%= code_for('conditionals') %> - -- - Aliases - Because the == operator frequently causes undesirable coercion, - is intransitive, and has a different meaning than in other languages, - CoffeeScript compiles == into ===, and != into - !==. - In addition, is compiles into ===, - and isnt into !==. -
-- You can use not as an alias for !. -
-- For logic, and compiles to &&, and or - into ||. -
-- Instead of a newline or semicolon, then can be used to separate - conditions from expressions, in while, - if/else, and switch/when statements. -
-- As in YAML, on and yes - are the same as boolean true, while off and no are boolean false. -
-- For single-line statements, unless can be used as the inverse of if. -
-- As a shortcut for this.property, you can use @property. -
-- You can use in to test for array presence, and of to - test for JavaScript object-key presence. -
- <%= code_for('aliases') %> - -- - Splats... - The JavaScript arguments object is a useful way to work with - functions that accept variable numbers of arguments. CoffeeScript provides - splats ..., both for function definition as well as invocation, - making variable numbers of arguments a little bit more palatable. -
- <%= code_for('splats', true) %> - -- - While, Until, and Loop - The only low-level loop that CoffeeScript provides is the while loop. The - main difference from JavaScript is that the while loop can be used - as an expression, returning an array containing the result of each iteration - through the loop. -
- <%= code_for('while', 'lyrics.join("\n")') %> -- For readability, the until keyword is equivalent to while not, - and the loop keyword is equivalent to while true. - Other JavaScript loops, such as for loops and do-while loops - can be mimicked by variations on loop, but the hope is that you - won't need to do that with CoffeeScript, either because you're using - each (forEach) style iterators, or... -
- -- - Comprehensions (Arrays, Objects, and Ranges) - For your looping needs, CoffeeScript provides array comprehensions - similar to Python's. They replace (and compile into) for loops, with - optional guard clauses and the value of the current array index. - Unlike for loops, array comprehensions are expressions, and can be returned - and assigned. They should be able to handle most places where you otherwise - would use a loop, each/forEach, map, or select/filter. -
- <%= code_for('array_comprehensions') %> -- If you know the start and end of your loop, or would like to step through - in fixed-size increments, you can use a range to specify the start and - end of your comprehension. -
- <%= code_for('range_comprehensions', 'countdown') %> -- Comprehensions can also be used to iterate over the keys and values in - an object. Use of to signal comprehension over the properties of - an object instead of the values in an array. -
- <%= code_for('object_comprehensions', 'ages.join(", ")') %> -
- By default, object comprehensions are safe, and use a hasOwnProperty
- check to make sure that you're dealing with properties on the current
- object. If you'd like the regular JavaScript
for (key in obj) ...
- loop, for speed or for another reason, you can use
- for all key, value of object in CoffeeScript.
-
- - Array Slicing and Splicing with Ranges - CoffeeScript borrows Ruby's - range syntax - for extracting slices of arrays. With two dots (3..5), the range - is inclusive: the first argument is the index of the first element in - the slice, and the second is the index of the last one. Three dots signify - a range that excludes the end. -
- <%= code_for('slices', 'copy') %> -- The same syntax can be used with assignment to replace a segment of an - array with new values (to splice it). -
- <%= code_for('splices', 'numbers') %> - -- - Everything is an Expression (at least, as much as possible) - You might have noticed how even though we don't add return statements - to CoffeeScript functions, they nonetheless return their final value. - The CoffeeScript compiler tries to make sure that all statements in the - language can be used as expressions. Watch how the return gets - pushed down into each possible branch of execution, in the function - below. -
- <%= code_for('expressions', 'eldest') %> -- Even though functions will always return their final value, it's both possible - and encouraged to return early from a function body writing out the explicit - return (return value), when you know that you're done. -
-- Because variable declarations occur at the top of scope, assignment can - be used within expressions, even for variables that haven't been seen before: -
- <%= code_for('expressions_assignment', 'six') %> -- Things that would otherwise be statements in JavaScript, when used - as part of an expression in CoffeeScript, are converted into expressions - by wrapping them in a closure. This lets you do useful things, like assign - the result of a comprehension to a variable: -
- <%= code_for('expressions_comprehension', 'globals') %> -- As well as silly things, like passing a try/catch statement directly - into a function call: -
- <%= code_for('expressions_try', true) %> -- There are a handful of statements in JavaScript that can't be meaningfully - converted into expressions, namely break, continue, - and return. If you make use of them within a block of code, - CoffeeScript won't try to perform the conversion. -
- -- - The Existential Operator - It's a little difficult to check for the existence of a variable in - JavaScript. if (variable) ... comes close, but fails for zero, - the empty string, and false. CoffeeScript's existential operator ? returns true unless - a variable is null or undefined, which makes it analogous - to Ruby's nil? -
-- It can also be used for safer conditional assignment than ||= - provides, for cases where you may be handling numbers or strings. -
- <%= code_for('existence', 'speed') %> -- The accessor variant of the existential operator ?. can be used to soak - up null references in a chain of properties. Use it instead - of the dot accessor . in cases where the base value may be null - or undefined. If all of the properties exist then you'll get the expected - result, if the chain is broken, undefined is returned instead of - the TypeError that would be raised otherwise. -
- <%= code_for('soaks') %> -- Soaking up nulls is similar to Ruby's - andand gem, and to the - safe navigation operator - in Groovy. -
- -- - Classes, Inheritance, and Super - JavaScript's prototypal inheritance has always been a bit of a - brain-bender, with a whole family tree of libraries that provide a cleaner - syntax for classical inheritance on top of JavaScript's prototypes: - Base2, - Prototype.js, - JS.Class, etc. - The libraries provide syntactic sugar, but the built-in inheritance would - be completely usable if it weren't for a couple of small exceptions: - it's awkward to call super (the prototype object's - implementation of the current function), and it's awkward to correctly - set the prototype chain. -
-- Instead of repetitively attaching functions to a prototype, CoffeeScript - provides a basic class structure that allows you to name your class, - set the superclass, assign prototypal properties, and define the constructor, - in a single assignable expression. -
- <%= code_for('classes', true) %> -- If structuring your prototypes classically isn't your cup of tea, CoffeeScript - provides a couple of lower-level conveniences. The extends operator - helps with proper prototype setup, :: gives you - quick access to an object's prototype, and super() - is converted into a call against the immediate ancestor's method of the same name. -
- <%= code_for('prototypes', '"one_two".dasherize()') %> -
- Finally, you may assign Class-level (static) properties within a class
- definition by using
@property: value
-
- - Pattern Matching (Destructuring Assignment) - To make extracting values from complex arrays and objects more convenient, - CoffeeScript implements ECMAScript Harmony's proposed - destructuring assignment - syntax. When you assign an array or object literal to a value, CoffeeScript - breaks up and matches both sides against each other, assigning the values - on the right to the variables on the left. In the simplest case, it can be - used for parallel assignment: -
- <%= code_for('parallel_assignment', 'theBait') %> -- But it's also helpful for dealing with functions that return multiple - values. -
- <%= code_for('multiple_return_values', 'forecast') %> -- Pattern matching can be used with any depth of array and object nesting, - to help pull out deeply nested properties. -
- <%= code_for('object_extraction', 'name + " â " + street') %> -- Pattern matching can even be combined with splats. -
- <%= code_for('patterns_and_splats', 'contents.join("")') %> - -- - Function binding - In JavaScript, the this keyword is dynamically scoped to mean the - object that the current function is attached to. If you pass a function as - as callback, or attach it to a different object, the original value of this - will be lost. If you're not familiar with this behavior, - this Digital Web article - gives a good overview of the quirks. -
-- The fat arrow => can be used to both define a function, and to bind - it to the current value of this, right on the spot. This is helpful - when using callback-based libraries like Prototype or jQuery, for creating - iterator functions to pass to each, or event-handler functions - to use with bind. Functions created with the fat arrow are able to access - properties of the this where they're defined. -
- <%= code_for('fat_arrow') %> -- If we had used -> in the callback above, @customer would - have referred to the undefined "customer" property of the DOM element, - and trying to call purchase() on it would have raised an exception. -
- -- - Embedded JavaScript - Hopefully, you'll never need to use it, but if you ever need to intersperse - snippets of JavaScript within your CoffeeScript, you can - use backticks to pass it straight through. -
- <%= code_for('embedded', 'hi()') %> - -- - Switch/When/Else - Switch statements in JavaScript are a bit awkward. You need to - remember to break at the end of every case statement to - avoid accidentally falling through to the default case. - CoffeeScript prevents accidental fall-through, and can convert the switch - into a returnable, assignable expression. The format is: switch condition, - when clauses, else the default case. -
-- As in Ruby, switch statements in CoffeeScript can take multiple - values for each when clause. If any of the values match, the clause - runs. -
- <%= code_for('switch') %> - -- - Try/Catch/Finally - Try/catch statements are just about the same as JavaScript (although - they work as expressions). -
- <%= code_for('try') %> - -- - Chained Comparisons - CoffeeScript borrows - chained comparisons - from Python — making it easy to test if a value falls within a - certain range. -
- <%= code_for('comparisons', 'healthy') %> - -- - String and RegExp Interpolation - Ruby-style string interpolation is included in CoffeeScript. Double-quoted - strings allow for interpolated values, while single-quoted strings are literal. -
- <%= code_for('interpolation', 'quote') %> -
- And arbitrary expressions can be interpolated by using brackets #{ ... }
- Interpolation works the same way within regular expressions.
-
- - Multiline Strings, Heredocs, and Block Comments - Multiline strings are allowed in CoffeeScript. -
- <%= code_for('strings', 'mobyDick') %> -- Heredocs can be used to hold formatted or indentation-sensitive text - (or, if you just don't feel like escaping quotes and apostrophes). The - indentation level that begins the heredoc is maintained throughout, so - you can keep it all aligned with the body of your code. -
- <%= code_for('heredocs') %> -- Double-quoted heredocs, like double-quoted strings, allow interpolation. -
-- Sometimes you'd like to pass a block comment through to the generated - JavaScript. For example, when you need to embed a licensing header at - the top of a file. Block comments, which mirror the synax for heredocs, - are preserved in the generated code. -
- <%= code_for('block_comment') %> - -- CoffeeScript includes a simple build system similar to - Make and - Rake. Naturally, - it's called Cake, and is used for the build and test tasks for the CoffeeScript - language itself. Tasks are defined in a file named Cakefile, and - can be invoked by running cake taskname from within the directory. - To print a list of all the tasks and options, just run cake. -
- -- Task definitions are written in CoffeeScript, so you can put arbitrary code - in your Cakefile. Define a task with a name, a long description, and the - function to invoke when the task is run. If your task takes a command-line - option, you can define the option with short and long flags, and it will - be made available in the options object. Here's a task that uses - the Node.js API to rebuild CoffeeScript's parser: -
- <%= code_for('cake_tasks') %> -- If you need to invoke one task before another — for example, running - build before test, you can use the invoke function: - invoke 'build' -
- -- While it's not recommended for serious use, CoffeeScripts may be included - directly within the browser using <script type="text/coffeescript"> - tags. The source includes a compressed and minified version of the compiler - (Download current version here, 43k when gzipped) - as extras/coffee-script.js. Include this file on a page with - inline CoffeeScript tags, and it will compile and evaluate them in order. -
- -- In fact, the little bit of glue script that runs "Try CoffeeScript" above, - as well as jQuery for the menu, is implemented in just this way. - View source and look at the bottom of the page to see the example. - Including the script also gives you access to CoffeeScript.compile() - so you can pop open Firebug and try compiling some strings. -
- -- The usual caveats about CoffeeScript apply — your inline scripts will - run within a closure wrapper, so if you want to expose global variables or - functions, attach them to the window object. -
- -- Quick help and advice can usually be found in the CoffeeScript IRC room. - Join #coffeescript on irc.freenode.net, or click the - button below to open a webchat session on this page. -
- -- -
- -- 0.9.4 - CoffeeScript now uses appropriately-named temporary variables, and recycles - their references after use. Added require.extensions support for - Node.js 0.3. Loading CoffeeScript in the browser now adds just a - single CoffeeScript object to global scope. - Fixes for implicit object and block comment edge cases. -
- -- 0.9.3 - CoffeeScript switch statements now compile into JS switch - statements — they previously compiled into if/else chains - for JavaScript 1.3 compatibility. - Soaking a function invocation is now supported. Users of the RubyMine - editor should now be able to use --watch mode. -
- -- 0.9.2 - Specifying the start and end of a range literal is now optional, eg. array[3..]. - You can now say a not instanceof b. - Fixed important bugs with nested significant and non-significant indentation (Issue #637). - Added a --require flag that allows you to hook into the coffee command. - Added a custom jsl.conf file for our preferred JavaScriptLint setup. - Sped up Jison grammar compilation time by flattening rules for operations. - Block comments can now be used with JavaScript-minifier-friendly syntax. - Added JavaScript's compound assignment bitwise operators. Bugfixes to - implicit object literals with leading number and string keys, as the subject - of implicit calls, and as part of compound assignment. -
- -- 0.9.1 - Bugfix release for 0.9.1. Greatly improves the handling of mixed - implicit objects, implicit function calls, and implicit indentation. - String and regex interpolation is now strictly #{ ... } (Ruby style). - The compiler now takes a --require flag, which specifies scripts - to run before compilation. -
- -- 0.9.0 - The CoffeeScript 0.9 series is considered to be a release candidate - for 1.0; let's give her a shakedown cruise. 0.9.0 introduces a massive - backwards-incompatible change: Assignment now uses =, and object - literals use :, as in JavaScript. This allows us to have implicit - object literals, and YAML-style object definitions. Half assignments are - removed, in favor of +=, or=, and friends. - Interpolation now uses a hash mark # instead of the dollar sign - $ — because dollar signs may be part of a valid JS identifier. - Downwards range comprehensions are now safe again, and are optimized to - straight for loops when created with integer endpoints. - A fast, unguarded form of object comprehension was added: - for all key, value of object. Mentioning the super keyword - with no arguments now forwards all arguments passed to the function, - as in Ruby. If you extend class B from parent class A, if - A has an extended method defined, it will be called, passing in B — - this enables static inheritance, among other things. Cleaner output for - functions bound with the fat arrow. @variables can now be used - in parameter lists, with the parameter being automatically set as a property - on the object — useful in constructors and setter functions. - Constructor functions can now take splats. -
- -- 0.7.2 - Quick bugfix (right after 0.7.1) for a problem that prevented coffee - command-line options from being parsed in some circumstances. -
- -- 0.7.1 - Block-style comments are now passed through and printed as JavaScript block - comments -- making them useful for licenses and copyright headers. Better - support for running coffee scripts standalone via hashbangs. - Improved syntax errors for tokens that are not in the grammar. -
- -- 0.7.0 - Official CoffeeScript variable style is now camelCase, as in JavaScript. - Reserved words are now allowed as object keys, and will be quoted for you. - Range comprehensions now generate cleaner code, but you have to specify by -1 - if you'd like to iterate downward. Reporting of syntax errors is greatly - improved from the previous release. Running coffee with no arguments - now launches the REPL, with Readline support. The <- bind operator - has been removed from CoffeeScript. The loop keyword was added, - which is equivalent to a while true loop. Comprehensions that contain - closures will now close over their variables, like the semantics of a forEach. - You can now use bound function in class definitions (bound to the instance). - For consistency, a in b is now an array presence check, and a of b - is an object-key check. Comments are no longer passed through to the generated - JavaScript. -
- -- 0.6.2 - The coffee command will now preserve directory structure when - compiling a directory full of scripts. Fixed two omissions that were preventing - the CoffeeScript compiler from running live within Internet Explorer. - There's now a syntax for block comments, similar in spirit to CoffeeScript's heredocs. - ECMA Harmony DRY-style pattern matching is now supported, where the name - of the property is the same as the name of the value: {name, length}: func. - Pattern matching is now allowed within comprehension variables. unless - is now allowed in block form. until loops were added, as the inverse - of while loops. switch statements are now allowed without - switch object clauses. Compatible - with Node.js v0.1.95. -
- -- 0.6.1 - Upgraded CoffeeScript for compatibility with the new Node.js v0.1.90 - series. -
- -- 0.6.0 - Trailing commas are now allowed, a-la Python. Static - properties may be assigned directly within class definitions, - using @property notation. -
- -- 0.5.6 - Interpolation can now be used within regular expressions and heredocs, as well as - strings. Added the <- bind operator. - Allowing assignment to half-expressions instead of special ||=-style - operators. The arguments object is no longer automatically converted into - an array. After requiring coffee-script, Node.js can now directly - load .coffee files, thanks to registerExtension. Multiple - splats can now be used in function calls, arrays, and pattern matching. -
- -- 0.5.5 - String interpolation, contributed by - Stan Angeloff. - Since --run has been the default since 0.5.3, updating - --stdio and --eval to run by default, pass --compile - as well if you'd like to print the result. -
- -- 0.5.4 - Bugfix that corrects the Node.js global constants __filename and - __dirname. Tweaks for more flexible parsing of nested function - literals and improperly-indented comments. Updates for the latest Node.js API. -
- -- 0.5.3 - CoffeeScript now has a syntax for defining classes. Many of the core - components (Nodes, Lexer, Rewriter, Scope, Optparse) are using them. - Cakefiles can use optparse.coffee to define options for tasks. - --run is now the default flag for the coffee command, - use --compile to save JavaScripts. Bugfix for an ambiguity between - RegExp literals and chained divisions. -
- -
- 0.5.2
- Added a compressed version of the compiler for inclusion in web pages as
-
extras/coffee-script.js. It'll automatically run any script tags
- with type text/coffeescript for you. Added a --stdio option
- to the coffee command, for piped-in compiles.
-
- 0.5.1 - Improvements to null soaking with the existential operator, including - soaks on indexed properties. Added conditions to while loops, - so you can use them as filters with when, in the same manner as - comprehensions. -
- -- 0.5.0 - CoffeeScript 0.5.0 is a major release, While there are no language changes, - the Ruby compiler has been removed in favor of a self-hosting - compiler written in pure CoffeeScript. -
- -
- 0.3.2
- @property is now a shorthand for this.property.
- Switched the default JavaScript engine from Narwhal to Node.js. Pass
- the --narwhal flag if you'd like to continue using it.
-
- 0.3.0
- CoffeeScript 0.3 includes major syntax changes:
-
- The function symbol was changed to
- ->, and the bound function symbol is now =>.
-
- Parameter lists in function definitions must now be wrapped in parentheses.
-
- Added property soaking, with the ?. operator.
-
- Made parentheses optional, when invoking functions with arguments.
-
- Removed the obsolete block literal syntax.
-
- 0.2.6 - Added Python-style chained comparisons, the conditional existence - operator ?=, and some examples from Beautiful Code. - Bugfixes relating to statement-to-expression conversion, arguments-to-array - conversion, and the TextMate syntax highlighter. -
- -- 0.2.5 - The conditions in switch statements can now take multiple values at once — - If any of them are true, the case will run. Added the long arrow ==>, - which defines and immediately binds a function to this. While loops can - now be used as expressions, in the same way that comprehensions can. Splats - can be used within pattern matches to soak up the rest of an array. -
- -- 0.2.4 - Added ECMAScript Harmony style destructuring assignment, for dealing with - extracting values from nested arrays and objects. Added indentation-sensitive - heredocs for nicely formatted strings or chunks of code. -
- -- 0.2.3 - Axed the unsatisfactory ino keyword, replacing it with of for - object comprehensions. They now look like: for prop, value of object. -
- -
- 0.2.2
- When performing a comprehension over an object, use ino, instead
- of in, which helps us generate smaller, more efficient code at
- compile time.
-
- Added :: as a shorthand for saying .prototype.
-
- The "splat" symbol has been changed from a prefix asterisk *, to
- a postfix ellipsis ...
-
- Added JavaScript's in operator,
- empty return statements, and empty while loops.
-
- Constructor functions that start with capital letters now include a
- safety check to make sure that the new instance of the object is returned.
-
- The extends keyword now functions identically to goog.inherits
- in Google's Closure Library.
-
- 0.2.1 - Arguments objects are now converted into real arrays when referenced. -
- -- 0.2.0 - Major release. Significant whitespace. Better statement-to-expression - conversion. Splats. Splice literals. Object comprehensions. Blocks. - The existential operator. Many thanks to all the folks who posted issues, - with special thanks to - Liam O'Connor-Davis for whitespace - and expression help. -
- -- 0.1.6 - Bugfix for running coffee --interactive and --run - from outside of the CoffeeScript directory. Bugfix for nested - function/if-statements. -
- -- 0.1.5 - Array slice literals and array comprehensions can now both take Ruby-style - ranges to specify the start and end. JavaScript variable declaration is - now pushed up to the top of the scope, making all assignment statements into - expressions. You can use \ to escape newlines. - The coffee-script command is now called coffee. -
- -- 0.1.4 - The official CoffeeScript extension is now .coffee instead of - .cs, which properly belongs to - C#. - Due to popular demand, you can now also use = to assign. Unlike - JavaScript, = can also be used within object literals, interchangeably - with :. Made a grammatical fix for chained function calls - like func(1)(2)(3)(4). Inheritance and super no longer use - __proto__, so they should be IE-compatible now. -
- -- 0.1.3 - The coffee command now includes --interactive, - which launches an interactive CoffeeScript session, and --run, - which directly compiles and executes a script. Both options depend on a - working installation of Narwhal. - The aint keyword has been replaced by isnt, which goes - together a little smoother with is. - Quoted strings are now allowed as identifiers within object literals: eg. - {"5+5": 10}. - All assignment operators now use a colon: +:, -:, - *:, etc. -
- -- 0.1.2 - Fixed a bug with calling super() through more than one level of - inheritance, with the re-addition of the extends keyword. - Added experimental Narwhal - support (as a Tusk package), contributed by - Tom Robinson, including - bin/cs as a CoffeeScript REPL and interpreter. - New --no-wrap option to suppress the safety function - wrapper. -
- -- 0.1.1 - Added instanceof and typeof as operators. -
- -- 0.1.0 - Initial CoffeeScript release. -
- -- CoffeeScript is a little language that compiles into JavaScript. Think - of it as JavaScript's less ostentatious kid brother — the same genes, - roughly the same height, but a different sense of style. Apart from a handful of - bonus goodies, statements in CoffeeScript correspond one-to-one with their - equivalent in JavaScript, it's just another way of saying it. -
- -- Disclaimer: - CoffeeScript is just for fun. Until it reaches 1.0, there are no guarantees - that the syntax won't change between versions. That said, - it compiles into clean JavaScript (the good parts) that can use existing - JavaScript libraries seamlessly, and passes through - JSLint without warnings. The compiled - output is pretty-printed and quite readable. -
- -- Latest Version: - 0.9.4 -
- -CoffeeScript on the left, compiled JavaScript output on the right.
- -# Assignment: -number = 42 -opposite = true - -# Conditions: -number = -42 if opposite - -# Functions: -square = (x) -> x * x - -# Arrays: -list = [1, 2, 3, 4, 5] - -# Objects: -math = - root: Math.sqrt - square: square - cube: (x) -> x * square x - -# Splats: -race = (winner, runners...) -> - print winner, runners - -# Existence: -alert "I knew it!" if elvis? - -# Array comprehensions: -cubes = math.cube num for num in list -
var _i, _len, _result, cubes, list, math, num, number, opposite, race, square; -var __slice = Array.prototype.slice; -number = 42; -opposite = true; -if (opposite) { - number = -42; -} -square = function(x) { - return x * x; -}; -list = [1, 2, 3, 4, 5]; -math = { - root: Math.sqrt, - square: square, - cube: function(x) { - return x * square(x); - } -}; -race = function(winner) { - var runners; - runners = __slice.call(arguments, 1); - return print(winner, runners); -}; -if (typeof elvis !== "undefined" && elvis !== null) { - alert("I knew it!"); -} -cubes = (function() { - _result = []; - for (_i = 0, _len = list.length; _i < _len; _i++) { - num = list[_i]; - _result.push(math.cube(num)); - } - return _result; -})(); -
- For a longer CoffeeScript example, check out - Underscore.coffee, a port - of the Underscore.js - library of helper functions. Underscore.coffee can pass the entire Underscore.js - test suite. The CoffeeScript version is faster than the original for a number - of methods (in general, due to the speed of CoffeeScript's array comprehensions), and - after being minified and gzipped, is only 241 bytes larger than the original - JavaScript version. - Additional examples are included in the source repository, inside the - examples folder. -
- -- The CoffeeScript compiler is written in pure CoffeeScript, using a - small DSL - on top of the Jison parser generator, and is available - as a Node.js utility. The core compiler however, - does not depend on Node, and can be run in other server-side-JavaScript environments, - or in the browser (see "Try CoffeeScript", above). -
- -- To install, first make sure you have a working copy of the latest tagged version of - Node.js, currently 0.1.102 or higher. - Then clone the CoffeeScript - source repository - from GitHub, or download the latest - release: 0.9.4. - To install the CoffeeScript compiler system-wide - under /usr/local, open the directory and run: -
- --sudo bin/cake install- -
- Alternatively, if you already have the - Node Package Manager installed, - you can use that to grab the latest CoffeeScript: -
- --sudo npm install coffee-script- -
- Both of these provide the coffee command, which will execute CoffeeScripts - under Node.js by default, but is also used to compile CoffeeScript - .coffee files into JavaScript, or to run an an interactive REPL. - When compiling to JavaScript, coffee writes the output - as .js files in the same directory by default, but output - can be customized with the following options: -
- --c, --compile |
- - Compile a .coffee script into a .js JavaScript file - of the same name. - | -
-i, --interactive |
- - Launch an interactive CoffeeScript session to try short snippets. - More pleasant if wrapped with - rlwrap. - | -
-o, --output [DIR] |
- - Write out all compiled JavaScript files into the specified directory. - Use in conjunction with --compile or --watch. - | -
-w, --watch |
- - Watch the modification times of the coffee-scripts, recompiling as - soon as a change occurs. - | -
-p, --print |
- - Instead of writing out the JavaScript as a file, print it - directly to stdout. - | -
-l, --lint |
-
- If the jsl
- (JavaScript Lint)
- command is installed, use it
- to check the compilation of a CoffeeScript file. (Handy in
- conjunction with --watch) - |
-
-s, --stdio |
-
- Pipe in CoffeeScript to STDIN and get back JavaScript over STDOUT.
- Good for use with processes written in other languages. An example: - cat src/cake.coffee | coffee -sc - |
-
-e, --eval |
-
- Compile and print a little snippet of CoffeeScript directly from the
- command line. For example: coffee -e "puts num for num in [10..1]" - |
-
-r, --require |
- - Load a library before compiling or executing your script. Can be used - to hook in to the compiler (to add Growl notifications, for example). - | -
-b, --bare |
- - Compile the JavaScript without the top-level function safety wrapper. - (Used for CoffeeScript as a Node.js module.) - | -
-t, --tokens |
- - Instead of parsing the CoffeeScript, just lex it, and print out the - token stream: [IDENTIFIER square] [ASSIGN =] [PARAM_START (] ... - | -
-n, --nodes |
-
- Instead of compiling the CoffeeScript, just lex and parse it, and print
- out the parse tree:
--Expressions - Assign - Value "square" - Code "x" - Op * - Value "x" - Value "x"- |
-
- Examples: -
- --coffee -c path/to/script.coffee -coffee --interactive -coffee --watch --lint experimental.coffee -coffee --print app/scripts/*.coffee > concatenation.js- -
- - This reference is structured so that it can be read from top to bottom, - if you like. Later sections use ideas and syntax previously introduced. - Familiarity with JavaScript is assumed. - In all of the following examples, the source CoffeeScript is provided on - the left, and the direct compilation into JavaScript is on the right. - -
- -- - Many of the examples can be run (where it makes sense) by pressing the "run" - button towards the bottom right. You can also paste examples into - "Try CoffeeScript" in the toolbar, and play with them from there. - -
- - Significant Whitespace - CoffeeScript uses Python-style significant whitespace: You don't need to - use semicolons ; to terminate expressions, ending - the line will do just as well. Semicolons can still be used to fit - multiple expressions onto a single line. Instead of using curly braces - { } to delimit blocks of code (like functions, - if-statements, - switch, and try/catch), - use indentation. -
- -
- You don't need to use parentheses to invoke a function if you're passing
- arguments:
print "coffee". Implicit parentheses wrap forwards
- to the end of the line, or block expression.
-
- You can use newlines to break up your expression into smaller pieces, - as long as CoffeeScript can determine that the line hasn't finished yet, - because it ends with an operator or a dot ... seen most commonly - in jQuery-chaining style JavaScript. -
- -- - Functions and Invocation - Functions are defined by a list of parameters, an arrow, and the - function body. The empty function looks like this: -> -
-square = (x) -> x * x -cube = (x) -> square(x) * x -
var cube, square; -square = function(x) { - return x * x; -}; -cube = function(x) { - return square(x) * x; -}; -
- - Objects and Arrays - Object and Array literals look very similar to their JavaScript cousins. - When you spread out each property on a separate line, the commas are - optional. Implicit objects may be created with indentation instead of - brackets, winding up looking quite similar to YAML. -
-song = ["do", "re", "mi", "fa", "so"] - -singers = {Jagger: "Rock", Elvis: "Roll"} - -matrix = [ - 1, 0, 1 - 0, 0, 1 - 1, 1, 0 -] - -kids = - brother: - name: "Max" - age: 11 - sister: - name: "Ida" - age: 9 -
var kids, matrix, singers, song; -song = ["do", "re", "mi", "fa", "so"]; -singers = { - Jagger: "Rock", - Elvis: "Roll" -}; -matrix = [1, 0, 1, 0, 0, 1, 1, 1, 0]; -kids = { - brother: { - name: "Max", - age: 11 - }, - sister: { - name: "Ida", - age: 9 - } -}; -
- In JavaScript, you can't use reserved words, like class, as properties - of an object, without quoting them as strings. CoffeeScript notices and quotes - them for you, so you don't have to worry about it (say, when using jQuery). -
-$('.account').css class: 'active' -
$('.account').css({ - "class": 'active' -}); -
- - Lexical Scoping and Variable Safety - The CoffeeScript compiler takes care to make sure that all of your variables - are properly declared within lexical scope — you never need to write - var yourself. -
-outer = 1 -changeNumbers = -> - inner = -1 - outer = 10 -inner = changeNumbers() -
var changeNumbers, inner, outer; -outer = 1; -changeNumbers = function() { - var inner; - inner = -1; - return (outer = 10); -}; -inner = changeNumbers(); -
- Notice how all of the variable declarations have been pushed up to - the top of the closest scope, the first time they appear. - outer is not redeclared within the inner function, because it's - already in scope; inner within the function, on the other hand, - should not be able to change the value of the external variable of the same name, and - therefore has a declaration of its own. -
-- This behavior is effectively identical to Ruby's scope for local variables. - Because you don't have direct access to the var keyword, - it's impossible to shadow an outer variable on purpose, you may only refer - to it. So be careful that you're not reusing the name of an external - variable accidentally, if you're writing a deeply nested function. -
-- Although suppressed within this documentation for clarity, all - CoffeeScript output is wrapped in an anonymous function: - (function(){ ... })(); This safety wrapper, combined with the - automatic generation of the var keyword, make it exceedingly difficult - to pollute the global namespace by accident. -
-- If you'd like to create top-level variables for other scripts to use, - attach them as properties on window, or on the exports - object in CommonJS. The existential operator (covered below), gives you a - reliable way to figure out where to add them, if you're targeting both - CommonJS and the browser: root = exports ? this -
- -- - If, Else, Unless, and Conditional Assignment - If/else statements can be written without the use of parentheses and - curly brackets. As with functions and other block expressions, - multi-line conditionals are delimited by indentation. There's also a handy - postfix form, with the if or unless at the end. -
-- CoffeeScript can compile if statements into JavaScript expressions, - using the ternary operator when possible, and closure wrapping otherwise. There - is no explicit ternary statement in CoffeeScript — you simply use - a regular if statement inline. -
-mood = greatlyImproved if singing - -if happy and knowsIt - clapsHands() - chaChaCha() -else - showIt() - -date = if friday then sue else jill - -options or= defaults -
var date, mood, options; -if (singing) { - mood = greatlyImproved; -} -if (happy && knowsIt) { - clapsHands(); - chaChaCha(); -} else { - showIt(); -} -date = friday ? sue : jill; -options || (options = defaults); -
- - Aliases - Because the == operator frequently causes undesirable coercion, - is intransitive, and has a different meaning than in other languages, - CoffeeScript compiles == into ===, and != into - !==. - In addition, is compiles into ===, - and isnt into !==. -
-- You can use not as an alias for !. -
-- For logic, and compiles to &&, and or - into ||. -
-- Instead of a newline or semicolon, then can be used to separate - conditions from expressions, in while, - if/else, and switch/when statements. -
-- As in YAML, on and yes - are the same as boolean true, while off and no are boolean false. -
-- For single-line statements, unless can be used as the inverse of if. -
-- As a shortcut for this.property, you can use @property. -
-- You can use in to test for array presence, and of to - test for JavaScript object-key presence. -
-launch() if ignition is on - -volume = 10 if band isnt SpinalTap - -letTheWildRumpusBegin() unless answer is no - -if car.speed < limit then accelerate() - -winner = yes if pick in [47, 92, 13] - -print inspect "My name is " + @name -
var volume, winner; -if (ignition === true) { - launch(); -} -if (band !== SpinalTap) { - volume = 10; -} -if (answer !== false) { - letTheWildRumpusBegin(); -} -if (car.speed < limit) { - accelerate(); -} -if ((47 === pick || 92 === pick || 13 === pick)) { - winner = true; -} -print(inspect("My name is " + this.name)); -
- - Splats... - The JavaScript arguments object is a useful way to work with - functions that accept variable numbers of arguments. CoffeeScript provides - splats ..., both for function definition as well as invocation, - making variable numbers of arguments a little bit more palatable. -
-gold = silver = rest = "unknown" - -awardMedals = (first, second, others...) -> - gold = first - silver = second - rest = others - -contenders = [ - "Michael Phelps" - "Liu Xiang" - "Yao Ming" - "Allyson Felix" - "Shawn Johnson" - "Roman Sebrle" - "Guo Jingjing" - "Tyson Gay" - "Asafa Powell" - "Usain Bolt" -] - -awardMedals contenders... - -alert "Gold: " + gold -alert "Silver: " + silver -alert "The Field: " + rest -
var awardMedals, contenders, gold, rest, silver; -var __slice = Array.prototype.slice; -gold = (silver = (rest = "unknown")); -awardMedals = function(first, second) { - var others; - others = __slice.call(arguments, 2); - gold = first; - silver = second; - return (rest = others); -}; -contenders = ["Michael Phelps", "Liu Xiang", "Yao Ming", "Allyson Felix", "Shawn Johnson", "Roman Sebrle", "Guo Jingjing", "Tyson Gay", "Asafa Powell", "Usain Bolt"]; -awardMedals.apply(awardMedals, contenders); -alert("Gold: " + gold); -alert("Silver: " + silver); -alert("The Field: " + rest); -
- - While, Until, and Loop - The only low-level loop that CoffeeScript provides is the while loop. The - main difference from JavaScript is that the while loop can be used - as an expression, returning an array containing the result of each iteration - through the loop. -
-# Econ 101 -if this.studyingEconomics - buy() while supply > demand - sell() until supply > demand - -# Nursery Rhyme -num = 6 -lyrics = while num -= 1 - num + " little monkeys, jumping on the bed. - One fell out and bumped his head." -
var _result, lyrics, num; -if (this.studyingEconomics) { - while (supply > demand) { - buy(); - } - while (!(supply > demand)) { - sell(); - } -} -num = 6; -lyrics = (function() { - _result = []; - while (num -= 1) { - _result.push(num + " little monkeys, jumping on the bed. One fell out and bumped his head."); - } - return _result; -})(); -
- For readability, the until keyword is equivalent to while not, - and the loop keyword is equivalent to while true. - Other JavaScript loops, such as for loops and do-while loops - can be mimicked by variations on loop, but the hope is that you - won't need to do that with CoffeeScript, either because you're using - each (forEach) style iterators, or... -
- -- - Comprehensions (Arrays, Objects, and Ranges) - For your looping needs, CoffeeScript provides array comprehensions - similar to Python's. They replace (and compile into) for loops, with - optional guard clauses and the value of the current array index. - Unlike for loops, array comprehensions are expressions, and can be returned - and assigned. They should be able to handle most places where you otherwise - would use a loop, each/forEach, map, or select/filter. -
-# Eat lunch. -lunch = eat food for food in ['toast', 'cheese', 'wine'] - -# Naive collision detection. -for roid, pos in asteroids - for roid2 in asteroids when roid isnt roid2 - roid.explode() if roid.overlaps roid2 -
var _i, _len, _len2, _ref, _result, food, lunch, pos, roid, roid2; -lunch = (function() { - _result = []; _ref = ['toast', 'cheese', 'wine']; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - food = _ref[_i]; - _result.push(eat(food)); - } - return _result; -})(); -for (pos = 0, _len = asteroids.length; pos < _len; pos++) { - roid = asteroids[pos]; - for (_i = 0, _len2 = asteroids.length; _i < _len2; _i++) { - roid2 = asteroids[_i]; - if (roid !== roid2) { - if (roid.overlaps(roid2)) { - roid.explode(); - } - } - } -} -
- If you know the start and end of your loop, or would like to step through - in fixed-size increments, you can use a range to specify the start and - end of your comprehension. -
-countdown = num for num in [10..1] - -deliverEggs = -> - for i in [0...eggs.length] by 12 - dozen = eggs[i...i+12] - deliver new eggCarton dozen -
var _result, countdown, deliverEggs, num; -countdown = (function() { - _result = []; - for (num = 10; num >= 1; num--) { - _result.push(num); - } - return _result; -})(); -deliverEggs = function() { - var _ref, _result2, dozen, i; - _result2 = []; _ref = eggs.length; - for (i = 0; (0 <= _ref ? i < _ref : i > _ref); i += 12) { - _result2.push((function() { - dozen = eggs.slice(i, i + 12); - return deliver(new eggCarton(dozen)); - })()); - } - return _result2; -}; -
- Comprehensions can also be used to iterate over the keys and values in - an object. Use of to signal comprehension over the properties of - an object instead of the values in an array. -
-yearsOld = max: 10, ida: 9, tim: 11 - -ages = for child, age of yearsOld - child + " is " + age -
var _result, age, ages, child, yearsOld; -var __hasProp = Object.prototype.hasOwnProperty; -yearsOld = { - max: 10, - ida: 9, - tim: 11 -}; -ages = (function() { - _result = []; - for (child in yearsOld) { - if (!__hasProp.call(yearsOld, child)) continue; - age = yearsOld[child]; - _result.push(child + " is " + age); - } - return _result; -})(); -
- By default, object comprehensions are safe, and use a hasOwnProperty
- check to make sure that you're dealing with properties on the current
- object. If you'd like the regular JavaScript
for (key in obj) ...
- loop, for speed or for another reason, you can use
- for all key, value of object in CoffeeScript.
-
- - Array Slicing and Splicing with Ranges - CoffeeScript borrows Ruby's - range syntax - for extracting slices of arrays. With two dots (3..5), the range - is inclusive: the first argument is the index of the first element in - the slice, and the second is the index of the last one. Three dots signify - a range that excludes the end. -
-numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - -threeToSix = numbers[3..6] - -copy = numbers[0...numbers.length] - -
var copy, numbers, threeToSix; -numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; -threeToSix = numbers.slice(3, 6 + 1); -copy = numbers.slice(0, numbers.length); -
- The same syntax can be used with assignment to replace a segment of an - array with new values (to splice it). -
-numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - -numbers[3..6] = [-3, -4, -5, -6] - - -
var _ref, numbers; -numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; -([].splice.apply(numbers, [3, 6 - 3 + 1].concat(_ref = [-3, -4, -5, -6])), _ref); -
- - Everything is an Expression (at least, as much as possible) - You might have noticed how even though we don't add return statements - to CoffeeScript functions, they nonetheless return their final value. - The CoffeeScript compiler tries to make sure that all statements in the - language can be used as expressions. Watch how the return gets - pushed down into each possible branch of execution, in the function - below. -
-grade = (student) -> - if student.excellentWork - "A+" - else if student.okayStuff - if student.triedHard then "B" else "B-" - else - "C" - -eldest = if 24 > 21 then "Liz" else "Ike" -
var eldest, grade; -grade = function(student) { - return student.excellentWork ? "A+" : (student.okayStuff ? (student.triedHard ? "B" : "B-") : "C"); -}; -eldest = 24 > 21 ? "Liz" : "Ike"; -
- Even though functions will always return their final value, it's both possible - and encouraged to return early from a function body writing out the explicit - return (return value), when you know that you're done. -
-- Because variable declarations occur at the top of scope, assignment can - be used within expressions, even for variables that haven't been seen before: -
-six = (one = 1) + (two = 2) + (three = 3) -
var one, six, three, two; -six = (one = 1) + (two = 2) + (three = 3); -
- Things that would otherwise be statements in JavaScript, when used - as part of an expression in CoffeeScript, are converted into expressions - by wrapping them in a closure. This lets you do useful things, like assign - the result of a comprehension to a variable: -
-# The first ten global properties. - -globals = (name for name of window)[0...10] -
var _result, globals, name; -var __hasProp = Object.prototype.hasOwnProperty; -globals = (function() { - _result = []; - for (name in window) { - if (!__hasProp.call(window, name)) continue; - _result.push(name); - } - return _result; -})().slice(0, 10); -
- As well as silly things, like passing a try/catch statement directly - into a function call: -
-alert( - try - nonexistent / undefined - catch error - "And the error is ... " + error -) -
alert((function() { - try { - return nonexistent / undefined; - } catch (error) { - return "And the error is ... " + error; - } -})()); -
- There are a handful of statements in JavaScript that can't be meaningfully - converted into expressions, namely break, continue, - and return. If you make use of them within a block of code, - CoffeeScript won't try to perform the conversion. -
- -- - The Existential Operator - It's a little difficult to check for the existence of a variable in - JavaScript. if (variable) ... comes close, but fails for zero, - the empty string, and false. CoffeeScript's existential operator ? returns true unless - a variable is null or undefined, which makes it analogous - to Ruby's nil? -
-- It can also be used for safer conditional assignment than ||= - provides, for cases where you may be handling numbers or strings. -
-solipsism = true if mind? and not world? - -speed ?= 140 - - - - - -
var solipsism, speed; -if ((typeof mind !== "undefined" && mind !== null) && !(typeof world !== "undefined" && world !== null)) { - solipsism = true; -} -(typeof speed !== "undefined" && speed !== null) ? speed : (speed = 140); -
- The accessor variant of the existential operator ?. can be used to soak - up null references in a chain of properties. Use it instead - of the dot accessor . in cases where the base value may be null - or undefined. If all of the properties exist then you'll get the expected - result, if the chain is broken, undefined is returned instead of - the TypeError that would be raised otherwise. -
-lottery.drawWinner()?.address?.zipcode -
var _ref, _ref2; -(((_ref = lottery.drawWinner()) != null) ? (((_ref2 = _ref.address) != null) ? _ref2.zipcode : undefined) : undefined); -
- Soaking up nulls is similar to Ruby's - andand gem, and to the - safe navigation operator - in Groovy. -
- -- - Classes, Inheritance, and Super - JavaScript's prototypal inheritance has always been a bit of a - brain-bender, with a whole family tree of libraries that provide a cleaner - syntax for classical inheritance on top of JavaScript's prototypes: - Base2, - Prototype.js, - JS.Class, etc. - The libraries provide syntactic sugar, but the built-in inheritance would - be completely usable if it weren't for a couple of small exceptions: - it's awkward to call super (the prototype object's - implementation of the current function), and it's awkward to correctly - set the prototype chain. -
-- Instead of repetitively attaching functions to a prototype, CoffeeScript - provides a basic class structure that allows you to name your class, - set the superclass, assign prototypal properties, and define the constructor, - in a single assignable expression. -
-class Animal - constructor: (@name) -> - - move: (meters) -> - alert @name + " moved " + meters + "m." - -class Snake extends Animal - move: -> - alert "Slithering..." - super 5 - -class Horse extends Animal - move: -> - alert "Galloping..." - super 45 - -sam = new Snake "Sammy the Python" -tom = new Horse "Tommy the Palomino" - -sam.move() -tom.move() - - - - -
var Animal, Horse, Snake, sam, tom; -var __extends = function(child, parent) { - var ctor = function() {}; - ctor.prototype = parent.prototype; - child.prototype = new ctor(); - child.prototype.constructor = child; - if (typeof parent.extended === "function") parent.extended(child); - child.__super__ = parent.prototype; -}; -Animal = (function() { - return function Animal(_arg) { - this.name = _arg; - return this; - }; -})(); -Animal.prototype.move = function(meters) { - return alert(this.name + " moved " + meters + "m."); -}; -Snake = (function() { - return function Snake() { - return Animal.apply(this, arguments); - }; -})(); -__extends(Snake, Animal); -Snake.prototype.move = function() { - alert("Slithering..."); - return Snake.__super__.move.call(this, 5); -}; -Horse = (function() { - return function Horse() { - return Animal.apply(this, arguments); - }; -})(); -__extends(Horse, Animal); -Horse.prototype.move = function() { - alert("Galloping..."); - return Horse.__super__.move.call(this, 45); -}; -sam = new Snake("Sammy the Python"); -tom = new Horse("Tommy the Palomino"); -sam.move(); -tom.move(); -
- If structuring your prototypes classically isn't your cup of tea, CoffeeScript - provides a couple of lower-level conveniences. The extends operator - helps with proper prototype setup, :: gives you - quick access to an object's prototype, and super() - is converted into a call against the immediate ancestor's method of the same name. -
-String::dasherize = -> - this.replace /_/g, "-" -
String.prototype.dasherize = function() { - return this.replace(/_/g, "-"); -}; -
- Finally, you may assign Class-level (static) properties within a class
- definition by using
@property: value
-
- - Pattern Matching (Destructuring Assignment) - To make extracting values from complex arrays and objects more convenient, - CoffeeScript implements ECMAScript Harmony's proposed - destructuring assignment - syntax. When you assign an array or object literal to a value, CoffeeScript - breaks up and matches both sides against each other, assigning the values - on the right to the variables on the left. In the simplest case, it can be - used for parallel assignment: -
-theBait = 1000 -theSwitch = 0 - -[theBait, theSwitch] = [theSwitch, theBait] -
var _ref, theBait, theSwitch; -theBait = 1000; -theSwitch = 0; -_ref = [theSwitch, theBait], theBait = _ref[0], theSwitch = _ref[1]; -
- But it's also helpful for dealing with functions that return multiple - values. -
-weatherReport = (location) -> - # Make an Ajax request to fetch the weather... - [location, 72, "Mostly Sunny"] - -[city, temp, forecast] = weatherReport "Berkeley, CA" -
var _ref, city, forecast, temp, weatherReport; -weatherReport = function(location) { - return [location, 72, "Mostly Sunny"]; -}; -_ref = weatherReport("Berkeley, CA"), city = _ref[0], temp = _ref[1], forecast = _ref[2]; -
- Pattern matching can be used with any depth of array and object nesting, - to help pull out deeply nested properties. -
-futurists = - sculptor: "Umberto Boccioni" - painter: "Vladimir Burliuk" - poet: - name: "F.T. Marinetti" - address: [ - "Via Roma 42R" - "Bellagio, Italy 22021" - ] - -{poet: {name, address: [street, city]}} = futurists -
var _ref, _ref2, city, futurists, name, street; -futurists = { - sculptor: "Umberto Boccioni", - painter: "Vladimir Burliuk", - poet: { - name: "F.T. Marinetti", - address: ["Via Roma 42R", "Bellagio, Italy 22021"] - } -}; -_ref = futurists.poet, name = _ref.name, _ref2 = _ref.address, street = _ref2[0], city = _ref2[1]; -
- Pattern matching can even be combined with splats. -
-tag = "<impossible>" - -[open, contents..., close] = tag.split("") - - - - -
var _ref, close, contents, open, tag; -var __slice = Array.prototype.slice; -tag = "<impossible>"; -_ref = tag.split(""), open = _ref[0], contents = __slice.call(_ref, 1, _ref.length - 1), close = _ref[_ref.length - 1]; -
- - Function binding - In JavaScript, the this keyword is dynamically scoped to mean the - object that the current function is attached to. If you pass a function as - as callback, or attach it to a different object, the original value of this - will be lost. If you're not familiar with this behavior, - this Digital Web article - gives a good overview of the quirks. -
-- The fat arrow => can be used to both define a function, and to bind - it to the current value of this, right on the spot. This is helpful - when using callback-based libraries like Prototype or jQuery, for creating - iterator functions to pass to each, or event-handler functions - to use with bind. Functions created with the fat arrow are able to access - properties of the this where they're defined. -
-Account = (customer, cart) -> - @customer = customer - @cart = cart - - $('.shopping_cart').bind 'click', (event) => - @customer.purchase @cart -
var Account; -var __bind = function(func, context) { - return function() { return func.apply(context, arguments); }; -}; -Account = function(customer, cart) { - this.customer = customer; - this.cart = cart; - return $('.shopping_cart').bind('click', __bind(function(event) { - return this.customer.purchase(this.cart); - }, this)); -}; -
- If we had used -> in the callback above, @customer would - have referred to the undefined "customer" property of the DOM element, - and trying to call purchase() on it would have raised an exception. -
- -- - Embedded JavaScript - Hopefully, you'll never need to use it, but if you ever need to intersperse - snippets of JavaScript within your CoffeeScript, you can - use backticks to pass it straight through. -
-hi = `function() { - return [document.title, "Hello JavaScript"].join(": "); -}` - - -
var hi; -hi = function() { - return [document.title, "Hello JavaScript"].join(": "); -}; -
- - Switch/When/Else - Switch statements in JavaScript are a bit awkward. You need to - remember to break at the end of every case statement to - avoid accidentally falling through to the default case. - CoffeeScript prevents accidental fall-through, and can convert the switch - into a returnable, assignable expression. The format is: switch condition, - when clauses, else the default case. -
-- As in Ruby, switch statements in CoffeeScript can take multiple - values for each when clause. If any of the values match, the clause - runs. -
-switch day - when "Mon" then go work - when "Tue" then go relax - when "Thu" then go iceFishing - when "Fri", "Sat" - if day is bingoDay - go bingo - go dancing - when "Sun" then go church - else go work -
switch (day) { - case "Mon": - go(work); - break; - case "Tue": - go(relax); - break; - case "Thu": - go(iceFishing); - break; - case "Fri": - case "Sat": - if (day === bingoDay) { - go(bingo); - go(dancing); - } - break; - case "Sun": - go(church); - break; - default: - go(work); -} -
- - Try/Catch/Finally - Try/catch statements are just about the same as JavaScript (although - they work as expressions). -
-try - allHellBreaksLoose() - catsAndDogsLivingTogether() -catch error - print error -finally - cleanUp() -
try { - allHellBreaksLoose(); - catsAndDogsLivingTogether(); -} catch (error) { - print(error); -} finally { - cleanUp(); -} -
- - Chained Comparisons - CoffeeScript borrows - chained comparisons - from Python — making it easy to test if a value falls within a - certain range. -
-cholesterol = 127 - -healthy = 200 > cholesterol > 60 - - -
var cholesterol, healthy; -cholesterol = 127; -healthy = (200 > cholesterol) && (cholesterol > 60); -
- - String and RegExp Interpolation - Ruby-style string interpolation is included in CoffeeScript. Double-quoted - strings allow for interpolated values, while single-quoted strings are literal. -
-author = "Wittgenstein" -quote = "A picture is a fact. -- #{author}" -
var author, quote; -author = "Wittgenstein"; -quote = ("A picture is a fact. -- " + author); -
- And arbitrary expressions can be interpolated by using brackets #{ ... }
- Interpolation works the same way within regular expressions.
-
sentence = "#{ 22 / 7 } is a decent approximation of Ï" - -sep = "[.\\/\\- ]" -dates = /\d+#{sep}\d+#{sep}\d+/g - - -
var dates, sentence, sep; -sentence = ("" + (22 / 7) + " is a decent approximation of Ï"); -sep = "[.\\/\\- ]"; -dates = /\d+#{sep}\d+#{sep}\d+/g; -
- - Multiline Strings, Heredocs, and Block Comments - Multiline strings are allowed in CoffeeScript. -
-mobyDick = "Call me Ishmael. Some years ago -- - never mind how long precisely -- having little - or no money in my purse, and nothing particular - to interest me on shore, I thought I would sail - about a little and see the watery part of the - world..." - - -
var mobyDick; -mobyDick = "Call me Ishmael. Some years ago -- never mind how long precisely -- having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world..."; -
- Heredocs can be used to hold formatted or indentation-sensitive text - (or, if you just don't feel like escaping quotes and apostrophes). The - indentation level that begins the heredoc is maintained throughout, so - you can keep it all aligned with the body of your code. -
-html = ''' - <strong> - cup of coffeescript - </strong> - ''' -
var html; -html = '<strong>\n cup of coffeescript\n</strong>'; -
- Double-quoted heredocs, like double-quoted strings, allow interpolation. -
-- Sometimes you'd like to pass a block comment through to the generated - JavaScript. For example, when you need to embed a licensing header at - the top of a file. Block comments, which mirror the syntax for heredocs, - are preserved in the generated code. -
-### -CoffeeScript Compiler v0.9.4 -Released under the MIT License -### -
/* -CoffeeScript Compiler v0.9.4 -Released under the MIT License -*/ -
- CoffeeScript includes a simple build system similar to - Make and - Rake. Naturally, - it's called Cake, and is used for the build and test tasks for the CoffeeScript - language itself. Tasks are defined in a file named Cakefile, and - can be invoked by running cake taskname from within the directory. - To print a list of all the tasks and options, just run cake. -
- -- Task definitions are written in CoffeeScript, so you can put arbitrary code - in your Cakefile. Define a task with a name, a long description, and the - function to invoke when the task is run. If your task takes a command-line - option, you can define the option with short and long flags, and it will - be made available in the options object. Here's a task that uses - the Node.js API to rebuild CoffeeScript's parser: -
-fs = require 'fs' - -option '-o', '--output [DIR]', 'directory for compiled code' - -task 'build:parser', 'rebuild the Jison parser', (options) -> - require 'jison' - code = require('./lib/grammar').parser.generate() - dir = options.output or 'lib' - fs.writeFile "#{dir}/parser.js", code -
var fs; -fs = require('fs'); -option('-o', '--output [DIR]', 'directory for compiled code'); -task('build:parser', 'rebuild the Jison parser', function(options) { - var code, dir; - require('jison'); - code = require('./lib/grammar').parser.generate(); - dir = options.output || 'lib'; - return fs.writeFile("" + dir + "/parser.js", code); -}); -
- If you need to invoke one task before another — for example, running - build before test, you can use the invoke function: - invoke 'build' -
- -- While it's not recommended for serious use, CoffeeScripts may be included - directly within the browser using <script type="text/coffeescript"> - tags. The source includes a compressed and minified version of the compiler - (Download current version here, 43k when gzipped) - as extras/coffee-script.js. Include this file on a page with - inline CoffeeScript tags, and it will compile and evaluate them in order. -
- -- In fact, the little bit of glue script that runs "Try CoffeeScript" above, - as well as jQuery for the menu, is implemented in just this way. - View source and look at the bottom of the page to see the example. - Including the script also gives you access to CoffeeScript.compile() - so you can pop open Firebug and try compiling some strings. -
- -- The usual caveats about CoffeeScript apply — your inline scripts will - run within a closure wrapper, so if you want to expose global variables or - functions, attach them to the window object. -
- -- Quick help and advice can usually be found in the CoffeeScript IRC room. - Join #coffeescript on irc.freenode.net, or click the - button below to open a webchat session on this page. -
- -- -
- -- 0.9.4 - CoffeeScript now uses appropriately-named temporary variables, and recycles - their references after use. Added require.extensions support for - Node.js 0.3. Loading CoffeeScript in the browser now adds just a - single CoffeeScript object to global scope. - Fixes for implicit object and block comment edge cases. -
- -- 0.9.3 - CoffeeScript switch statements now compile into JS switch - statements — they previously compiled into if/else chains - for JavaScript 1.3 compatibility. - Soaking a function invocation is now supported. Users of the RubyMine - editor should now be able to use --watch mode. -
- -- 0.9.2 - Specifying the start and end of a range literal is now optional, eg. array[3..]. - You can now say a not instanceof b. - Fixed important bugs with nested significant and non-significant indentation (Issue #637). - Added a --require flag that allows you to hook into the coffee command. - Added a custom jsl.conf file for our preferred JavaScriptLint setup. - Sped up Jison grammar compilation time by flattening rules for operations. - Block comments can now be used with JavaScript-minifier-friendly syntax. - Added JavaScript's compound assignment bitwise operators. Bugfixes to - implicit object literals with leading number and string keys, as the subject - of implicit calls, and as part of compound assignment. -
- -- 0.9.1 - Bugfix release for 0.9.1. Greatly improves the handling of mixed - implicit objects, implicit function calls, and implicit indentation. - String and regex interpolation is now strictly #{ ... } (Ruby style). - The compiler now takes a --require flag, which specifies scripts - to run before compilation. -
- -- 0.9.0 - The CoffeeScript 0.9 series is considered to be a release candidate - for 1.0; let's give her a shakedown cruise. 0.9.0 introduces a massive - backwards-incompatible change: Assignment now uses =, and object - literals use :, as in JavaScript. This allows us to have implicit - object literals, and YAML-style object definitions. Half assignments are - removed, in favor of +=, or=, and friends. - Interpolation now uses a hash mark # instead of the dollar sign - $ — because dollar signs may be part of a valid JS identifier. - Downwards range comprehensions are now safe again, and are optimized to - straight for loops when created with integer endpoints. - A fast, unguarded form of object comprehension was added: - for all key, value of object. Mentioning the super keyword - with no arguments now forwards all arguments passed to the function, - as in Ruby. If you extend class B from parent class A, if - A has an extended method defined, it will be called, passing in B — - this enables static inheritance, among other things. Cleaner output for - functions bound with the fat arrow. @variables can now be used - in parameter lists, with the parameter being automatically set as a property - on the object — useful in constructors and setter functions. - Constructor functions can now take splats. -
- -- 0.7.2 - Quick bugfix (right after 0.7.1) for a problem that prevented coffee - command-line options from being parsed in some circumstances. -
- -- 0.7.1 - Block-style comments are now passed through and printed as JavaScript block - comments -- making them useful for licenses and copyright headers. Better - support for running coffee scripts standalone via hashbangs. - Improved syntax errors for tokens that are not in the grammar. -
- -- 0.7.0 - Official CoffeeScript variable style is now camelCase, as in JavaScript. - Reserved words are now allowed as object keys, and will be quoted for you. - Range comprehensions now generate cleaner code, but you have to specify by -1 - if you'd like to iterate downward. Reporting of syntax errors is greatly - improved from the previous release. Running coffee with no arguments - now launches the REPL, with Readline support. The <- bind operator - has been removed from CoffeeScript. The loop keyword was added, - which is equivalent to a while true loop. Comprehensions that contain - closures will now close over their variables, like the semantics of a forEach. - You can now use bound function in class definitions (bound to the instance). - For consistency, a in b is now an array presence check, and a of b - is an object-key check. Comments are no longer passed through to the generated - JavaScript. -
- -- 0.6.2 - The coffee command will now preserve directory structure when - compiling a directory full of scripts. Fixed two omissions that were preventing - the CoffeeScript compiler from running live within Internet Explorer. - There's now a syntax for block comments, similar in spirit to CoffeeScript's heredocs. - ECMA Harmony DRY-style pattern matching is now supported, where the name - of the property is the same as the name of the value: {name, length}: func. - Pattern matching is now allowed within comprehension variables. unless - is now allowed in block form. until loops were added, as the inverse - of while loops. switch statements are now allowed without - switch object clauses. Compatible - with Node.js v0.1.95. -
- -- 0.6.1 - Upgraded CoffeeScript for compatibility with the new Node.js v0.1.90 - series. -
- -- 0.6.0 - Trailing commas are now allowed, a-la Python. Static - properties may be assigned directly within class definitions, - using @property notation. -
- -- 0.5.6 - Interpolation can now be used within regular expressions and heredocs, as well as - strings. Added the <- bind operator. - Allowing assignment to half-expressions instead of special ||=-style - operators. The arguments object is no longer automatically converted into - an array. After requiring coffee-script, Node.js can now directly - load .coffee files, thanks to registerExtension. Multiple - splats can now be used in function calls, arrays, and pattern matching. -
- -- 0.5.5 - String interpolation, contributed by - Stan Angeloff. - Since --run has been the default since 0.5.3, updating - --stdio and --eval to run by default, pass --compile - as well if you'd like to print the result. -
- -- 0.5.4 - Bugfix that corrects the Node.js global constants __filename and - __dirname. Tweaks for more flexible parsing of nested function - literals and improperly-indented comments. Updates for the latest Node.js API. -
- -- 0.5.3 - CoffeeScript now has a syntax for defining classes. Many of the core - components (Nodes, Lexer, Rewriter, Scope, Optparse) are using them. - Cakefiles can use optparse.coffee to define options for tasks. - --run is now the default flag for the coffee command, - use --compile to save JavaScripts. Bugfix for an ambiguity between - RegExp literals and chained divisions. -
- -
- 0.5.2
- Added a compressed version of the compiler for inclusion in web pages as
-
extras/coffee-script.js. It'll automatically run any script tags
- with type text/coffeescript for you. Added a --stdio option
- to the coffee command, for piped-in compiles.
-
- 0.5.1 - Improvements to null soaking with the existential operator, including - soaks on indexed properties. Added conditions to while loops, - so you can use them as filters with when, in the same manner as - comprehensions. -
- -- 0.5.0 - CoffeeScript 0.5.0 is a major release, While there are no language changes, - the Ruby compiler has been removed in favor of a self-hosting - compiler written in pure CoffeeScript. -
- -
- 0.3.2
- @property is now a shorthand for this.property.
- Switched the default JavaScript engine from Narwhal to Node.js. Pass
- the --narwhal flag if you'd like to continue using it.
-
- 0.3.0
- CoffeeScript 0.3 includes major syntax changes:
-
- The function symbol was changed to
- ->, and the bound function symbol is now =>.
-
- Parameter lists in function definitions must now be wrapped in parentheses.
-
- Added property soaking, with the ?. operator.
-
- Made parentheses optional, when invoking functions with arguments.
-
- Removed the obsolete block literal syntax.
-
- 0.2.6 - Added Python-style chained comparisons, the conditional existence - operator ?=, and some examples from Beautiful Code. - Bugfixes relating to statement-to-expression conversion, arguments-to-array - conversion, and the TextMate syntax highlighter. -
- -- 0.2.5 - The conditions in switch statements can now take multiple values at once — - If any of them are true, the case will run. Added the long arrow ==>, - which defines and immediately binds a function to this. While loops can - now be used as expressions, in the same way that comprehensions can. Splats - can be used within pattern matches to soak up the rest of an array. -
- -- 0.2.4 - Added ECMAScript Harmony style destructuring assignment, for dealing with - extracting values from nested arrays and objects. Added indentation-sensitive - heredocs for nicely formatted strings or chunks of code. -
- -- 0.2.3 - Axed the unsatisfactory ino keyword, replacing it with of for - object comprehensions. They now look like: for prop, value of object. -
- -
- 0.2.2
- When performing a comprehension over an object, use ino, instead
- of in, which helps us generate smaller, more efficient code at
- compile time.
-
- Added :: as a shorthand for saying .prototype.
-
- The "splat" symbol has been changed from a prefix asterisk *, to
- a postfix ellipsis ...
-
- Added JavaScript's in operator,
- empty return statements, and empty while loops.
-
- Constructor functions that start with capital letters now include a
- safety check to make sure that the new instance of the object is returned.
-
- The extends keyword now functions identically to goog.inherits
- in Google's Closure Library.
-
- 0.2.1 - Arguments objects are now converted into real arrays when referenced. -
- -- 0.2.0 - Major release. Significant whitespace. Better statement-to-expression - conversion. Splats. Splice literals. Object comprehensions. Blocks. - The existential operator. Many thanks to all the folks who posted issues, - with special thanks to - Liam O'Connor-Davis for whitespace - and expression help. -
- -- 0.1.6 - Bugfix for running coffee --interactive and --run - from outside of the CoffeeScript directory. Bugfix for nested - function/if-statements. -
- -- 0.1.5 - Array slice literals and array comprehensions can now both take Ruby-style - ranges to specify the start and end. JavaScript variable declaration is - now pushed up to the top of the scope, making all assignment statements into - expressions. You can use \ to escape newlines. - The coffee-script command is now called coffee. -
- -- 0.1.4 - The official CoffeeScript extension is now .coffee instead of - .cs, which properly belongs to - C#. - Due to popular demand, you can now also use = to assign. Unlike - JavaScript, = can also be used within object literals, interchangeably - with :. Made a grammatical fix for chained function calls - like func(1)(2)(3)(4). Inheritance and super no longer use - __proto__, so they should be IE-compatible now. -
- -- 0.1.3 - The coffee command now includes --interactive, - which launches an interactive CoffeeScript session, and --run, - which directly compiles and executes a script. Both options depend on a - working installation of Narwhal. - The aint keyword has been replaced by isnt, which goes - together a little smoother with is. - Quoted strings are now allowed as identifiers within object literals: eg. - {"5+5": 10}. - All assignment operators now use a colon: +:, -:, - *:, etc. -
- -- 0.1.2 - Fixed a bug with calling super() through more than one level of - inheritance, with the re-addition of the extends keyword. - Added experimental Narwhal - support (as a Tusk package), contributed by - Tom Robinson, including - bin/cs as a CoffeeScript REPL and interpreter. - New --no-wrap option to suppress the safety function - wrapper. -
- -- 0.1.1 - Added instanceof and typeof as operators. -
- -- 0.1.0 - Initial CoffeeScript release. -
- -ExpressoInsanely fast TDD framework for node featuring code coverage reporting. | |
expresso | bin/expresso |
-!/usr/bin/env node- |
-
-
- |
-
- Module dependencies. - - |
-
-
- |
-
- Expresso version. - - |
-
-
- |
-
- Failure count. - - |
-
-
- |
-
- Number of tests executed. - - |
-
-
- |
-
- Whitelist of tests to run. - - |
-
-
- |
-
- Boring output. - - |
-
-
- |
-
- Growl notifications. - - |
-
-
- |
-
- Server port. - - |
-
-
- |
-
- Watch mode. - - |
-
-
- |
-
- Execute serially. - - |
-
-
- |
-
- Usage documentation. - - |
-
-
- |
-
- Colorized sys.error(). - - - -
|
-
-
- |
-
- Colorize the given string using ansi-escape sequences. -Disabled when --boring is set. - - - -
|
-
-
- |
-
- Assert that
|
-
-
- |
-
- Assert that
|
-
-
- |
-
- Assert that
|
-
-
- |
-
- Assert that
|
-
-
- |
-
- Assert that
|
-
-
- |
-
- Assert that
|
-
-
- |
-
- Assert that Examples- -assert.includes('foobar', 'bar'); - assert.includes(['foo', 'bar'], 'foo'); - - - -
|
-
-
- |
-
- Assert length of
|
-
-
- |
-
- Assert response from
|
-
-
- |
-
- Pad the given string to the maximum width provided. - - - -
|
-
-
- |
-
- Pad the given string to the maximum width provided. - - - -
|
-
-
- |
-
- Report test coverage. - - - -
|
-
-
- |
-
- Populate code coverage data. - - - -
|
-
-
- |
-
- Total coverage for the given file data. - - - -
|
-
-
- |
-
- Run the given test
|
-
-
- |
-
- Show the cursor when
|
-
-
- |
-
- Run the given test
|
-
-
- |
-
- Run tests for the given
|
-
-
- |
-
- Clear the module cache for the given
|
-
-
- |
-
- Watch the given
|
-
-
- |
-
- Report
|
-
-
- |
-
- Run the given tests. - - - -
|
-
-
- |
-
- Report exceptions. - - |
-
-
- |
-
- Growl notify the given
|
-
-
- |
-
Expresso is a JavaScript TDD framework written for nodejs. Expresso is extremely fast, and is packed with features such as additional assertion methods, code coverage reporting, CI support, and more.
- -assert.eql()
alias of assert.deepEqual()
assert.response()
http response utilityassert.includes()
assert.isNull()
assert.isUndefined()
assert.isNotNull()
assert.isDefined()
assert.match()
assert.length()
To install both expresso and node-jscoverage run -the command below, which will first compile node-jscoverage:
- -$ make install
-
-
-To install expresso alone without coverage reporting run:
- -$ make install-expresso
-
-
-Install via npm:
- -$ npm install expresso
-
-
-To define tests we simply export several functions:
- -exports['test String#length'] = function(assert){
- assert.equal(6, 'foobar'.length);
-};
-
-
-Alternatively for large numbers of tests you may want to -export your own object containing the tests, however this -is essentially the as above:
- -module.exports = {
- 'test String#length': function(assert){
- assert.equal(6, 'foobar'.length);
- }
-};
-
-
-If you prefer not to use quoted keys:
- -exports.testsStringLength = function(assert){
- assert.equal(6, 'foobar'.length);
-};
-
-
-The second argument passed to each callback is beforeExit, -which is typically used to assert that callbacks have been -invoked.
- -exports.testAsync = function(assert, beforeExit){
- var n = 0;
- setTimeout(function(){
- ++n;
- assert.ok(true);
- }, 200);
- setTimeout(function(){
- ++n;
- assert.ok(true);
- }, 200);
- beforeExit(function(){
- assert.equal(2, n, 'Ensure both timeouts are called');
- });
-};
-
-
-Asserts that the given val is null.
- -assert.isNull(null);
-
-
-Asserts that the given val is not null.
- -assert.isNotNull(undefined);
-assert.isNotNull(false);
-
-
-Asserts that the given val is undefined.
- -assert.isUndefined(undefined);
-
-
-Asserts that the given val is not undefined.
- -assert.isDefined(null);
-assert.isDefined(false);
-
-
-Asserts that the given str matches regexp.
- -assert.match('foobar', /^foo(bar)?/);
-assert.match('foo', /^foo(bar)?/);
-
-
-Assert that the given val has a length of n.
- -assert.length([1,2,3], 3);
-assert.length('foo', 3);
-
-
-Assert that the given obj is typeof type.
- -assert.type(3, 'number');
-
-
-Assert that object b is equal to object a. This is an -alias for the core assert.deepEqual() method which does complex -comparisons, opposed to assert.equal() which uses ==.
- -assert.eql('foo', 'foo');
-assert.eql([1,2], [1,2]);
-assert.eql({ foo: 'bar' }, { foo: 'bar' });
-
-
-Assert that obj is within val. This method supports Array_s -and Strings_s.
- -assert.includes([1,2,3], 3);
-assert.includes('foobar', 'foo');
-assert.includes('foobar', 'bar');
-
-
-Performs assertions on the given server, which should not call -listen(), as this is handled internally by expresso and the server -is killed after all responses have completed. This method works with -any http.Server instance, so Connect and Express servers will work -as well.
- -The req object may contain:
- -The res object may be a callback function which -receives the response for assertions, or an object -which is then used to perform several assertions -on the response with the following properties:
- -When providing res you may then also pass a callback function -as the fourth argument for additional assertions.
- -Below are some examples:
- -assert.response(server, {
- url: '/', timeout: 500
-}, {
- body: 'foobar'
-});
-
-assert.response(server, {
- url: '/',
- method: 'GET'
-},{
- body: '{"name":"tj"}',
- status: 200,
- headers: {
- 'Content-Type': 'application/json; charset=utf8',
- 'X-Foo': 'bar'
- }
-});
-
-assert.response(server, {
- url: '/foo',
- method: 'POST',
- data: 'bar baz'
-},{
- body: '/foo bar baz',
- status: 200
-}, 'Test POST');
-
-assert.response(server, {
- url: '/foo',
- method: 'POST',
- data: 'bar baz'
-},{
- body: '/foo bar baz',
- status: 200
-}, function(res){
- // All done, do some more tests if needed
-});
-
-assert.response(server, {
- url: '/'
-}, function(res){
- assert.ok(res.body.indexOf('tj') >= 0, 'Test assert.response() callback');
-});
-
-
-To run a single test suite (file) run:
- -$ expresso test/a.test.js
-
-
-To run several suites we may simply append another:
- -$ expresso test/a.test.js test/b.test.js
-
-
-We can also pass a whitelist of tests to run within all suites:
- -$ expresso --only "foo()" --only "bar()"
-
-
-Or several with one call:
- -$ expresso --only "foo(), bar()"
-
-
-Globbing is of course possible as well:
- -$ expresso test/*
-
-
-When expresso is called without any files, test/* is the default, -so the following is equivalent to the command above:
- -$ expresso
-
-
-If you wish to unshift a path to require.paths
before
-running tests, you may use the -I
or --include
flag.
$ expresso --include lib test/*
-
-
-The previous example is typically what I would recommend, since expresso -supports test coverage via node-jscoverage (bundled with expresso), -so you will need to expose an instrumented version of you library.
- -To instrument your library, simply run node-jscoverage, -passing the src and dest directories:
- -$ node-jscoverage lib lib-cov
-
-
-Now we can run our tests again, using the lib-cov directory that has been -instrumented with coverage statements:
- -$ expresso -I lib-cov test/*
-
-
-The output will look similar to below, depending on your test coverage of course :)
- - - -To make this process easier expresso has the -c or --cov which essentially -does the same as the two commands above. The following two commands will -run the same tests, however one will auto-instrument, and unshift lib-cov, -and the other will run tests normally:
- -$ expresso -I lib test/*
-$ expresso -I lib --cov test/*
-
-
-Currently coverage is bound to the lib directory, however in the
-future --cov
will most likely accept a path.
Sometimes it is useful to postpone running of tests until a callback or event has fired, currently the exports.foo = function(){}; syntax is supported for this:
- -setTimeout(function(){
- exports['test async exports'] = function(assert){
- assert.ok('wahoo');
- };
-}, 100);
-
-
-