From c50cf23b7b27d7d9732fa53bc4dcbd633ca7cdce Mon Sep 17 00:00:00 2001
From: Jeremy Ashkenas Activate CoffeeScript in the browser by having it compile and evaluate
+ Override exported methods for non-Node.js engines. Use standard JavaScript Running code does not provide access to this scope. If we're not in a browser environment, we're finished with the public API. Load a remote script from the current domain via XHR. Activate CoffeeScript in the browser by having it compile and evaluate
all script tags with a content-type of Print an error and exit when attempting to all an undefined task. If included on a webpage, it will automatically sniff out, compile, and
-execute all scripts present in Set up dependencies correctly for both the server and the browser. The current CoffeeScript version number. Compile a string of CoffeeScript code to JavaScript, using the Coffee/Jison
+execute all scripts present in TODO: Remove registerExtension when fully deprecated The current CoffeeScript version number. Compile a string of CoffeeScript code to JavaScript, using the Coffee/Jison
compiler. Compile and execute a string of CoffeeScript (on the server), correctly
-setting Instantiate a Lexer for our use here. The real Lexer produces a generic stream of tokens. This object provides a
+setting We want the root module. Set the filename Clear the module cache Compile Compile and evaluate a string of CoffeeScript (in a Node.js-like environment).
+The CoffeeScript REPL uses this to run the input. Instantiate a Lexer for our use here. The real Lexer produces a generic stream of tokens. This object provides a
thin wrapper around it, compatible with the Jison API. We can then pass it
directly as a "Jison lexer".
browser.coffee
puts ''
for all name, task of tasks
spaces = 20 - name.length
- spaces = if spaces > 0 then (' ' for i in [0..spaces]).join('') else ''
+ 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
browser.coffee
CoffeeScript = require './coffee-script'
eval
to eval code. CoffeeScript.eval = (code, options) ->
+ eval CoffeeScript.compile code, options
CoffeeScript.run = (code, options) ->
+ (Function CoffeeScript.compile code, options)()
return unless window?
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
text/coffeescript
.
-This happens on page load. if document?.getElementsByTagName
- grind = (coffee) ->
- setTimeout exports.compile coffee
- grindRemote = (url) ->
- xhr = new (window.ActiveXObject or XMLHttpRequest)('Microsoft.XMLHTTP')
- xhr.open 'GET', url, true
- xhr.overrideMimeType 'text/plain' if 'overrideMimeType' of xhr
- xhr.onreadystatechange = ->
- grind xhr.responseText if xhr.readyState is 4
- xhr.send null
- processScripts = ->
- for script in document.getElementsByTagName 'script'
- if script.type is 'text/coffeescript'
- if script.src
- grindRemote script.src
- else
- grind script.innerHTML
- null
- if window.addEventListener
- addEventListener 'DOMContentLoaded', processScripts, false
- else
- attachEvent 'onload', processScripts
+This happens on page load.
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
missingTask = (task) ->
diff --git a/documentation/docs/coffee-script.html b/documentation/docs/coffee-script.html
index d6b45674..36b38493 100644
--- a/documentation/docs/coffee-script.html
+++ b/documentation/docs/coffee-script.html
@@ -4,19 +4,16 @@ contains the main entry functions for tokenzing, parsing, and compiling source
CoffeeScript into JavaScript.
text/coffeescript
tags. if process?
- path = require 'path'
- Lexer = require('./lexer').Lexer
- parser = require('./parser').parser
- helpers = require('./helpers').helpers
- helpers.extend global, require './nodes'
- if require.registerExtension
- require.registerExtension '.coffee', (content) -> compile content
-else
- this.exports = this.CoffeeScript = {}
- Lexer = this.Lexer
- parser = this.parser
- helpers = this.helpers
exports.VERSION = '0.9.3'
text/coffeescript
tags. path = require 'path'
+{Lexer} = require './lexer'
+{parser} = require './parser'
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
exports.VERSION = '0.9.4'
exports.compile = compile = (code, options) ->
options or= {}
try
@@ -28,10 +25,13 @@ compiler.
.traverse()
with a callback. exports.nodes = (code) ->
parser.parse lexer.tokenize code
__filename
, __dirname
, and relative require()
. exports.run = (code, options) ->
- module.filename = __filename = options.fileName
- __dirname = path.dirname __filename
- eval exports.compile code, options
lexer = new Lexer
__filename
, __dirname
, and relative require()
. exports.run = (code, options) ->
root = module
+ while root.parent
+ root = root.parent
root.filename = __filename = "#{options.fileName} (compiled)"
root.moduleCache = {} if root.moduleCache
root._compile exports.compile(code, options), root.filename
exports.eval = (code, options) ->
+ __filename = options.fileName
+ __dirname = path.dirname __filename
+ eval exports.compile(code, options)
lexer = new Lexer
parser.lexer =
lex: ->
@@ -45,4 +45,6 @@ directly as a "Jison lexer".
@pos = 0
upcomingInput: -> ""
+parser.yy = require './nodes'
+
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]
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 @@ -182,6 +183,7 @@ and optional references to the superclass.
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]
diff --git a/documentation/docs/helpers.html b/documentation/docs/helpers.html
index 63ba5a5a..d3bc7add 100644
--- a/documentation/docs/helpers.html
+++ b/documentation/docs/helpers.html
@@ -1,35 +1,34 @@
helpers.coffee Jump To … 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.
Set up exported variables for both Node.js and the browser.
this.exports = this unless process?
-helpers = exports.helpers = {}
Cross-browser indexOf, so that IE can join the party.
helpers.indexOf = indexOf = (array, item, from) ->
+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) ->
+ -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) ->
+ 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.
+ num
Merge objects, returning a fresh copy with attributes from both sides.
Used every time BaseNode#compile
is called, to allow properties in the
options hash to propagate down the tree without polluting other branches.
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).
+ fresh
Extend a source object with the properties of another object (shallow copy).
We use this to simulate Node's deprecated process.mixin
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
+ (object[key] = val) for all key, val of properties
Return a completely flattened version of an array. Handy for getting a
list of children
from the nodes.
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
+ 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]
diff --git a/documentation/docs/lexer.html b/documentation/docs/lexer.html
index 0db93604..fc3d1f54 100644
--- a/documentation/docs/lexer.html
+++ b/documentation/docs/lexer.html
@@ -6,15 +6,9 @@ form:
[tag, value, lineNumber]
-Which is a format that can be fed directly into Jison.
Set up the Lexer for both Node.js and the browser, depending on where we are.
if process?
- {Rewriter} = require './rewriter'
- {helpers} = require './helpers'
-else
- this.exports = this
- Rewriter = this.Rewriter
- helpers = this.helpers
Import the helpers we need.
{include, count, starts, compact} = helpers
The Lexer Class
The Lexer class reads a stream of CoffeeScript and divvys it up into tagged
+
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
+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
@@ -41,7 +35,7 @@ unless explicitly asked not to.
@extractNextToken()
@closeIndentation()
return @tokens if o.rewrite is off
- (new Rewriter).rewrite @tokens At every position, run through this list of attempted matches,
+ (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:
@literalToken
is the fallback catch-all.
extractNextToken: ->
return if @identifierToken()
@@ -53,7 +47,7 @@ short-circuiting if any of them succeed. Their order determines precedence:
return if @numberToken()
return if @regexToken()
return if @jsToken()
- return @literalToken()
Tokenizers
Matches identifying literals: variables, keywords, method names, etc.
+ 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
@@ -83,13 +77,13 @@ though is
means ===
otherwise.
tag = 'UNARY' if id is '!'
@token tag, id
@token ']', ']' if close_index
- true Matches numbers, including decimals, hex, and exponential notation.
+ 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
+ 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 =
@@ -98,7 +92,7 @@ are balanced within the string's contents, and within nested interpolations.
@interpolateString string.replace /\n/g, '\\\n'
@line += count string, "\n"
@i += string.length
- true
Matches heredocs, adjusting indentation to the correct level, as heredocs
+ 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
@@ -106,19 +100,20 @@ preserve whitespace, but ignore indentation to the left.
@interpolateString quote + doc + quote, heredoc: yes
@line += count match[1], "\n"
@i += match[1].length
- true Matches and consumes comments.
commentToken: ->
+ true
Matches and consumes comments.
commentToken: ->
return false unless match = @chunk.match(COMMENT)
@line += count match[1], "\n"
@i += match[1].length
- if match[4]
- @token 'HERECOMMENT', @sanitizeHeredoc match[4], herecomment: true, indent: match[3]
+ 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: ->
+ 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
+ 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 @balancedToken
, and
borrow interpolation from @interpolateString
.
regexToken: ->
@@ -138,9 +133,9 @@ borrow interpolation from @interpolateString
.
else
@token 'REGEX', regex
@i += regex.length
- true Matches a token in which which the passed delimiter pairs must be correctly
+ 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.
+ @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:
@@ -173,7 +168,7 @@ can close multiple indents, so we need to know how far in we happen to be.
@indebt = 0
@outdentToken @indent - size, noNewlines
@indent = size
- true Record an outdent token or multiple tokens, if we happen to be moving back
+ 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
@@ -193,18 +188,18 @@ inwards past several recorded indents.
@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
+ 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) ->
+ true
Generate a newline token. Consecutive newlines get merged together.
newlineToken: (newlines) ->
@token 'TERMINATOR', "\n" unless @tag() is 'TERMINATOR'
- true
Use a \
at a line-ending to suppress the newline.
+ true
Use a \
at a line-ending to suppress the newline.
The slash is removed here once its job is done.
suppressNewlines: ->
@tokens.pop() if @value() is "\\"
- true
We treat all other single characters as a token. Eg.: ( ) , . !
+ true
We treat all other single characters as a token. Eg.: ( ) , . !
Multi-character operators are also literal tokens, so that Jison can assign
the proper order of operations. There are some symbols that we tag specially
here. ;
and newlines are both treated as a TERMINATOR
, we distinguish
@@ -238,7 +233,7 @@ parentheses that indicate a method call from regular parentheses, and so on.
@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 IDENTIFIER
, look at the previous token to determine
+ true
Token Manipulators
As we consume a new IDENTIFIER
, look at the previous token to determine
if it's a special kind of accessor. Return true
if any type of accessor
is the previous token.
tagAccessor: ->
return false if (not prev = @prev()) or (prev and prev.spaced)
@@ -252,7 +247,7 @@ is the previous token.
@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
+ 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'
@@ -261,10 +256,11 @@ erasing all external indentation on the left-hand side.
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'), '').replace(/^\n/, '')
+ 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
+ .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 ')'
@@ -277,12 +273,12 @@ parameters specially in order to make things easier for the parser.
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
+ 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,
+ 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
+ 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) ->
@@ -310,7 +306,7 @@ interpolations within strings, ad infinitum.
<
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
+ 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.
@@ -360,22 +356,22 @@ token stream.
@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) ->
+ 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) ->
+ 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
+ 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 false
otherwise.
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: ->
+ 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 = [
+ not @chunk.match ASSIGNED
Constants
Keywords that CoffeeScript shares in common with JavaScript.
JS_KEYWORDS = [
"if", "else",
"true", "false",
"new", "return",
@@ -385,46 +381,46 @@ match if successful, and false
otherwise.
"delete", "instanceof", "typeof",
"switch", "super", "extends", "class",
"this", "null", "debugger"
-] CoffeeScript-only keywords, which we're more relaxed about allowing. They can't
+]
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
+]
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|\$)*)/
+]
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 = /^(([ \t]*\n)*([ \t]*)###([^#][\s\S]*?)(###[ \t]*\n|(###)?$)|(\s*#(?!##[^#])[^\n]*)+)/
+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 = /^\/([^\/])/
+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
+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
+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
+
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 WHEN
, indicate that the WHEN
+of a function invocation or indexing operation.
CALLABLE = ['IDENTIFIER', 'SUPER', ')', ']', '}', 'STRING', '@', 'THIS', '?', '::']
Tokens that, when immediately preceding a WHEN
, indicate that the WHEN
occurs at the start of a line. We disambiguate these from trailing whens to
-avoid an ambiguity in the grammar.
LINE_BREAK = ['INDENT', 'OUTDENT', 'TERMINATOR']
Conversions from CoffeeScript operators into JavaScript ones.
CONVERSIONS =
+avoid an ambiguity in the grammar.
LINE_BREAK = ['INDENT', 'OUTDENT', 'TERMINATOR']
Conversions from CoffeeScript operators into JavaScript ones.
CONVERSIONS =
'and': '&&'
'or': '||'
'is': '=='
diff --git a/documentation/docs/nodes.html b/documentation/docs/nodes.html
index c3c420b9..181e0355 100644
--- a/documentation/docs/nodes.html
+++ b/documentation/docs/nodes.html
@@ -1,14 +1,7 @@
nodes.coffee Jump To … nodes.coffee
nodes.coffee
contains all of the node classes for the syntax tree. Most
nodes are created as the result of actions in the grammar,
but some are created by other nodes as a method of code generation. To convert
-the syntax tree into a string of JavaScript code, call compile()
on the root.
Set up for both Node.js and the browser, by
-including the Scope class and the helper functions.
if process?
- Scope = require('./scope').Scope
- helpers = require('./helpers').helpers
-else
- this.exports = this
- helpers = this.helpers
- Scope = this.Scope
Import the helpers we plan to use.
{compact, flatten, merge, del, include, indexOf, starts, ends} = helpers
BaseNode
The BaseNode is the abstract base class for all nodes in the syntax tree.
+the syntax tree into a string of JavaScript code, call compile()
on the root.
{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 compileNode
method, which performs the
code generation for that node. To compile a node to JavaScript,
call compile
on it, which wraps compileNode
in some generic extra smarts,
@@ -19,7 +12,7 @@ being requested by the surrounding function), information about the current
scope, and indentation level.
exports.BaseNode = class BaseNode
constructor: ->
- @tags = {}
Common logic for determining whether to wrap this node in a closure before
+ @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
@@ -36,11 +29,14 @@ top-level statement within the function body.
closure = @isStatement(o) and not @isPureStatement() and not top and
not @options.asStatement and this not instanceof CommentNode and
not @containsPureStatement()
- if closure then @compileClosure(@options) else @compileNode(@options) Statements converted into expressions via closure-wrapping share a scope
+ 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
+ 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= {}
@@ -50,18 +46,18 @@ by assigning it to a temporary variable.
else if this instanceof ValueNode and options.assignment
this.cacheIndexes(o)
else
- reference = literal o.scope.freeVariable()
+ 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) ->
+ 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.
+ 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?
+ 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
does not cross
scope boundaries.
contains: (block) ->
@@ -70,10 +66,10 @@ scope boundaries.
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
+ 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
representation of the node, for inspecting the parse tree.
+ @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
representation of the node, for inspecting the parse tree.
This is what coffee --nodes
prints out.
toString: (idt, override) ->
idt or= ''
children = (child.toString idt + TAB for child in @collectChildren()).join('')
@@ -93,14 +89,14 @@ This is what coffee --nodes
prints out.
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
+ 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
+ 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
if
, switch
, or try
, and so on...
exports.Expressions = class Expressions extends BaseNode
@@ -110,26 +106,26 @@ indented block of code -- the implementation of a function, a clause in an
constructor: (nodes) ->
super()
- @expressions = compact flatten nodes or []
Tack an expression on to the end of this expression list.
push: (node) ->
+ @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) ->
+ 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
+ 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
+ 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) ->
+ 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
+ (@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) ->
@@ -137,20 +133,20 @@ clean up obvious double-parentheses.
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})();\n" Compile the expressions body for the contents of a function, with
+ 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
+ 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
+ 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
+ new Expressions(nodes)
LiteralNode
Literals are static values that can be passed through directly into
JavaScript without translation, such as: strings, numbers,
true
, false
, null
...
exports.LiteralNode = class LiteralNode extends BaseNode
@@ -160,7 +156,7 @@ JavaScript without translation, such as: strings, numbers,
super()
makeReturn: ->
- if @isStatement() then this else super()
Break and continue must be treated as pure statements -- they lose their
+ 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
@@ -171,7 +167,7 @@ meaning when wrapped in a closure.
idt + @value + end
toString: (idt) ->
- '"' + @value + '"' ReturnNode
A return
is a pureStatement -- wrapping it in a closure wouldn't
+ '"' + @value + '"'
ReturnNode
A return
is a pureStatement -- wrapping it in a closure wouldn't
make sense.
exports.ReturnNode = class ReturnNode extends BaseNode
class: 'ReturnNode'
@@ -192,18 +188,18 @@ make sense.
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,
+ "#{@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) ->
+ 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 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: ->
+ !!@properties.length
Some boolean checks for the benefit of other nodes.
isArray: ->
@base instanceof ArrayNode and not @hasProperties()
isObject: ->
@@ -213,13 +209,13 @@ or vanilla.
@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
+ 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) ->
+ 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
+ @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..]
@@ -230,8 +226,8 @@ the value of the indexes.
[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.
+ [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 ?.
interspersed. Then we have to take care not to accidentally
evaluate a anything twice when building the soak chain.
compileNode: (o) ->
@@ -252,7 +248,7 @@ evaluate a anything twice when building the soak chain.
@source = baseline
if prop.soakNode
if @base.containsType(CallNode) and i is 0
- temp = o.scope.freeVariable()
+ temp = o.scope.freeVariable 'ref'
complete = "(#{ baseline = temp } = (#{complete}))"
complete = if i is 0
"(typeof #{complete} === \"undefined\" || #{baseline} === null) ? undefined : "
@@ -268,7 +264,7 @@ evaluate a anything twice when building the soak chain.
complete += part
@last = part
- if op and @wrapped then "(#{complete})" else complete CommentNode
CoffeeScript passes through block comments as JavaScript block comments
+ 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'
@@ -281,7 +277,7 @@ at the same position.
this
compileNode: (o) ->
- @tab + '/*' + @comment.replace(/\r?\n/g, '\n' + @tab) + '*/' CallNode
Node for a function invocation. Takes care of converting super()
calls into
+ @tab + '/*' + @comment.replace(/\r?\n/g, '\n' + @tab) + '*/'
CallNode
Node for a function invocation. Takes care of converting super()
calls into
calls against the prototype's function of the same name.
exports.CallNode = class CallNode extends BaseNode
class: 'CallNode'
@@ -295,26 +291,33 @@ calls against the prototype's function of the same name.
@args or= []
@first = @last = ''
@compileSplatArguments = (o) ->
- SplatNode.compileSplattedArray.call(this, @args, o) Tag this invocation as creating a new instance.
newInstance: ->
+ 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) ->
+ 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) ->
+ 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
- [@first, @meth] = @variable.compileReference o, precompile: yes
+ 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 then @meth = @variable.compile o
+ else if @variable
+ @meth = @variable.compile o
for arg in @args when arg instanceof SplatNode
code = @compileSplat(o)
if not code
@@ -325,16 +328,16 @@ calls against the prototype's function of the same name.
@compileSuper(args.join(', '), o)
else
"#{@first}#{@prefix()}#{@meth}(#{ args.join(', ') })#{@last}"
- if op and @variable and @variable.wrapped then "(#{code})" else code super()
is converted into a call against the superclass's implementation
+ if op and @variable and @variable.wrapped then "(#{code})" else code
super()
is converted into a call against the superclass's implementation
of the current function.
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
+ "#{@superReference(o)}.call(this#{ if args.length then ', ' else '' }#{args})"
If you call a function with a splat, it's converted into a JavaScript
.apply()
call to allow an array of arguments to be passed.
If it's a constructor, then things get real tricky. We have to inject an
inner constructor in order to be able to pass the varargs.
compileSplat: (o) ->
meth = @meth or @superReference(o)
obj = @variable and @variable.source or 'this'
if obj.match(/\(/)
- temp = o.scope.freeVariable()
+ temp = o.scope.freeVariable 'ref'
obj = temp
meth = "(#{temp} = #{ @variable.source })#{ @variable.last }"
if @isNew
@@ -342,29 +345,29 @@ inner constructor in order to be able to pass the varargs.
for arg in @args
arg.contains (n) -> mentionsArgs or= n instanceof LiteralNode and (n.value is 'arguments')
utility 'extends'
- a = o.scope.freeVariable()
- b = o.scope.freeVariable()
- c = o.scope.freeVariable()
+ 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() {
+ "#{@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 (#{b} = #{a}.apply(#{c} = new ctor, #{ @compileSplatArguments(o) })) === "object" ? #{b} : #{c};
-{@tab}}).#{ if mentionsArgs then 'apply(this, arguments)' else 'call(this)'}#{@last}
ExtendsNode
exports.ExtendsNode = class ExtendsNode extends BaseNode
+{@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.
+ super()
Node to extend an object's prototype with an ancestor object.
After goog.inherits
from the
Closure Library.
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
+ (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']
@@ -378,8 +381,8 @@ After goog.inherits
from the
name = @name.compile o
o.chainRoot.wrapped or= @soakNode
namePart = if name.match(IS_STRING) then "[#{name}]" else ".#{name}"
- @prototype + namePart
A .
accessor into a property of a value, or the ::
shorthand for
-an accessor into the object's prototype.
IndexNode
exports.IndexNode = class IndexNode extends BaseNode
+ @prototype + namePart
A .
accessor into a property of a value, or the ::
shorthand for
+an accessor into the object's prototype.
IndexNode
exports.IndexNode = class IndexNode extends BaseNode
class: 'IndexNode'
children: ['index']
@@ -391,7 +394,7 @@ an accessor into the object's prototype.
o.chainRoot.wrapped or= @soakNode
idx = @index.compile o
prefix = if @proto then '.prototype' else ''
- "#{prefix}[#{idx}]" A [ ... ]
indexed accessor into an array or object.
RangeNode
exports.RangeNode = class RangeNode extends BaseNode
+ "#{prefix}[#{idx}]"
A [ ... ]
indexed accessor into an array or object.
RangeNode
exports.RangeNode = class RangeNode extends BaseNode
class: 'RangeNode'
children: ['from', 'to']
@@ -399,7 +402,7 @@ an accessor into the object's prototype.
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,
+ @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)
@@ -409,7 +412,7 @@ corresponding array of integers at runtime.
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.
+ 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
@@ -420,7 +423,7 @@ But only if they need to be cached to avoid double evaluation. <
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
+ "#{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'
@@ -429,15 +432,15 @@ needed to iterate over the values in the range. Used by comprehensions.
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) ->
+ "#{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()
- result = o.scope.freeVariable()
+ i = o.scope.freeVariable 'i'
+ result = o.scope.freeVariable 'result'
pre = "\n#{idt}#{result} = []; #{vars}"
if @fromNum and @toNum
o.index = i
@@ -446,7 +449,7 @@ needed to iterate over the values in the range. Used by comprehensions.
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
+ "(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']
@@ -459,9 +462,9 @@ needed to iterate over the values in the range. Used by comprehensions.
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 Array#slice
, the second parameter
+ ".slice(#{from}#{to})"
An array slice literal. Unlike JavaScript's Array#slice
, the second parameter
specifies the index of the end of the slice, just as the first parameter
-is the index of the beginning.
ObjectNode
exports.ObjectNode = class ObjectNode extends BaseNode
+is the index of the beginning.
ObjectNode
exports.ObjectNode = class ObjectNode extends BaseNode
class: 'ObjectNode'
children: ['properties']
@@ -486,7 +489,7 @@ is the index of the beginning.
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
+ if top then "(#{obj})" else obj
An object literal, nothing fancy.
ArrayNode
exports.ArrayNode = class ArrayNode extends BaseNode
class: 'ArrayNode'
children: ['objects']
@@ -514,20 +517,20 @@ is the index of the beginning.
if indexOf(objects, '\n') >= 0
"[\n#{@idt(1)}#{objects}\n#{@tab}]"
else
- "[#{objects}]" An array literal.
ClassNode
exports.ClassNode = class ClassNode extends BaseNode
+ "[#{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) ->
+ 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
+ this
Initialize a ClassNode with its name, an optional superclass, and a
list of prototype property assignments.
compileNode: (o) ->
- @variable = literal o.scope.freeVariable() if @variable is '__temp__'
+ @variable = literal o.scope.freeVariable 'ctor' if @variable is '__temp__'
extension = @parent and new ExtendsNode(@variable, @parent)
props = new Expressions
o.top = true
@@ -559,7 +562,7 @@ list of prototype property assignments.
else
func.bound = false
constScope or= new Scope(o.scope, constructor.body, constructor)
- me or= constScope.freeVariable()
+ 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); }"
@@ -574,9 +577,9 @@ list of prototype property assignments. 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
+ 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
+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\.)?/
@@ -599,7 +602,7 @@ property of an object -- including within object literals.
super()
isStatement: ->
- @isValue() and (@variable.isArray() or @variable.isObject()) Matchers for detecting prototype assignments.
compileNode: (o) ->
+ @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()
@@ -616,23 +619,23 @@ property of an object -- including within object literals.
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
or
+ if top or @parenthetical then val else "(#{val})"
Compile an assignment, delegating to compilePatternMatch
or
compileSplice
if appropriate. Keep track of the name of the base object
we've been assigned to, for correct internal references. If the variable
has not been seen yet within the current scope, declare it.
compilePatternMatch: (o) ->
- valVar = o.scope.freeVariable()
+ 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
+ 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 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
@@ -647,7 +650,7 @@ for details.
<
val = new ValueNode(literal(valVar), [new accessClass(idx)])
assigns.push(new AssignNode(obj, val).compile(o))
code = assigns.join("\n")
- code A shorthand {a, b, c} = val
pattern-match.
compileSplice: (o) ->
+ code
A shorthand {a, b, c} = val
pattern-match.
compileSplice: (o) ->
name = @variable.compile merge o, onlyFirst: true
l = @variable.properties.length
range = @variable.properties[l - 1].range
@@ -655,8 +658,8 @@ for details.
<
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
-Array#splice
method.
CodeNode
exports.CodeNode = class CodeNode extends BaseNode
+ "#{name}.splice.apply(#{name}, [#{from}, #{to}].concat(#{val}))"
Compile the assignment from an array splice literal, using JavaScript's
+Array#splice
method.
CodeNode
exports.CodeNode = class CodeNode extends BaseNode
class: 'CodeNode'
children: ['params', 'body']
@@ -666,7 +669,7 @@ for details.
<
@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.
+ @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'
@@ -688,7 +691,7 @@ has no children -- they're within the inner scope.
else
if param.attach
{value} = param
- [param, param.splat] = [literal(o.scope.freeVariable()), param.splat]
+ [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
@@ -707,7 +710,7 @@ has no children -- they're within the inner scope. if top then "(#{func})" else func
topSensitive: ->
- true Compilation creates a new scope unless explicitly asked to share with the
+ 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 arguments
objects. If the function is bound with the =>
arrow, generates a wrapper that saves the current value of this
through
@@ -716,8 +719,8 @@ a closure.
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
+ '\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']
@@ -730,9 +733,9 @@ unless crossScope is true
@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,
+ 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
+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']
@@ -743,28 +746,28 @@ as well as be a splat, gathering up a group of parameters into an array.
@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,
+ 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.freeVariable 'len'
o.scope.assign len, "arguments.length"
- variadic = o.scope.freeVariable()
+ 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()
+ 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
+ "#{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
+ "#{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
@@ -780,8 +783,8 @@ from the right-hand-side's corresponding array.
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
+ 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']
@@ -804,7 +807,7 @@ splats, to a proper array
this
topSensitive: ->
- true A while loop, the only sort of low-level loop exposed by CoffeeScript. From
+ 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
@@ -814,7 +817,7 @@ flexibility or more speed than a comprehension can provide.
cond = @condition.compile(o)
set = ''
unless top
- rvar = o.scope.freeVariable()
+ rvar = o.scope.freeVariable 'result'
set = "#{@tab}#{rvar} = [];\n"
@body = PushNode.wrap(rvar, @body) if @body
pre = "#{set}#{@tab}while (#{cond})"
@@ -823,15 +826,15 @@ flexibility or more speed than a comprehension can provide. 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
+ "#{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
+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 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']
+ '===': '!=='
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']
@@ -872,11 +875,11 @@ CoffeeScript operations into their JavaScript equivalents.
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) ->
+ [@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
+ "(#{first}) && (#{shared} #{@operator} #{second})"
Mimic Python's chained comparisons when multiple comparison operators are
used sequentially. For example:
bin/coffee -e "puts 50 < 65 > 10"
@@ -887,16 +890,16 @@ true
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
+ "#{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 ExistenceNode.compileTest
+ "#{test} ? #{ref} : #{ @second.compile(o) }"
If this is an existence operator, we delegate to ExistenceNode.compileTest
to give us the safe references for the variables.
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
+ parts.join('')
Compile a unary OpNode.
exports.InNode = class InNode extends BaseNode
class: 'InNode'
children: ['object', 'array']
@@ -918,9 +921,9 @@ to give us the safe references for the variables.
compileLoopTest: (o) ->
[@arr1, @arr2] = @array.compileReference o, precompile: yes
- [i, l] = [o.scope.freeVariable(), o.scope.freeVariable()]
+ [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
+ "(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']
@@ -932,26 +935,26 @@ to give us the safe references for the variables.
makeReturn: ->
@attempt = @attempt.makeReturn() if @attempt
@recovery = @recovery.makeReturn() if @recovery
- this A classic try/catch/finally block.
compileNode: (o) ->
+ 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
+ "#{@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: ->
+ 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
+ "#{@tab}throw #{@expression.compile(o)};"
A ThrowNode is already a return, of sorts...
ExistenceNode
exports.ExistenceNode = class ExistenceNode extends BaseNode
class: 'ExistenceNode'
children: ['expression']
@@ -961,13 +964,13 @@ is optional, the catch is not.
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
+ 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 .nil?
in Ruby, and avoids having to consult a JavaScript truth
table.
@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 compileTest
method
+ ["(typeof #{first} !== \"undefined\" && #{second} !== null)", second]
The meat of the ExistenceNode is in this static compileTest
method
because other nodes like to check the existence of their variables as well.
-Be careful not to double-evaluate anything.
ParentheticalNode
exports.ParentheticalNode = class ParentheticalNode extends BaseNode
+Be careful not to double-evaluate anything.
ParentheticalNode
exports.ParentheticalNode = class ParentheticalNode extends BaseNode
class: 'ParentheticalNode'
children: ['expression']
@@ -991,11 +994,11 @@ Be careful not to double-evaluate anything.
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
+ "(#{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
+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']
@@ -1024,7 +1027,7 @@ parentheses, but no longer -- you can put in as many as you please.
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
+ ''
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.
@@ -1036,12 +1039,12 @@ you can map and filter in a single pass. 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()
+ 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() unless topLevel
- ivar = if codeInBody then scope.freeVariable() else if range then name else index or scope.freeVariable()
+ 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])
@@ -1049,14 +1052,14 @@ you can map and filter in a single pass. sourcePart = source.compileVariables(o)
forPart = source.compile merge o, index: ivar, step: @step
else
- svar = scope.freeVariable()
+ 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}) + '\n'
+ 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()
+ 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
@@ -1078,10 +1081,10 @@ you can map and filter in a single pass. 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
+ "#{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
+some cannot.
SwitchNode
exports.SwitchNode = class SwitchNode extends BaseNode
class: 'SwitchNode'
children: ['subject', 'cases', 'otherwise']
@@ -1099,7 +1102,7 @@ some cannot.
<
this
compileNode: (o) ->
- idt = o.indent = @idt 1
+ idt = o.indent = @idt 2
o.top = yes
code = "#{ @tab }switch (#{ @subject.compile o }) {"
for pair in @cases
@@ -1107,13 +1110,13 @@ some cannot. <
exprs = block.expressions
for condition in flatten [conditions]
condition = new OpNode '!!', new ParentheticalNode condition if @tags.subjectless
- code += "\n#{ @tab }case #{ condition.compile o }:"
+ 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#{ @tab }default:\n#{ @otherwise.compile o }"
+ 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
+ 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']
@@ -1135,7 +1138,7 @@ some cannot.
<
forceStatement: ->
@tags.statement = true
- this If/else statements. Acts as an expression by pushing down requested returns
+ 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,
@@ -1145,7 +1148,7 @@ because ternaries are already proper expressions, and don't need conversion.
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) ->
+ 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) ->
@@ -1165,7 +1168,7 @@ because ternaries are already proper expressions, and don't need conversion.
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
+ 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'
@@ -1181,23 +1184,23 @@ to be a statement. Otherwise a ternary is safe.
' 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
+ "#{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 =
+ 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
+ )])
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 array.push(value)
, which is helpful for recording the result
-arrays from comprehensions.
ClosureNode
ClosureNode = exports.ClosureNode =
A faux-node used to wrap an expressions body in a closure.
wrap: (expressions, statement) ->
+arrays from comprehensions.
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 = []
@@ -1212,9 +1215,9 @@ arrays from comprehensions.
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,
+ 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 this
or arguments
,
-then make sure that the closure wrapper preserves the original values.
UTILITIES =
Utility Functions
extends: """
+then make sure that the closure wrapper preserves the original values.
UTILITIES =
Utility Functions
extends: """
function(child, parent) {
var ctor = function(){};
ctor.prototype = parent.prototype;
@@ -1223,20 +1226,20 @@ then make sure that the closure wrapper preserves the original values.
if (typeof parent.extended === "function") parent.extended(child);
child.__super__ = parent.prototype;
}
- """
Correctly set up a prototype chain for inheritance, including a reference
+ """
Correctly set up a prototype chain for inheritance, including a reference
to the superclass for super()
calls. See:
goog.inherits.
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
+ """
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) ->
+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
\ No newline at end of file
+
Helper for ensuring that utility functions are assigned at the top level.
undefined
\ No newline at end of file
diff --git a/documentation/docs/optparse.html b/documentation/docs/optparse.html
index b4c5a52a..8b59e3ce 100644
--- a/documentation/docs/optparse.html
+++ b/documentation/docs/optparse.html
@@ -39,7 +39,7 @@ of the valid options, for --help
and such.
Regex matchers for option flags.
LONG_FLAG = /^(--\w[\w\-]+)/
diff --git a/documentation/docs/repl.html b/documentation/docs/repl.html
index 514fb601..3b641308 100644
--- a/documentation/docs/repl.html
+++ b/documentation/docs/repl.html
@@ -9,7 +9,7 @@ Using it looks like this:
Attempt to evaluate the command. If there's an exception, print it out instead
of exiting.
run = (buffer) ->
try
- val = CoffeeScript.run buffer.toString(), noWrap: true, globals: true, fileName: 'repl'
+ 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()
diff --git a/documentation/docs/rewriter.html b/documentation/docs/rewriter.html
index ee75263f..40a93949 100644
--- a/documentation/docs/rewriter.html
+++ b/documentation/docs/rewriter.html
@@ -3,13 +3,9 @@ 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.
Set up exported variables for both Node.js and the browser.
if process?
- {helpers} = require('./helpers')
-else
- this.exports = this
- helpers = this.helpers
Import the helpers we need.
{include} = 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 +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 @@ -26,7 +22,7 @@ corrected before implicit parentheses can be wrapped around blocks of code.
@addImplicitParentheses() @ensureBalance BALANCED_PAIRS @rewriteClosingParens() - @tokensRewrite the token stream, looking one token ahead and behind. + @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 @@ -47,7 +43,7 @@ our feet.
Massage newlines and indentations so that comments don't have to be + 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'
@@ -66,14 +62,14 @@ correctly indented, or appear on a line of their own.
Leading newlines would introduce an ambiguity in the grammar, so we + 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 + @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 + 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) ->
@@ -84,14 +80,14 @@ calls that close on the same line, just before their outdent.
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. + 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. + 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) ->
@@ -103,6 +99,7 @@ Insert the missing braces here, so that the parser doesn't have to.
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
@@ -115,7 +112,7 @@ Insert the missing braces here, so that the parser doesn't have to.
@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. + 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
@@ -135,8 +132,9 @@ deal with them.
Because our grammar is LALR(1), it can't handle some single-line + 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: ->
@@ -172,7 +170,7 @@ but we need to make sure it's balanced.
Tag postfix conditionals as such, so that we can parse them with a + 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']
@@ -183,7 +181,7 @@ different precedence.
Ensure that all listed pairs of tokens are correctly balanced throughout + return 1
Ensure that all listed pairs of tokens are correctly balanced throughout the course of the token stream.
ensureBalance: (pairs) ->
levels = {}
openLine = {}
@@ -201,7 +199,7 @@ the course of the token stream.
We'd like to support syntax like this:
+ throw new Error "unclosed #{open} on line #{line}"We'd like to support syntax like this:
el.click((event) ->
el.hide())
@@ -248,20 +246,20 @@ rewriting.
@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]
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 BALANCED_PAIRS
we're trying to fix up, so we can
+ 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]
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 BALANCED_PAIRS
we're trying to fix up, so we can
look things up from either end.
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_CALL
, indicate a function invocation.
IMPLICIT_FUNC = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@', 'THIS']
If preceded by an IMPLICIT_FUNC
, indicates a function invocation.
IMPLICIT_CALL = [
+ 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_CALL
, indicate a function invocation.
IMPLICIT_FUNC = ['IDENTIFIER', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@', 'THIS']
If preceded by an IMPLICIT_FUNC
, indicates a function invocation.
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. +]
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']
+SINGLE_CLOSERS = ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN']
Tokens that end a line.
LINEBREAKS = ['TERMINATOR', 'INDENT', 'OUTDENT']