Additional indent in front of these is ignored.
- Import the helpers we plan to use.
¶
- Functions required by parser
-
+ Functions required by parser
- exports.extend = extend
-exports.addLocationDataFn = addLocationDataFn
+ exports.extend = extend
+exports.addLocationDataFn = addLocationDataFn
@@ -165,15 +166,14 @@ exports.addLocationDataFn = addLocationDataFn
- Constant functions for nodes that don't need customization.
-
+ Constant functions for nodes that don't need customization.
- YES = -> yes
-NO = -> no
-THIS = -> this
-NEGATE = -> @negated = not @negated; this
+ YES = -> yes
+NO = -> no
+THIS = -> this
+NEGATE = -> @negated = not @negated; this
@@ -181,13 +181,26 @@ exports.addLocationDataFn = addLocationDataFn
-
+
-
CodeFragment
+
CodeFragment
+
The various nodes defined below all compile to a collection of CodeFragment objects.
+A CodeFragments is a block of generated code, and the location in the source file where the code
+came from. CodeFragments can be assembled together into working code just by catting together
+all the CodeFragments' code
snippets, in order.
+
exports.CodeFragment = class CodeFragment
+ constructor: (parent, code) ->
+ @code = "#{code}"
+ @locationData = parent?.locationData
+ @type = parent?.constructor?.name or 'unknown'
+
+ toString: ->
+ "#{@code}#{if @locationData then ": " + locationDataToString(@locationData) else ''}"
+
@@ -197,22 +210,12 @@ exports.addLocationDataFn = addLocationDataFn
- The various nodes defined below all compile to a collection of CodeFragment objects.
-A CodeFragments is a block of generated code, and the location in the source file where the code
-came from. CodeFragments can be assembled together into working code just by catting together
-all the CodeFragments' code
snippets, in order.
-
+ Convert an array of CodeFragments into a string.
- exports.CodeFragment = class CodeFragment
- constructor: (parent, code) ->
- @code = "#{code}"
- @locationData = parent?.locationData
- @type = parent?.constructor?.name or 'unknown'
-
- toString: ->
- "#{@code}#{if @locationData then ": " + locationDataToString(@locationData) else ''}"
+ fragmentsToText = (fragments) ->
+ (fragment.code for fragment in fragments).join('')
@@ -223,13 +226,23 @@ all the CodeFragments' code
snippets, in order.
- Convert an array of CodeFragments into a string.
-
+ Base
+The Base is the abstract base class for all nodes in the syntax tree.
+Each subclass implements the compileNode
method, which performs the
+code generation for that node. To compile a node to JavaScript,
+call compile
on it, which wraps compileNode
in some generic extra smarts,
+to know when the generated code needs to be wrapped up in a closure.
+An options hash is passed and cloned throughout, containing information about
+the environment from higher in the tree (such as if a returned value is
+being requested by the surrounding function), information about the current
+scope, and indentation level.
- fragmentsToText = (fragments) ->
- (fragment.code for fragment in fragments).join('')
+ exports.Base = class Base
+
+ compile: (o, lvl) ->
+ fragmentsToText @compileToFragments o, lvl
@@ -237,13 +250,28 @@ all the CodeFragments' code
snippets, in order.
-
+
-
Base
+
Common logic for determining whether to wrap this node in a closure before
+compiling it, or to compile directly. We need to wrap if this node is a
+statement, and it's not a pureStatement, and we're not at
+the top level of a block (which would be unnecessary), and we haven't
+already been asked to return the result (because statements know how to
+return results).
+
compileToFragments: (o, lvl) ->
+ o = extend {}, o
+ o.level = lvl if lvl
+ node = @unfoldSoak(o) or this
+ node.tab = o.indent
+ if o.level is LEVEL_TOP or not node.isStatement(o)
+ node.compileNode o
+ else
+ node.compileClosure o
+
@@ -253,23 +281,26 @@ all the CodeFragments'
code
snippets, in order.
-
The Base is the abstract base class for all nodes in the syntax tree.
-Each subclass implements the compileNode
method, which performs the
-code generation for that node. To compile a node to JavaScript,
-call compile
on it, which wraps compileNode
in some generic extra smarts,
-to know when the generated code needs to be wrapped up in a closure.
-An options hash is passed and cloned throughout, containing information about
-the environment from higher in the tree (such as if a returned value is
-being requested by the surrounding function), information about the current
-scope, and indentation level.
-
+
Statements converted into expressions via closure-wrapping share a scope
+object with their parent closure, to preserve the expected lexical scope.
- exports.Base = class Base
-
- compile: (o, lvl) ->
- fragmentsToText @compileToFragments o, lvl
+ compileClosure: (o) ->
+ if jumpNode = @jumps()
+ jumpNode.error 'cannot use a pure statement in an expression'
+ o.sharedScope = yes
+ func = new Code [], Block.wrap [this]
+ args = []
+ if (argumentsNode = @contains isLiteralArguments) or @contains isLiteralThis
+ args = [new Literal 'this']
+ if argumentsNode
+ meth = 'apply'
+ args.push new Literal 'arguments'
+ else
+ meth = 'call'
+ func = new Value func, [new Access new Literal meth]
+ (new Call func, args).compileNode o
@@ -280,25 +311,26 @@ scope, and indentation level.
- Common logic for determining whether to wrap this node in a closure before
-compiling it, or to compile directly. We need to wrap if this node is a
-statement, and it's not a pureStatement, and we're not at
-the top level of a block (which would be unnecessary), and we haven't
-already been asked to return the result (because statements know how to
-return results).
-
+ If the code generation wishes to use the result of a complex expression
+in multiple places, ensure that the expression is only ever evaluated once,
+by assigning it to a temporary variable. Pass a level to precompile.
+If level
is passed, then returns [val, ref]
, where val
is the compiled value, and ref
+is the compiled reference. If level
is not passed, this returns [val, ref]
where
+the two values are raw nodes which have not been compiled.
- compileToFragments: (o, lvl) ->
- o = extend {}, o
- o.level = lvl if lvl
- node = @unfoldSoak(o) or this
- node.tab = o.indent
- if o.level is LEVEL_TOP or not node.isStatement(o)
- node.compileNode o
- else
- node.compileClosure o
+ cache: (o, level, reused) ->
+ unless @isComplex()
+ ref = if level then @compileToFragments o, level else this
+ [ref, ref]
+ else
+ ref = new Literal reused or o.scope.freeVariable 'ref'
+ sub = new Assign ref, this
+ if level then [sub.compileToFragments(o, level), [@makeCode(ref.value)]] else [sub, ref]
+
+ cacheToCodeFragments: (cacheValues) ->
+ [fragmentsToText(cacheValues[0]), fragmentsToText(cacheValues[1])]
@@ -309,17 +341,18 @@ return results).
- Statements converted into expressions via closure-wrapping share a scope
-object with their parent closure, to preserve the expected lexical scope.
-
+ Construct a node that returns the current node's result.
+Note that this is overridden for smarter behavior for
+many statement nodes (e.g. If, For)...
- compileClosure: (o) ->
- if jumpNode = @jumps()
- jumpNode.error 'cannot use a pure statement in an expression'
- o.sharedScope = yes
- Closure.wrap(this).compileNode o
+ makeReturn: (res) ->
+ me = @unwrapAll()
+ if res
+ new Call new Literal("#{res}.push"), [me]
+ else
+ new Return me
@@ -330,29 +363,20 @@ object with their parent closure, to preserve the expected lexical scope.
- If the code generation wishes to use the result of a complex expression
-in multiple places, ensure that the expression is only ever evaluated once,
-by assigning it to a temporary variable. Pass a level to precompile.
-
-
-If level
is passed, then returns [val, ref]
, where val
is the compiled value, and ref
-is the compiled reference. If level
is not passed, this returns [val, ref]
where
-the two values are raw nodes which have not been compiled.
-
+ Does this node, or any of its children, contain a node of a certain kind?
+Recursively traverses down the children nodes and returns the first one
+that verifies pred
. Otherwise return undefined. contains
does not cross
+scope boundaries.
- cache: (o, level, reused) ->
- unless @isComplex()
- ref = if level then @compileToFragments o, level else this
- [ref, ref]
- else
- ref = new Literal reused or o.scope.freeVariable 'ref'
- sub = new Assign ref, this
- if level then [sub.compileToFragments(o, level), [@makeCode(ref.value)]] else [sub, ref]
-
- cacheToCodeFragments: (cacheValues) ->
- [fragmentsToText(cacheValues[0]), fragmentsToText(cacheValues[1])]
+ contains: (pred) ->
+ node = undefined
+ @traverseChildren no, (n) ->
+ if pred n
+ node = n
+ return no
+ node
@@ -363,19 +387,14 @@ the two values are raw nodes which have not been compiled.
- Construct a node that returns the current node's result.
-Note that this is overridden for smarter behavior for
-many statement nodes (e.g. If, For)...
-
+ Pull out the last non-comment node of a node list.
- makeReturn: (res) ->
- me = @unwrapAll()
- if res
- new Call new Literal("#{res}.push"), [me]
- else
- new Return me
+ lastNonComment: (list) ->
+ i = list.length
+ return list[i] while i-- when list[i] not instanceof Comment
+ null
@@ -386,21 +405,16 @@ many statement nodes (e.g. If, For)...
- Does this node, or any of its children, contain a node of a certain kind?
-Recursively traverses down the children nodes and returns the first one
-that verifies pred
. Otherwise return undefined. contains
does not cross
-scope boundaries.
-
+ toString
representation of the node, for inspecting the parse tree.
+This is what coffee --nodes
prints out.
- contains: (pred) ->
- node = undefined
- @traverseChildren no, (n) ->
- if pred n
- node = n
- return no
- node
+ toString: (idt = '', name = @constructor.name) ->
+ tree = '\n' + idt + name
+ tree += '?' if @soak
+ @eachChild (node) -> tree += node.toString idt + TAB
+ tree
@@ -411,15 +425,29 @@ scope boundaries.
- Pull out the last non-comment node of a node list.
-
+ Passes each child to a function, breaking when the function returns false
.
- lastNonComment: (list) ->
- i = list.length
- return list[i] while i-- when list[i] not instanceof Comment
- null
+ eachChild: (func) ->
+ return this unless @children
+ for attr in @children when @[attr]
+ for child in flatten [@[attr]]
+ return this if func(child) is false
+ this
+
+ traverseChildren: (crossScope, func) ->
+ @eachChild (child) ->
+ recur = func(child)
+ child.traverseChildren(crossScope, func) unless recur is no
+
+ invert: ->
+ new Op '!', this
+
+ unwrapAll: ->
+ node = this
+ continue until node is node = node.unwrap()
+ node
@@ -430,68 +458,12 @@ scope boundaries.
- toString
representation of the node, for inspecting the parse tree.
-This is what coffee --nodes
prints out.
-
-
-
-
- toString: (idt = '', name = @constructor.name) ->
- tree = '\n' + idt + name
- tree += '?' if @soak
- @eachChild (node) -> tree += node.toString idt + TAB
- tree
-
-
-
-
-
-
-
-
-
Passes each child to a function, breaking when the function returns false
.
-
-
-
-
- eachChild: (func) ->
- return this unless @children
- for attr in @children when @[attr]
- for child in flatten [@[attr]]
- return this if func(child) is false
- this
-
- traverseChildren: (crossScope, func) ->
- @eachChild (child) ->
- recur = func(child)
- child.traverseChildren(crossScope, func) unless recur is no
-
- invert: ->
- new Op '!', this
-
- unwrapAll: ->
- node = this
- continue until node is node = node.unwrap()
- node
-
-
-
-
-
-
-
-
Default implementations of the common node properties and methods. Nodes
-will override these with custom logic, if needed.
-
+will override these with custom logic, if needed.
- children: []
+ children: []
isStatement : NO
jumps : NO
@@ -505,18 +477,60 @@ will override these with custom logic, if needed.
+
+
+
+
+
Is this node used to assign a certain variable?
+
+
+
+
+
+
+
+
+
+
+
+
+
For this node and all descendents, set the location data to locationData
+if the location data is not already set.
+
+
+
+ updateLocationDataIfMissing: (locationData) ->
+ return this if @locationData
+ @locationData = locationData
+
+ @eachChild (child) ->
+ child.updateLocationDataIfMissing locationData
+
+
+
+
-
Is this node used to assign a certain variable?
-
+
Throw a SyntaxError associated with this node's location.
-
+ error: (message) ->
+ throwSyntaxError message, @locationData
+
+ makeCode: (code) ->
+ new CodeFragment this, code
+
+ wrapInBraces: (fragments) ->
+ [].concat @makeCode('('), fragments, @makeCode(')')
@@ -527,17 +541,18 @@ will override these with custom logic, if needed.
-
For this node and all descendents, set the location data to locationData
-if the location data is not already set.
-
+
fragmentsList
is an array of arrays of fragments. Each array in fragmentsList will be
+concatonated together, with joinStr
added in between each, to produce a final flat array
+of fragments.
-
updateLocationDataIfMissing: (locationData) ->
- @locationData or= locationData
-
- @eachChild (child) ->
- child.updateLocationDataIfMissing locationData
+
joinFragmentArrays: (fragmentsList, joinStr) ->
+ answer = []
+ for fragments,i in fragmentsList
+ if i then answer.push @makeCode joinStr
+ answer = answer.concat fragments
+ answer
@@ -548,19 +563,18 @@ if the location data is not already set.
-
Throw a SyntaxError associated with this node's location.
-
+
Block
+
The block is the list of expressions that forms the body of an
+indented block of code -- the implementation of a function, a clause in an
+if
, switch
, or try
, and so on...
- error: (message) ->
- throwSyntaxError message, @locationData
+ exports.Block = class Block extends Base
+ constructor: (nodes) ->
+ @expressions = compact flatten nodes or []
- makeCode: (code) ->
- new CodeFragment this, code
-
- wrapInBraces: (fragments) ->
- [].concat @makeCode('('), fragments, @makeCode(')')
+ children: ['expressions']
@@ -571,19 +585,13 @@ if the location data is not already set.
- fragmentsList
is an array of arrays of fragments. Each array in fragmentsList will be
-concatonated together, with joinStr
added in between each, to produce a final flat array
-of fragments.
-
+ Tack an expression on to the end of this expression list.
-
joinFragmentArrays: (fragmentsList, joinStr) ->
- answer = []
- for fragments,i in fragmentsList
- if i then answer.push @makeCode joinStr
- answer = answer.concat fragments
- answer
+
push: (node) ->
+ @expressions.push node
+ this
@@ -591,13 +599,16 @@ of fragments.
-
+
-
Block
+
Remove and return the last expression of this expression list.
+
pop: ->
+ @expressions.pop()
+
@@ -607,18 +618,13 @@ of fragments.
-
The block is the list of expressions that forms the body of an
-indented block of code -- the implementation of a function, a clause in an
-if
, switch
, or try
, and so on...
-
+
Add an expression at the beginning of this expression list.
- exports.Block = class Block extends Base
- constructor: (nodes) ->
- @expressions = compact flatten nodes or []
-
- children: ['expressions']
+ unshift: (node) ->
+ @expressions.unshift node
+ this
@@ -629,14 +635,13 @@ indented block of code -- the implementation of a function, a clause in an
-
Tack an expression on to the end of this expression list.
-
+
If this Block consists of just a single node, unwrap it by pulling
+it back out.
- push: (node) ->
- @expressions.push node
- this
+ unwrap: ->
+ if @expressions.length is 1 then @expressions[0] else this
@@ -647,13 +652,21 @@ indented block of code -- the implementation of a function, a clause in an
- Remove and return the last expression of this expression list.
-
+ Is this an empty block of code?
- pop: ->
- @expressions.pop()
+ isEmpty: ->
+ not @expressions.length
+
+ isStatement: (o) ->
+ for exp in @expressions when exp.isStatement o
+ return yes
+ no
+
+ jumps: (o) ->
+ for exp in @expressions
+ return jumpNode if jumpNode = exp.jumps o
@@ -664,14 +677,20 @@ indented block of code -- the implementation of a function, a clause in an
- Add an expression at the beginning of this expression list.
-
+ A Block node does not return its entire body, rather it
+ensures that the final expression is returned.
- unshift: (node) ->
- @expressions.unshift node
- this
+ makeReturn: (res) ->
+ len = @expressions.length
+ while len--
+ expr = @expressions[len]
+ if expr not instanceof Comment
+ @expressions[len] = expr.makeReturn res
+ @expressions.splice(len, 1) if expr instanceof Return and not expr.expression
+ break
+ this
@@ -682,14 +701,12 @@ indented block of code -- the implementation of a function, a clause in an
- If this Block consists of just a single node, unwrap it by pulling
-it back out.
-
+ A Block is the only node that can serve as the root.
- unwrap: ->
- if @expressions.length is 1 then @expressions[0] else this
+ compileToFragments: (o = {}, level) ->
+ if o.scope then super o, level else @compileRoot o
@@ -700,22 +717,22 @@ it back out.
- Is this an empty block of code?
-
+ Compile all expressions within the Block body. If we need to
+return the result, and it's an expression, simply return it. If it's a
+statement, ask the statement to do so.
- isEmpty: ->
- not @expressions.length
+ compileNode: (o) ->
+ @tab = o.indent
+ top = o.level is LEVEL_TOP
+ compiledNodes = []
- isStatement: (o) ->
- for exp in @expressions when exp.isStatement o
- return yes
- no
+ for node, index in @expressions
- jumps: (o) ->
- for exp in @expressions
- return exp if exp.jumps o
+ node = node.unwrapAll()
+ node = (node.unfoldSoak(o) or node)
+ if node instanceof Block
@@ -726,21 +743,32 @@ it back out.
- A Block node does not return its entire body, rather it
-ensures that the final expression is returned.
-
+ This is a nested block. We don't do anything special here like enclose
+it in a new scope; we just compile the statements in this block along with
+our own
- makeReturn: (res) ->
- len = @expressions.length
- while len--
- expr = @expressions[len]
- if expr not instanceof Comment
- @expressions[len] = expr.makeReturn res
- @expressions.splice(len, 1) if expr instanceof Return and not expr.expression
- break
- this
+ compiledNodes.push node.compileNode o
+ else if top
+ node.front = true
+ fragments = node.compileToFragments o
+ unless node.isStatement o
+ fragments.unshift @makeCode "#{@tab}"
+ fragments.push @makeCode ";"
+ compiledNodes.push fragments
+ else
+ compiledNodes.push node.compileToFragments o, LEVEL_LIST
+ if top
+ if @spaced
+ return [].concat @joinFragmentArrays(compiledNodes, '\n\n'), @makeCode("\n")
+ else
+ return @joinFragmentArrays(compiledNodes, '\n')
+ if compiledNodes.length
+ answer = @joinFragmentArrays(compiledNodes, ', ')
+ else
+ answer = [@makeCode "void 0"]
+ if compiledNodes.length > 1 and o.level >= LEVEL_LIST then @wrapInBraces answer else answer
@@ -751,13 +779,18 @@ ensures that the final expression is returned.
- A Block is the only node that can serve as the root.
-
+ If we happen to be the top-level Block, wrap everything in
+a safety closure, unless requested not to.
+It would be better not to generate them in the first place, but for now,
+clean up obvious double-parentheses.
- compileToFragments: (o = {}, level) ->
- if o.scope then super o, level else @compileRoot o
+ compileRoot: (o) ->
+ o.indent = if o.bare then '' else TAB
+ o.level = LEVEL_TOP
+ @spaced = yes
+ o.scope = new Scope null, this, null
@@ -768,23 +801,26 @@ ensures that the final expression is returned.
- Compile all expressions within the Block body. If we need to
-return the result, and it's an expression, simply return it. If it's a
-statement, ask the statement to do so.
-
+ Mark given local variables in the root scope as parameters so they don't
+end up being declared on this block.
- compileNode: (o) ->
- @tab = o.indent
- top = o.level is LEVEL_TOP
- compiledNodes = []
-
- for node, index in @expressions
-
- node = node.unwrapAll()
- node = (node.unfoldSoak(o) or node)
- if node instanceof Block
+ o.scope.parameter name for name in o.locals or []
+ prelude = []
+ unless o.bare
+ preludeExps = for exp, i in @expressions
+ break unless exp.unwrap() instanceof Comment
+ exp
+ rest = @expressions[preludeExps.length...]
+ @expressions = preludeExps
+ if preludeExps.length
+ prelude = @compileNode merge(o, indent: '')
+ prelude.push @makeCode "\n"
+ @expressions = rest
+ fragments = @compileWithDeclarations o
+ return fragments if o.bare
+ [].concat prelude, @makeCode("(function() {\n"), fragments, @makeCode("\n}).call(this);\n")
@@ -795,33 +831,40 @@ statement, ask the statement to do so.
- This is a nested block. We don't do anything special here like enclose
-it in a new scope; we just compile the statements in this block along with
-our own
-
+ Compile the expressions body for the contents of a function, with
+declarations of all inner variables pushed up to the top.
- compiledNodes.push node.compileNode o
- else if top
- node.front = true
- fragments = node.compileToFragments o
- unless node.isStatement o
- fragments.unshift @makeCode "#{@tab}"
- fragments.push @makeCode ";"
- compiledNodes.push fragments
- else
- compiledNodes.push node.compileToFragments o, LEVEL_LIST
- if top
- if @spaced
- return [].concat @joinFragmentArrays(compiledNodes, '\n\n'), @makeCode("\n")
- else
- return @joinFragmentArrays(compiledNodes, '\n')
- if compiledNodes.length
- answer = @joinFragmentArrays(compiledNodes, ', ')
- else
- answer = [@makeCode "void 0"]
- if compiledNodes.length > 1 and o.level >= LEVEL_LIST then @wrapInBraces answer else answer
+ compileWithDeclarations: (o) ->
+ fragments = []
+ post = []
+ for exp, i in @expressions
+ exp = exp.unwrap()
+ break unless exp instanceof Comment or exp instanceof Literal
+ o = merge(o, level: LEVEL_TOP)
+ if i
+ rest = @expressions.splice i, 9e9
+ [spaced, @spaced] = [@spaced, no]
+ [fragments, @spaced] = [@compileNode(o), spaced]
+ @expressions = rest
+ post = @compileNode o
+ {scope} = o
+ if scope.expressions is this
+ declars = o.scope.hasDeclarations()
+ assigns = scope.hasAssignments
+ if declars or assigns
+ fragments.push @makeCode '\n' if i
+ fragments.push @makeCode "#{@tab}var "
+ if declars
+ fragments.push @makeCode scope.declaredVariables().join(', ')
+ if assigns
+ fragments.push @makeCode ",\n#{@tab + TAB}" if declars
+ fragments.push @makeCode scope.assignedVariables().join(",\n#{@tab + TAB}")
+ fragments.push @makeCode ";\n#{if @spaced then '\n' else ''}"
+ else if fragments.length and post.length
+ fragments.push @makeCode "\n"
+ fragments.concat post
@@ -832,19 +875,14 @@ our own
- If we happen to be the top-level Block, wrap everything in
-a safety closure, unless requested not to.
-It would be better not to generate them in the first place, but for now,
-clean up obvious double-parentheses.
-
+ Wrap up the given nodes as a Block, unless it already happens
+to be one.
- compileRoot: (o) ->
- o.indent = if o.bare then '' else TAB
- o.level = LEVEL_TOP
- @spaced = yes
- o.scope = new Scope null, this, null
+ @wrap: (nodes) ->
+ return nodes[0] if nodes.length is 1 and nodes[0] instanceof Block
+ new Block nodes
@@ -855,27 +893,63 @@ clean up obvious double-parentheses.
- Mark given local variables in the root scope as parameters so they don't
-end up being declared on this block.
-
+ Literal
+Literals are static values that can be passed through directly into
+JavaScript without translation, such as: strings, numbers,
+true
, false
, null
...
- o.scope.parameter name for name in o.locals or []
- prelude = []
- unless o.bare
- preludeExps = for exp, i in @expressions
- break unless exp.unwrap() instanceof Comment
- exp
- rest = @expressions[preludeExps.length...]
- @expressions = preludeExps
- if preludeExps.length
- prelude = @compileNode merge(o, indent: '')
- prelude.push @makeCode "\n"
- @expressions = rest
- fragments = @compileWithDeclarations o
- return fragments if o.bare
- [].concat prelude, @makeCode("(function() {\n"), fragments, @makeCode("\n}).call(this);\n")
+ exports.Literal = class Literal extends Base
+ constructor: (@value) ->
+
+ makeReturn: ->
+ if @isStatement() then this else super
+
+ isAssignable: ->
+ IDENTIFIER.test @value
+
+ isStatement: ->
+ @value in ['break', 'continue', 'debugger']
+
+ isComplex: NO
+
+ assigns: (name) ->
+ name is @value
+
+ jumps: (o) ->
+ return this if @value is 'break' and not (o?.loop or o?.block)
+ return this if @value is 'continue' and not o?.loop
+
+ compileNode: (o) ->
+ code = if @value is 'this'
+ if o.scope.method?.bound then o.scope.method.context else @value
+ else if @value.reserved
+ "\"#{@value}\""
+ else
+ @value
+ answer = if @isStatement() then "#{@tab}#{code};" else code
+ [@makeCode answer]
+
+ toString: ->
+ ' "' + @value + '"'
+
+class exports.Undefined extends Base
+ isAssignable: NO
+ isComplex: NO
+ compileNode: (o) ->
+ [@makeCode if o.level >= LEVEL_ACCESS then '(void 0)' else 'void 0']
+
+class exports.Null extends Base
+ isAssignable: NO
+ isComplex: NO
+ compileNode: -> [@makeCode "null"]
+
+class exports.Bool extends Base
+ isAssignable: NO
+ isComplex: NO
+ compileNode: -> [@makeCode @val]
+ constructor: (@val) ->
@@ -886,41 +960,28 @@ end up being declared on this block.
- Compile the expressions body for the contents of a function, with
-declarations of all inner variables pushed up to the top.
-
+ Return
+A return
is a pureStatement -- wrapping it in a closure wouldn't
+make sense.
- compileWithDeclarations: (o) ->
- fragments = []
- post = []
- for exp, i in @expressions
- exp = exp.unwrap()
- break unless exp instanceof Comment or exp instanceof Literal
- o = merge(o, level: LEVEL_TOP)
- if i
- rest = @expressions.splice i, 9e9
- [spaced, @spaced] = [@spaced, no]
- [fragments, @spaced] = [@compileNode(o), spaced]
- @expressions = rest
- post = @compileNode o
- {scope} = o
- if scope.expressions is this
- declars = o.scope.hasDeclarations()
- assigns = scope.hasAssignments
- if declars or assigns
- fragments.push @makeCode '\n' if i
- fragments.push @makeCode "#{@tab}var "
- if declars
- fragments.push @makeCode scope.declaredVariables().join(', ')
- if assigns
- fragments.push @makeCode ",\n#{@tab + TAB}" if declars
- fragments.push @makeCode scope.assignedVariables().join(",\n#{@tab + TAB}")
- fragments.push @makeCode ";\n#{if @spaced then '\n' else ''}"
- else if fragments.length and post.length
- fragments.push @makeCode "\n"
- fragments.concat post
+ exports.Return = class Return extends Base
+ constructor: (expr) ->
+ @expression = expr if expr and not expr.unwrap().isUndefined
+
+ children: ['expression']
+
+ isStatement: YES
+ makeReturn: THIS
+ jumps: THIS
+
+ compileToFragments: (o, level) ->
+ expr = @expression?.makeReturn()
+ if expr and expr not instanceof Return then expr.compileToFragments o, level else super o, level
+
+ compileNode: (o) ->
+ answer = []
@@ -931,15 +992,15 @@ declarations of all inner variables pushed up to the top.
- Wrap up the given nodes as a Block, unless it already happens
-to be one.
-
+ TODO: If we call expression.compile() here twice, we'll sometimes get back different results!
- @wrap: (nodes) ->
- return nodes[0] if nodes.length is 1 and nodes[0] instanceof Block
- new Block nodes
+ answer.push @makeCode @tab + "return#{if @expression then " " else ""}"
+ if @expression
+ answer = answer.concat @expression.compileToFragments o, LEVEL_PAREN
+ answer.push @makeCode ";"
+ return answer
@@ -947,13 +1008,25 @@ to be one.
-
+
-
Literal
+
Value
+
A value, variable or literal or parenthesized, indexed or dotted into,
+or vanilla.
+
exports.Value = class Value extends Base
+ constructor: (base, props, tag) ->
+ return base if not props and base instanceof Value
+ @base = base
+ @properties = props or []
+ @[tag] = true if tag
+ return this
+
+ children: ['base', 'properties']
+
@@ -963,63 +1036,19 @@ to be one.
-
Literals are static values that can be passed through directly into
-JavaScript without translation, such as: strings, numbers,
-true
, false
, null
...
-
+
Add a property (or properties ) Access
to the list.
- exports.Literal = class Literal extends Base
- constructor: (@value) ->
+ add: (props) ->
+ @properties = @properties.concat props
+ this
- makeReturn: ->
- if @isStatement() then this else super
+ hasProperties: ->
+ !!@properties.length
- isAssignable: ->
- IDENTIFIER.test @value
-
- isStatement: ->
- @value in ['break', 'continue', 'debugger']
-
- isComplex: NO
-
- assigns: (name) ->
- name is @value
-
- jumps: (o) ->
- return this if @value is 'break' and not (o?.loop or o?.block)
- return this if @value is 'continue' and not o?.loop
-
- compileNode: (o) ->
- code = if @value is 'this'
- if o.scope.method?.bound then o.scope.method.context else @value
- else if @value.reserved
- "\"#{@value}\""
- else
- @value
- answer = if @isStatement() then "#{@tab}#{code};" else code
- [@makeCode answer]
-
- toString: ->
- ' "' + @value + '"'
-
-class exports.Undefined extends Base
- isAssignable: NO
- isComplex: NO
- compileNode: (o) ->
- [@makeCode if o.level >= LEVEL_ACCESS then '(void 0)' else 'void 0']
-
-class exports.Null extends Base
- isAssignable: NO
- isComplex: NO
- compileNode: -> [@makeCode "null"]
-
-class exports.Bool extends Base
- isAssignable: NO
- isComplex: NO
- compileNode: -> [@makeCode @val]
- constructor: (@val) ->
+ bareLiteral: (type) ->
+ not @properties.length and @base instanceof type
@@ -1027,13 +1056,43 @@ JavaScript without translation, such as: strings, numbers,
-
+
-
Return
+
Some boolean checks for the benefit of other nodes.
+
isArray : -> @bareLiteral(Arr)
+ isRange : -> @bareLiteral(Range)
+ isComplex : -> @hasProperties() or @base.isComplex()
+ isAssignable : -> @hasProperties() or @base.isAssignable()
+ isSimpleNumber : -> @bareLiteral(Literal) and SIMPLENUM.test @base.value
+ isString : -> @bareLiteral(Literal) and IS_STRING.test @base.value
+ isRegex : -> @bareLiteral(Literal) and IS_REGEX.test @base.value
+ isAtomic : ->
+ for node in @properties.concat @base
+ return no if node.soak or node instanceof Call
+ yes
+
+ isNotCallable : -> @isSimpleNumber() or @isString() or @isRegex() or
+ @isArray() or @isRange() or @isSplice() or @isObject()
+
+ isStatement : (o) -> not @properties.length and @base.isStatement o
+ assigns : (name) -> not @properties.length and @base.assigns name
+ jumps : (o) -> not @properties.length and @base.jumps o
+
+ isObject: (onlyGenerated) ->
+ return no if @properties.length
+ (@base instanceof Obj) and (not onlyGenerated or @base.generated)
+
+ isSplice: ->
+ last(@properties) instanceof Slice
+
+ looksStatic: (className) ->
+ @base.value is className and @properties.length and
+ @properties[0].name?.value isnt 'prototype'
+
@@ -1043,28 +1102,13 @@ JavaScript without translation, such as: strings, numbers,
-
A return
is a pureStatement -- wrapping it in a closure wouldn't
-make sense.
-
+
The value can be unwrapped as its inner node, if there are no attached
+properties.
- exports.Return = class Return extends Base
- constructor: (expr) ->
- @expression = expr if expr and not expr.unwrap().isUndefined
-
- children: ['expression']
-
- isStatement: YES
- makeReturn: THIS
- jumps: THIS
-
- compileToFragments: (o, level) ->
- expr = @expression?.makeReturn()
- if expr and expr not instanceof Return then expr.compileToFragments o, level else super o, level
-
- compileNode: (o) ->
- answer = []
+ unwrap: ->
+ if @properties.length then this else @base
@@ -1075,16 +1119,26 @@ make sense.
- TODO: If we call expression.compile() here twice, we'll sometimes get back different results!
-
+ A reference has base part (this
value) and name part.
+We cache them separately for compiling complex expressions.
+a()[b()] ?= c
-> (_base = a())[_name = b()] ? _base[_name] = c
- answer.push @makeCode @tab + "return#{if @expression then " " else ""}"
- if @expression
- answer = answer.concat @expression.compileToFragments o, LEVEL_PAREN
- answer.push @makeCode ";"
- return answer
+ cacheReference: (o) ->
+ name = last @properties
+ if @properties.length < 2 and not @base.isComplex() and not name?.isComplex()
+ return [this, this]
+ base = new Value @base, @properties[...-1]
+ if base.isComplex()
+ bref = new Literal o.scope.freeVariable 'base'
+ base = new Value new Parens new Assign bref, base
+ return [base, bref] unless name
+ if name.isComplex()
+ nref = new Literal o.scope.freeVariable 'name'
+ name = new Index new Assign nref, name.index
+ nref = new Index nref
+ [base.add(name), new Value(bref or base.base, [nref or name])]
@@ -1092,13 +1146,26 @@ make sense.
-
+
-
Value
+
We compile a value to JavaScript by compiling and joining each property.
+Things get much more interesting if the chain of properties has soak
+operators ?.
interspersed. Then we have to take care not to accidentally
+evaluate anything twice when building the soak chain.
+
compileNode: (o) ->
+ @base.front = @front
+ props = @properties
+ fragments = @base.compileToFragments o, (if props.length then LEVEL_ACCESS else null)
+ if (@base instanceof Parens or props.length) and SIMPLENUM.test fragmentsToText fragments
+ fragments.push @makeCode '.'
+ for prop in props
+ fragments.push (prop.compileToFragments o)...
+ fragments
+
@@ -1108,21 +1175,25 @@ make sense.
-
A value, variable or literal or parenthesized, indexed or dotted into,
-or vanilla.
-
+
Unfold a soak into an If
: a?.b
-> a.b if a?
- exports.Value = class Value extends Base
- constructor: (base, props, tag) ->
- return base if not props and base instanceof Value
- @base = base
- @properties = props or []
- @[tag] = true if tag
- return this
-
- children: ['base', 'properties']
+ unfoldSoak: (o) ->
+ @unfoldedSoak ?= do =>
+ if ifn = @base.unfoldSoak o
+ ifn.body.properties.push @properties...
+ return ifn
+ for prop, i in @properties when prop.soak
+ prop.soak = off
+ fst = new Value @base, @properties[...i]
+ snd = new Value @base, @properties[i..]
+ if fst.isComplex()
+ ref = new Literal o.scope.freeVariable 'ref'
+ fst = new Parens new Assign ref, fst
+ snd.base = ref
+ return new If new Existence(fst), snd, soak: on
+ no
@@ -1133,17 +1204,23 @@ or vanilla.
- Add a property (or properties ) Access
to the list.
-
+
+CoffeeScript passes through block comments as JavaScript block comments
+at the same position.
- add: (props) ->
- @properties = @properties.concat props
- this
+ exports.Comment = class Comment extends Base
+ constructor: (@comment) ->
- hasProperties: ->
- !!@properties.length
+ isStatement: YES
+ makeReturn: THIS
+
+ compileNode: (o, level) ->
+ comment = @comment.replace /^(\s*)#/gm, "$1 *"
+ code = "/*#{multident comment, @tab}#{if '\n' in comment then "\n#{@tab}" else ''} */"
+ code = o.indent + code if (level or o.level) is LEVEL_TOP
+ [@makeCode("\n"), @makeCode(code)]
@@ -1154,31 +1231,21 @@ or vanilla.
- Some boolean checks for the benefit of other nodes.
-
+ Call
+Node for a function invocation. Takes care of converting super()
calls into
+calls against the prototype's function of the same name.
- isArray : -> not @properties.length and @base instanceof Arr
- isComplex : -> @hasProperties() or @base.isComplex()
- isAssignable : -> @hasProperties() or @base.isAssignable()
- isSimpleNumber : -> @base instanceof Literal and SIMPLENUM.test @base.value
- isString : -> @base instanceof Literal and IS_STRING.test @base.value
- isAtomic : ->
- for node in @properties.concat @base
- return no if node.soak or node instanceof Call
- yes
+ exports.Call = class Call extends Base
+ constructor: (variable, @args = [], @soak) ->
+ @isNew = false
+ @isSuper = variable is 'super'
+ @variable = if @isSuper then null else variable
+ if variable instanceof Value and variable.isNotCallable()
+ variable.error "literal is not a function"
- isStatement : (o) -> not @properties.length and @base.isStatement o
- assigns : (name) -> not @properties.length and @base.assigns name
- jumps : (o) -> not @properties.length and @base.jumps o
-
- isObject: (onlyGenerated) ->
- return no if @properties.length
- (@base instanceof Obj) and (not onlyGenerated or @base.generated)
-
- isSplice: ->
- last(@properties) instanceof Slice
+ children: ['variable', 'args']
@@ -1189,14 +1256,17 @@ or vanilla.
- The value can be unwrapped as its inner node, if there are no attached
-properties.
-
+ Tag this invocation as creating a new instance.
- unwrap: ->
- if @properties.length then this else @base
+ newInstance: ->
+ base = @variable?.base or @variable
+ if base instanceof Call and not base.isNew
+ base.newInstance()
+ else
+ @isNew = true
+ this
@@ -1207,27 +1277,22 @@ properties.
- A reference has base part (this
value) and name part.
-We cache them separately for compiling complex expressions.
-a()[b()] ?= c
-> (_base = a())[_name = b()] ? _base[_name] = c
-
+ Grab the reference to the superclass's implementation of the current
+method.
- cacheReference: (o) ->
- name = last @properties
- if @properties.length < 2 and not @base.isComplex() and not name?.isComplex()
- return [this, this]
- base = new Value @base, @properties[...-1]
- if base.isComplex()
- bref = new Literal o.scope.freeVariable 'base'
- base = new Value new Parens new Assign bref, base
- return [base, bref] unless name
- if name.isComplex()
- nref = new Literal o.scope.freeVariable 'name'
- name = new Index new Assign nref, name.index
- nref = new Index nref
- [base.add(name), new Value(bref or base.base, [nref or name])]
+ superReference: (o) ->
+ method = o.scope.namedMethod()
+ if method?.klass
+ accesses = [new Access(new Literal '__super__')]
+ accesses.push new Access new Literal 'constructor' if method.static
+ accesses.push new Access new Literal method.name
+ (new Value (new Literal method.klass), accesses).compile o
+ else if method?.ctor
+ "#{method.name}.__super__.constructor"
+ else
+ @error 'cannot call super outside of an instance method.'
@@ -1238,23 +1303,13 @@ We cache them separately for compiling complex expressions.
- We compile a value to JavaScript by compiling and joining each property.
-Things get much more interesting if the chain of properties has soak
-operators ?.
interspersed. Then we have to take care not to accidentally
-evaluate anything twice when building the soak chain.
-
+ The appropriate this
value for a super
call.
- compileNode: (o) ->
- @base.front = @front
- props = @properties
- fragments = @base.compileToFragments o, (if props.length then LEVEL_ACCESS else null)
- if (@base instanceof Parens or props.length) and SIMPLENUM.test fragmentsToText fragments
- fragments.push @makeCode '.'
- for prop in props
- fragments.push (prop.compileToFragments o)...
- fragments
+ superThis : (o) ->
+ method = o.scope.method
+ (method and not method.klass and method.context) or "this"
@@ -1265,26 +1320,40 @@ evaluate anything twice when building the soak chain.
- Unfold a soak into an If
: a?.b
-> a.b if a?
-
+ Soaked chained invocations unfold into if/else ternary structures.
- unfoldSoak: (o) ->
- @unfoldedSoak ?= do =>
- if ifn = @base.unfoldSoak o
- ifn.body.properties.push @properties...
- return ifn
- for prop, i in @properties when prop.soak
- prop.soak = off
- fst = new Value @base, @properties[...i]
- snd = new Value @base, @properties[i..]
- if fst.isComplex()
- ref = new Literal o.scope.freeVariable 'ref'
- fst = new Parens new Assign ref, fst
- snd.base = ref
- return new If new Existence(fst), snd, soak: on
- no
+ unfoldSoak: (o) ->
+ if @soak
+ if @variable
+ return ifn if ifn = unfoldSoak o, this, 'variable'
+ [left, rite] = new Value(@variable).cacheReference o
+ else
+ left = new Literal @superReference o
+ rite = new Value left
+ rite = new Call rite, @args
+ rite.isNew = @isNew
+ left = new Literal "typeof #{ left.compile o } === \"function\""
+ return new If left, new Value(rite), soak: yes
+ call = this
+ list = []
+ loop
+ if call.variable instanceof Call
+ list.push call
+ call = call.variable
+ continue
+ break unless call.variable instanceof Value
+ list.push call
+ break unless (call = call.variable.base) instanceof Call
+ for call in list.reverse()
+ if ifn
+ if call.variable instanceof Call
+ call.variable = ifn
+ else
+ call.variable.base = ifn
+ ifn = unfoldSoak o, call, 'variable'
+ ifn
@@ -1292,13 +1361,36 @@ evaluate anything twice when building the soak chain.
-
+
-
Comment
+
Compile a vanilla function call.
+
compileNode: (o) ->
+ @variable?.front = @front
+ compiledArray = Splat.compileSplattedArray o, @args, true
+ if compiledArray.length
+ return @compileSplat o, compiledArray
+ compiledArgs = []
+ for arg, argIndex in @args
+ if argIndex then compiledArgs.push @makeCode ", "
+ compiledArgs.push (arg.compileToFragments o, LEVEL_LIST)...
+
+ fragments = []
+ if @isSuper
+ preface = @superReference(o) + ".call(#{@superThis(o)}"
+ if compiledArgs.length then preface += ", "
+ fragments.push @makeCode preface
+ else
+ if @isNew then fragments.push @makeCode 'new '
+ fragments.push @variable.compileToFragments(o, LEVEL_ACCESS)...
+ fragments.push @makeCode "("
+ fragments.push compiledArgs...
+ fragments.push @makeCode ")"
+ fragments
+
@@ -1308,22 +1400,48 @@ evaluate anything twice when building the soak chain.
-
CoffeeScript passes through block comments as JavaScript block comments
-at the same position.
-
+
If you call a function with a splat, it's converted into a JavaScript
+.apply()
call to allow an array of arguments to be passed.
+If it's a constructor, then things get real tricky. We have to inject an
+inner constructor in order to be able to pass the varargs.
+
splatArgs is an array of CodeFragments to put into the 'apply'.
- exports.Comment = class Comment extends Base
- constructor: (@comment) ->
+ compileSplat: (o, splatArgs) ->
+ if @isSuper
+ return [].concat @makeCode("#{ @superReference o }.apply(#{@superThis(o)}, "),
+ splatArgs, @makeCode(")")
- isStatement: YES
- makeReturn: THIS
+ if @isNew
+ idt = @tab + TAB
+ return [].concat @makeCode("""
+ (function(func, args, ctor) {
+ #{idt}ctor.prototype = func.prototype;
+ #{idt}var child = new ctor, result = func.apply(child, args);
+ #{idt}return Object(result) === result ? result : child;
+ #{@tab}})("""),
+ (@variable.compileToFragments o, LEVEL_LIST),
+ @makeCode(", "), splatArgs, @makeCode(", function(){})")
- compileNode: (o, level) ->
- code = "/*#{multident @comment, @tab}#{if '\n' in @comment then "\n#{@tab}" else ''}*/\n"
- code = o.indent + code if (level or o.level) is LEVEL_TOP
- [@makeCode code]
+ answer = []
+ base = new Value @variable
+ if (name = base.properties.pop()) and base.isComplex()
+ ref = o.scope.freeVariable 'ref'
+ answer = answer.concat @makeCode("(#{ref} = "),
+ (base.compileToFragments o, LEVEL_LIST),
+ @makeCode(")"),
+ name.compileToFragments(o)
+ else
+ fun = base.compileToFragments o, LEVEL_ACCESS
+ fun = @wrapInBraces fun if SIMPLENUM.test fragmentsToText fun
+ if name
+ ref = fragmentsToText fun
+ fun.push (name.compileToFragments o)...
+ else
+ ref = 'null'
+ answer = answer.concat fun
+ answer = answer.concat @makeCode(".apply(#{ref}, "), splatArgs, @makeCode(")")
@@ -1331,13 +1449,21 @@ at the same position.
-
+
-
Call
+
Extends
+
Node to extend an object's prototype with an ancestor object.
+After goog.inherits
from the
+Closure Library.
+
exports.Extends = class Extends extends Base
+ constructor: (@child, @parent) ->
+
+ children: ['child', 'parent']
+
@@ -1347,19 +1473,12 @@ at the same position.
-
Node for a function invocation. Takes care of converting super()
calls into
-calls against the prototype's function of the same name.
-
+
Hooks one constructor into another's prototype chain.
- exports.Call = class Call extends Base
- constructor: (variable, @args = [], @soak) ->
- @isNew = false
- @isSuper = variable is 'super'
- @variable = if @isSuper then null else variable
-
- children: ['variable', 'args']
+ compileToFragments: (o) ->
+ new Call(new Value(new Literal utility 'extends'), [@child, @parent]).compileToFragments o
@@ -1370,18 +1489,29 @@ calls against the prototype's function of the same name.
- Tag this invocation as creating a new instance.
-
+ Access
+A .
access into a property of a value, or the ::
shorthand for
+an access into the object's prototype.
- newInstance: ->
- base = @variable?.base or @variable
- if base instanceof Call and not base.isNew
- base.newInstance()
- else
- @isNew = true
- this
+ exports.Access = class Access extends Base
+ constructor: (@name, tag) ->
+ @name.asKey = yes
+ @soak = tag is 'soak'
+
+ children: ['name']
+
+ compileToFragments: (o) ->
+ name = @name.compileToFragments o
+ if IDENTIFIER.test fragmentsToText name
+ name.unshift @makeCode "."
+ else
+ name.unshift @makeCode "["
+ name.push @makeCode "]"
+ name
+
+ isComplex: NO
@@ -1392,23 +1522,21 @@ calls against the prototype's function of the same name.
- Grab the reference to the superclass's implementation of the current
-method.
-
+ Index
+A [ ... ]
indexed access into an array or object.
- superReference: (o) ->
- method = o.scope.namedMethod()
- if method?.klass
- accesses = [new Access(new Literal '__super__')]
- accesses.push new Access new Literal 'constructor' if method.static
- accesses.push new Access new Literal method.name
- (new Value (new Literal method.klass), accesses).compile o
- else if method?.ctor
- "#{method.name}.__super__.constructor"
- else
- @error 'cannot call super outside of an instance method.'
+ exports.Index = class Index extends Base
+ constructor: (@index) ->
+
+ children: ['index']
+
+ compileToFragments: (o) ->
+ [].concat @makeCode("["), @index.compileToFragments(o, LEVEL_PAREN), @makeCode("]")
+
+ isComplex: ->
+ @index.isComplex()
@@ -1419,14 +1547,20 @@ method.
- The appropriate this
value for a super
call.
-
+ Range
+A range literal. Ranges can be used to extract portions (slices) of arrays,
+to specify a range for comprehensions, or as a value, to be expanded into the
+corresponding array of integers at runtime.
- superThis : (o) ->
- method = o.scope.method
- (method and not method.klass and method.context) or "this"
+ exports.Range = class Range extends Base
+
+ children: ['from', 'to']
+
+ constructor: (@from, @to, tag) ->
+ @exclusive = tag is 'exclusive'
+ @equals = if @exclusive then '' else '='
@@ -1437,41 +1571,18 @@ method.
- Soaked chained invocations unfold into if/else ternary structures.
-
+ Compiles the range's source variables -- where it starts and where it ends.
+But only if they need to be cached to avoid double evaluation.
- unfoldSoak: (o) ->
- if @soak
- if @variable
- return ifn if ifn = unfoldSoak o, this, 'variable'
- [left, rite] = new Value(@variable).cacheReference o
- else
- left = new Literal @superReference o
- rite = new Value left
- rite = new Call rite, @args
- rite.isNew = @isNew
- left = new Literal "typeof #{ left.compile o } === \"function\""
- return new If left, new Value(rite), soak: yes
- call = this
- list = []
- loop
- if call.variable instanceof Call
- list.push call
- call = call.variable
- continue
- break unless call.variable instanceof Value
- list.push call
- break unless (call = call.variable.base) instanceof Call
- for call in list.reverse()
- if ifn
- if call.variable instanceof Call
- call.variable = ifn
- else
- call.variable.base = ifn
- ifn = unfoldSoak o, call, 'variable'
- ifn
+ compileVariables: (o) ->
+ o = merge o, top: true
+ [@fromC, @fromVar] = @cacheToCodeFragments @from.cache o, LEVEL_LIST
+ [@toC, @toVar] = @cacheToCodeFragments @to.cache o, LEVEL_LIST
+ [@step, @stepVar] = @cacheToCodeFragments step.cache o, LEVEL_LIST if step = del o, 'step'
+ [@fromNum, @toNum] = [@fromVar.match(NUMBER), @toVar.match(NUMBER)]
+ @stepNum = @stepVar.match(NUMBER) if @stepVar
@@ -1482,33 +1593,14 @@ method.
- Compile a vanilla function call.
-
+ When compiled normally, the range returns the contents of the for loop
+needed to iterate over the values in the range. Used by comprehensions.
- compileNode: (o) ->
- @variable?.front = @front
- compiledArray = Splat.compileSplattedArray o, @args, true
- if compiledArray.length
- return @compileSplat o, compiledArray
- compiledArgs = []
- for arg, argIndex in @args
- if argIndex then compiledArgs.push @makeCode ", "
- compiledArgs.push (arg.compileToFragments o, LEVEL_LIST)...
-
- fragments = []
- if @isSuper
- preface = @superReference(o) + ".call(#{@superThis(o)}"
- if compiledArgs.length then preface += ", "
- fragments.push @makeCode preface
- else
- if @isNew then fragments.push @makeCode 'new '
- fragments.push @variable.compileToFragments(o, LEVEL_ACCESS)...
- fragments.push @makeCode "("
- fragments.push compiledArgs...
- fragments.push @makeCode ")"
- fragments
+ compileNode: (o) ->
+ @compileVariables o unless @fromVar
+ return @compileArray(o) unless o.index
@@ -1519,51 +1611,18 @@ method.
- If you call a function with a splat, it's converted into a JavaScript
-.apply()
call to allow an array of arguments to be passed.
-If it's a constructor, then things get real tricky. We have to inject an
-inner constructor in order to be able to pass the varargs.
-
-
-splatArgs is an array of CodeFragments to put into the 'apply'.
-
+ Set up endpoints.
- compileSplat: (o, splatArgs) ->
- if @isSuper
- return [].concat @makeCode("#{ @superReference o }.apply(#{@superThis(o)}, "),
- splatArgs, @makeCode(")")
-
- if @isNew
- idt = @tab + TAB
- return [].concat @makeCode("""
- (function(func, args, ctor) {
- #{idt}ctor.prototype = func.prototype;
- #{idt}var child = new ctor, result = func.apply(child, args);
- #{idt}return Object(result) === result ? result : child;
- #{@tab}})("""),
- (@variable.compileToFragments o, LEVEL_LIST),
- @makeCode(", "), splatArgs, @makeCode(", function(){})")
-
- answer = []
- base = new Value @variable
- if (name = base.properties.pop()) and base.isComplex()
- ref = o.scope.freeVariable 'ref'
- answer = answer.concat @makeCode("(#{ref} = "),
- (base.compileToFragments o, LEVEL_LIST),
- @makeCode(")"),
- name.compileToFragments(o)
- else
- fun = base.compileToFragments o, LEVEL_ACCESS
- fun = @wrapInBraces fun if SIMPLENUM.test fragmentsToText fun
- if name
- ref = fragmentsToText fun
- fun.push (name.compileToFragments o)...
- else
- ref = 'null'
- answer = answer.concat fun
- answer = answer.concat @makeCode(".apply(#{ref}, "), splatArgs, @makeCode(")")
+ known = @fromNum and @toNum
+ idx = del o, 'index'
+ idxName = del o, 'name'
+ namedIndex = idxName and idxName isnt idx
+ varPart = "#{idx} = #{@fromC}"
+ varPart += ", #{@toC}" if @toC isnt @toVar
+ varPart += ", #{@step}" if @step isnt @stepVar
+ [lt, gt] = ["#{idx} <#{@equals}", "#{idx} >#{@equals}"]
@@ -1571,13 +1630,22 @@ inner constructor in order to be able to pass the varargs.
-
+
-
Extends
+
Generate the condition.
+
condPart = if @stepNum
+ if parseNum(@stepNum[0]) > 0 then "#{lt} #{@toVar}" else "#{gt} #{@toVar}"
+ else if known
+ [from, to] = [parseNum(@fromNum[0]), parseNum(@toNum[0])]
+ if from <= to then "#{lt} #{to}" else "#{gt} #{to}"
+ else
+ cond = if @stepVar then "#{@stepVar} > 0" else "#{@fromVar} <= #{@toVar}"
+ "#{cond} ? #{lt} #{@toVar} : #{gt} #{@toVar}"
+
@@ -1587,17 +1655,25 @@ inner constructor in order to be able to pass the varargs.
-
Node to extend an object's prototype with an ancestor object.
-After goog.inherits
from the
-Closure Library.
-
+
Generate the step.
- exports.Extends = class Extends extends Base
- constructor: (@child, @parent) ->
+ stepPart = if @stepVar
+ "#{idx} += #{@stepVar}"
+ else if known
+ if namedIndex
+ if from <= to then "++#{idx}" else "--#{idx}"
+ else
+ if from <= to then "#{idx}++" else "#{idx}--"
+ else
+ if namedIndex
+ "#{cond} ? ++#{idx} : --#{idx}"
+ else
+ "#{cond} ? #{idx}++ : #{idx}--"
- children: ['child', 'parent']
+ varPart = "#{idxName} = #{varPart}" if namedIndex
+ stepPart = "#{idxName} = #{stepPart}" if namedIndex
@@ -1608,13 +1684,11 @@ After goog.inherits
from the
- Hooks one constructor into another's prototype chain.
-
+ The final loop body.
- compileToFragments: (o) ->
- new Call(new Value(new Literal utility 'extends'), [@child, @parent]).compileToFragments o
+ [@makeCode "#{varPart}; #{condPart}; #{stepPart}"]
@@ -1622,13 +1696,34 @@ After goog.inherits
from the
-
+
-
Access
+
When used as a value, expand the range into the equivalent array.
+
compileArray: (o) ->
+ if @fromNum and @toNum and Math.abs(@fromNum - @toNum) <= 20
+ range = [+@fromNum..+@toNum]
+ range.pop() if @exclusive
+ return [@makeCode "[#{ range.join(', ') }]"]
+ idt = @tab + TAB
+ i = o.scope.freeVariable 'i'
+ result = o.scope.freeVariable 'results'
+ pre = "\n#{idt}#{result} = [];"
+ if @fromNum and @toNum
+ o.index = i
+ body = fragmentsToText @compileNode o
+ else
+ vars = "#{i} = #{@fromC}" + if @toC isnt @toVar then ", #{@toC}" else ''
+ cond = "#{@fromVar} <= #{@toVar}"
+ body = "var #{vars}; #{cond} ? #{i} <#{@equals} #{@toVar} : #{i} >#{@equals} #{@toVar}; #{cond} ? #{i}++ : #{i}--"
+ post = "{ #{result}.push(#{i}); }\n#{idt}return #{result};\n#{o.indent}"
+ hasArgs = (node) -> node?.contains isLiteralArguments
+ args = ', arguments' if hasArgs(@from) or hasArgs(@to)
+ [@makeCode "(function() {#{pre}\n#{idt}for (#{body})#{post}}).apply(this#{args ? ''})"]
+
@@ -1638,29 +1733,19 @@ After
goog.inherits
from the
-
A .
access into a property of a value, or the ::
shorthand for
-an access into the object's prototype.
-
+
Slice
+
An array slice literal. Unlike JavaScript's Array#slice
, the second parameter
+specifies the index of the end of the slice, just as the first parameter
+is the index of the beginning.
- exports.Access = class Access extends Base
- constructor: (@name, tag) ->
- @name.asKey = yes
- @soak = tag is 'soak'
+ exports.Slice = class Slice extends Base
- children: ['name']
+ children: ['range']
- compileToFragments: (o) ->
- name = @name.compileToFragments o
- if IDENTIFIER.test fragmentsToText name
- name.unshift @makeCode "."
- else
- name.unshift @makeCode "["
- name.push @makeCode "]"
- name
-
- isComplex: NO
+ constructor: (@range) ->
+ super()
@@ -1668,13 +1753,19 @@ an access into the object's prototype.
-
+
-
Index
+
We have to be careful when trying to slice through the end of the array,
+9e9
is used because not all implementations respect undefined
or 1/0
.
+9e9
should be safe because 9e9
> 2**32
, the max array length.
+
compileNode: (o) ->
+ {to, from} = @range
+ fromCompiled = from and from.compileToFragments(o, LEVEL_PAREN) or [@makeCode '0']
+
@@ -1684,21 +1775,22 @@ an access into the object's prototype.
-
A [ ... ]
indexed access into an array or object.
-
+
TODO: jwalton - move this into the 'if'?
- exports.Index = class Index extends Base
- constructor: (@index) ->
-
- children: ['index']
-
- compileToFragments: (o) ->
- [].concat @makeCode("["), @index.compileToFragments(o, LEVEL_PAREN), @makeCode("]")
-
- isComplex: ->
- @index.isComplex()
+ if to
+ compiled = to.compileToFragments o, LEVEL_PAREN
+ compiledText = fragmentsToText compiled
+ if not (not @range.exclusive and +compiledText is -1)
+ toStr = ', ' + if @range.exclusive
+ compiledText
+ else if SIMPLENUM.test compiledText
+ "#{+compiledText + 1}"
+ else
+ compiled = to.compileToFragments o, LEVEL_ACCESS
+ "+#{fragmentsToText compiled} + 1 || 9e9"
+ [@makeCode ".slice(#{ fragmentsToText fromCompiled }#{ toStr or '' })"]
@@ -1706,13 +1798,56 @@ an access into the object's prototype.
-
+
-
Range
+
Obj
+
An object literal, nothing fancy.
+
exports.Obj = class Obj extends Base
+ constructor: (props, @generated = false) ->
+ @objects = @properties = props or []
+
+ children: ['properties']
+
+ compileNode: (o) ->
+ props = @properties
+ return [@makeCode(if @front then '({})' else '{}')] unless props.length
+ if @generated
+ for node in props when node instanceof Value
+ node.error 'cannot have an implicit value in an implicit object'
+ idt = o.indent += TAB
+ lastNoncom = @lastNonComment @properties
+ answer = []
+ for prop, i in props
+ join = if i is props.length - 1
+ ''
+ else if prop is lastNoncom or prop instanceof Comment
+ '\n'
+ else
+ ',\n'
+ indent = if prop instanceof Comment then '' else idt
+ if prop instanceof Assign and prop.variable instanceof Value and prop.variable.hasProperties()
+ prop.variable.error 'Invalid object key'
+ if prop instanceof Value and prop.this
+ prop = new Assign prop.properties[0].name, prop, 'object'
+ if prop not instanceof Comment
+ if prop not instanceof Assign
+ prop = new Assign prop, prop, 'object'
+ (prop.variable.base or prop.variable).asKey = yes
+ if indent then answer.push @makeCode indent
+ answer.push prop.compileToFragments(o, LEVEL_TOP)...
+ if join then answer.push @makeCode join
+ answer.unshift @makeCode "{#{ props.length and '\n' }"
+ answer.push @makeCode "#{ props.length and '\n' + @tab }}"
+ if @front then @wrapInBraces answer else answer
+
+ assigns: (name) ->
+ for prop in @properties when prop.assigns name then return yes
+ no
+
@@ -1722,20 +1857,40 @@ an access into the object's prototype.
-
A range literal. Ranges can be used to extract portions (slices) of arrays,
-to specify a range for comprehensions, or as a value, to be expanded into the
-corresponding array of integers at runtime.
-
+
Arr
+
An array literal.
- exports.Range = class Range extends Base
+ exports.Arr = class Arr extends Base
+ constructor: (objs) ->
+ @objects = objs or []
- children: ['from', 'to']
+ children: ['objects']
- constructor: (@from, @to, tag) ->
- @exclusive = tag is 'exclusive'
- @equals = if @exclusive then '' else '='
+ compileNode: (o) ->
+ return [@makeCode '[]'] unless @objects.length
+ o.indent += TAB
+ answer = Splat.compileSplattedArray o, @objects
+ return answer if answer.length
+
+ answer = []
+ compiledObjs = (obj.compileToFragments o, LEVEL_LIST for obj in @objects)
+ for fragments, index in compiledObjs
+ if index
+ answer.push @makeCode ", "
+ answer.push fragments...
+ if fragmentsToText(answer).indexOf('\n') >= 0
+ answer.unshift @makeCode "[\n#{o.indent}"
+ answer.push @makeCode "\n#{@tab}]"
+ else
+ answer.unshift @makeCode "["
+ answer.push @makeCode "]"
+ answer
+
+ assigns: (name) ->
+ for obj in @objects when obj.assigns name then return yes
+ no
@@ -1746,19 +1901,19 @@ corresponding array of integers at runtime.
- Compiles the range's source variables -- where it starts and where it ends.
-But only if they need to be cached to avoid double evaluation.
-
+ Class
+The CoffeeScript class definition.
+Initialize a Class with its name, an optional superclass, and a
+list of prototype property assignments.
- compileVariables: (o) ->
- o = merge o, top: true
- [@fromC, @fromVar] = @cacheToCodeFragments @from.cache o, LEVEL_LIST
- [@toC, @toVar] = @cacheToCodeFragments @to.cache o, LEVEL_LIST
- [@step, @stepVar] = @cacheToCodeFragments step.cache o, LEVEL_LIST if step = del o, 'step'
- [@fromNum, @toNum] = [@fromVar.match(SIMPLENUM), @toVar.match(SIMPLENUM)]
- @stepNum = @stepVar.match(SIMPLENUM) if @stepVar
+ exports.Class = class Class extends Base
+ constructor: (@variable, @parent, @body = new Block) ->
+ @boundFuncs = []
+ @body.classBody = yes
+
+ children: ['variable', 'parent', 'body']
@@ -1769,15 +1924,19 @@ But only if they need to be cached to avoid double evaluation.
- When compiled normally, the range returns the contents of the for loop
-needed to iterate over the values in the range. Used by comprehensions.
-
+ Figure out the appropriate name for the constructor function of this class.
- compileNode: (o) ->
- @compileVariables o unless @fromVar
- return @compileArray(o) unless o.index
+ determineName: ->
+ return null unless @variable
+ decl = if tail = last @variable.properties
+ tail instanceof Access and tail.name.value
+ else
+ @variable.base.value
+ if decl in STRICT_PROSCRIBED
+ @variable.error "class variable name may not be #{decl}"
+ decl and= IDENTIFIER.test(decl) and decl
@@ -1788,19 +1947,19 @@ needed to iterate over the values in the range. Used by comprehensions.
- Set up endpoints.
-
+ For all this
-references and bound functions in the class definition,
+this
is the Class being constructed.
- known = @fromNum and @toNum
- idx = del o, 'index'
- idxName = del o, 'name'
- namedIndex = idxName and idxName isnt idx
- varPart = "#{idx} = #{@fromC}"
- varPart += ", #{@toC}" if @toC isnt @toVar
- varPart += ", #{@step}" if @step isnt @stepVar
- [lt, gt] = ["#{idx} <#{@equals}", "#{idx} >#{@equals}"]
+ setContext: (name) ->
+ @body.traverseChildren false, (node) ->
+ return false if node.classBody
+ if node instanceof Literal and node.value is 'this'
+ node.value = name
+ else if node instanceof Code
+ node.klass = name
+ node.context = name if node.bound
@@ -1811,19 +1970,16 @@ needed to iterate over the values in the range. Used by comprehensions.
- Generate the condition.
-
+ Ensure that all functions bound to the instance are proxied in the
+constructor.
- condPart = if @stepNum
- if +@stepNum > 0 then "#{lt} #{@toVar}" else "#{gt} #{@toVar}"
- else if known
- [from, to] = [+@fromNum, +@toNum]
- if from <= to then "#{lt} #{to}" else "#{gt} #{to}"
- else
- cond = if @stepVar then "#{@stepVar} > 0" else "#{@fromVar} <= #{@toVar}"
- "#{cond} ? #{lt} #{@toVar} : #{gt} #{@toVar}"
+ addBoundFunctions: (o) ->
+ for bvar in @boundFuncs
+ lhs = (new Value (new Literal "this"), [new Access bvar]).compile o
+ @ctor.body.unshift new Literal "#{lhs} = #{utility 'bind'}(#{lhs}, this)"
+ return
@@ -1834,26 +1990,38 @@ needed to iterate over the values in the range. Used by comprehensions.
- Generate the step.
-
+ Merge the properties from a top-level object as prototypal properties
+on the class.
- stepPart = if @stepVar
- "#{idx} += #{@stepVar}"
- else if known
- if namedIndex
- if from <= to then "++#{idx}" else "--#{idx}"
- else
- if from <= to then "#{idx}++" else "#{idx}--"
- else
- if namedIndex
- "#{cond} ? ++#{idx} : --#{idx}"
- else
- "#{cond} ? #{idx}++ : #{idx}--"
-
- varPart = "#{idxName} = #{varPart}" if namedIndex
- stepPart = "#{idxName} = #{stepPart}" if namedIndex
+ addProperties: (node, name, o) ->
+ props = node.base.properties[..]
+ exprs = while assign = props.shift()
+ if assign instanceof Assign
+ base = assign.variable.base
+ delete assign.context
+ func = assign.value
+ if base.value is 'constructor'
+ if @ctor
+ assign.error 'cannot define more than one constructor in a class'
+ if func.bound
+ assign.error 'cannot define a constructor as a bound function'
+ if func instanceof Code
+ assign = @ctor = func
+ else
+ @externalCtor = o.classScope.freeVariable 'class'
+ assign = new Assign new Literal(@externalCtor), func
+ else
+ if assign.variable.this
+ func.static = yes
+ else
+ assign.variable = new Value(new Literal(name), [(new Access new Literal 'prototype'), new Access base])
+ if func instanceof Code and func.bound
+ @boundFuncs.push base
+ func.bound = no
+ assign
+ compact exprs
@@ -1864,12 +2032,24 @@ needed to iterate over the values in the range. Used by comprehensions.
- The final loop body.
-
+ Walk the body of the class, looking for prototype properties to be converted
+and tagging static assignments.
- [@makeCode "#{varPart}; #{condPart}; #{stepPart}"]
+ walkBody: (name, o) ->
+ @traverseChildren false, (child) =>
+ cont = true
+ return false if child instanceof Class
+ if child instanceof Block
+ for node, i in exps = child.expressions
+ if node instanceof Assign and node.variable.looksStatic name
+ node.value.static = yes
+ else if node instanceof Value and node.isObject(true)
+ cont = false
+ exps[i] = @addProperties node, name, o
+ child.expressions = exps = flatten exps
+ cont and child not instanceof Class
@@ -1880,31 +2060,18 @@ needed to iterate over the values in the range. Used by comprehensions.
- When used as a value, expand the range into the equivalent array.
-
+ use strict
(and other directives) must be the first expression statement(s)
+of a function body. This method ensures the prologue is correctly positioned
+above the constructor
.
- compileArray: (o) ->
- if @fromNum and @toNum and Math.abs(@fromNum - @toNum) <= 20
- range = [+@fromNum..+@toNum]
- range.pop() if @exclusive
- return [@makeCode "[#{ range.join(', ') }]"]
- idt = @tab + TAB
- i = o.scope.freeVariable 'i'
- result = o.scope.freeVariable 'results'
- pre = "\n#{idt}#{result} = [];"
- if @fromNum and @toNum
- o.index = i
- body = fragmentsToText @compileNode o
- else
- vars = "#{i} = #{@fromC}" + if @toC isnt @toVar then ", #{@toC}" else ''
- cond = "#{@fromVar} <= #{@toVar}"
- body = "var #{vars}; #{cond} ? #{i} <#{@equals} #{@toVar} : #{i} >#{@equals} #{@toVar}; #{cond} ? #{i}++ : #{i}--"
- post = "{ #{result}.push(#{i}); }\n#{idt}return #{result};\n#{o.indent}"
- hasArgs = (node) -> node?.contains (n) -> n instanceof Literal and n.value is 'arguments' and not n.asKey
- args = ', arguments' if hasArgs(@from) or hasArgs(@to)
- [@makeCode "(function() {#{pre}\n#{idt}for (#{body})#{post}}).apply(this#{args ? ''})"]
+ hoistDirectivePrologue: ->
+ index = 0
+ {expressions} = @body
+ ++index while (node = expressions[index]) and node instanceof Comment or
+ node instanceof Value and node.isString()
+ @directives = expressions.splice 0, index
@@ -1912,13 +2079,27 @@ needed to iterate over the values in the range. Used by comprehensions.
-
+
-
Slice
+
Make sure that a constructor is defined for the class, and properly
+configured.
+
ensureConstructor: (name) ->
+ if not @ctor
+ @ctor = new Code
+ if @externalCtor
+ @ctor.body.push new Literal "#{@externalCtor}.apply(this, arguments)"
+ else if @parent
+ @ctor.body.push new Literal "#{name}.__super__.constructor.apply(this, arguments)"
+ @ctor.body.makeReturn()
+ @body.expressions.unshift @ctor
+ @ctor.ctor = @ctor.name = name
+ @ctor.klass = null
+ @ctor.noReturn = yes
+
@@ -1928,19 +2109,44 @@ needed to iterate over the values in the range. Used by comprehensions.
-
An array slice literal. Unlike JavaScript's Array#slice
, the second parameter
-specifies the index of the end of the slice, just as the first parameter
-is the index of the beginning.
-
+
Instead of generating the JavaScript string directly, we build up the
+equivalent syntax tree and compile that, in pieces. You can see the
+constructor, property assignments, and inheritance getting built out below.
- exports.Slice = class Slice extends Base
+ compileNode: (o) ->
+ if jumpNode = @body.jumps()
+ jumpNode.error 'Class bodies cannot contain pure statements'
+ if argumentsNode = @body.contains isLiteralArguments
+ argumentsNode.error "Class bodies shouldn't reference arguments"
- children: ['range']
+ name = @determineName() or '_Class'
+ name = "_#{name}" if name.reserved
+ lname = new Literal name
+ func = new Code [], Block.wrap [@body]
+ args = []
+ o.classScope = func.makeScope o.scope
- constructor: (@range) ->
- super()
+ @hoistDirectivePrologue()
+ @setContext name
+ @walkBody name, o
+ @ensureConstructor name
+ @addBoundFunctions o
+ @body.spaced = yes
+ @body.expressions.push lname
+
+ if @parent
+ superClass = new Literal o.classScope.freeVariable 'super', no
+ @body.expressions.unshift new Extends lname, superClass
+ func.params.push new Param superClass
+ args.push @parent
+
+ @body.expressions.unshift @directives...
+
+ klass = new Parens new Call func, args
+ klass = new Assign @variable, klass if @variable
+ klass.compileToFragments o
@@ -1951,16 +2157,30 @@ is the index of the beginning.
- We have to be careful when trying to slice through the end of the array,
-9e9
is used because not all implementations respect undefined
or 1/0
.
-9e9
should be safe because 9e9
> 2**32
, the max array length.
-
+ Assign
+The Assign is used to assign a local variable to value, or to set the
+property of an object -- including within object literals.
- compileNode: (o) ->
- {to, from} = @range
- fromCompiled = from and from.compileToFragments(o, LEVEL_PAREN) or [@makeCode '0']
+ exports.Assign = class Assign extends Base
+ constructor: (@variable, @value, @context, options) ->
+ @param = options and options.param
+ @subpattern = options and options.subpattern
+ forbidden = (name = @variable.unwrapAll().value) in STRICT_PROSCRIBED
+ if forbidden and @context isnt 'object'
+ @variable.error "variable name may not be \"#{name}\""
+
+ children: ['variable', 'value']
+
+ isStatement: (o) ->
+ o?.level is LEVEL_TOP and @context? and "?" in @context
+
+ assigns: (name) ->
+ @[if @context is 'object' then 'value' else 'variable'].assigns name
+
+ unfoldSoak: (o) ->
+ unfoldSoak o, this, 'variable'
@@ -1971,23 +2191,37 @@ is the index of the beginning.
- TODO: jwalton - move this into the 'if'?
-
+ Compile an assignment, delegating to compilePatternMatch
or
+compileSplice
if appropriate. Keep track of the name of the base object
+we've been assigned to, for correct internal references. If the variable
+has not been seen yet within the current scope, declare it.
- if to
- compiled = to.compileToFragments o, LEVEL_PAREN
- compiledText = fragmentsToText compiled
- if not (not @range.exclusive and +compiledText is -1)
- toStr = ', ' + if @range.exclusive
- compiledText
- else if SIMPLENUM.test compiledText
- "#{+compiledText + 1}"
- else
- compiled = to.compileToFragments o, LEVEL_ACCESS
- "+#{fragmentsToText compiled} + 1 || 9e9"
- [@makeCode ".slice(#{ fragmentsToText fromCompiled }#{ toStr or '' })"]
+ compileNode: (o) ->
+ if isValue = @variable instanceof Value
+ return @compilePatternMatch o if @variable.isArray() or @variable.isObject()
+ return @compileSplice o if @variable.isSplice()
+ return @compileConditional o if @context in ['||=', '&&=', '?=']
+ return @compileSpecialMath o if @context in ['**=', '//=', '%%=']
+ compiledName = @variable.compileToFragments o, LEVEL_LIST
+ name = fragmentsToText compiledName
+ unless @context
+ varBase = @variable.unwrapAll()
+ unless varBase.isAssignable()
+ @variable.error "\"#{@variable.compile o}\" cannot be assigned"
+ unless varBase.hasProperties?()
+ if @param
+ o.scope.add name, 'var'
+ else
+ o.scope.find name
+ if @value instanceof Code and match = METHOD_DEF.exec name
+ @value.klass = match[1] if match[2]
+ @value.name = match[3] ? match[4] ? match[5]
+ val = @value.compileToFragments o, LEVEL_LIST
+ return (compiledName.concat @makeCode(": "), val) if @context is 'object'
+ answer = compiledName.concat @makeCode(" #{ @context or '=' } "), val
+ if o.level <= LEVEL_LIST then answer else @wrapInBraces answer
@@ -1995,13 +2229,26 @@ is the index of the beginning.
-
+
-
Obj
+
Brief implementation of recursive pattern matching, when assigning array or
+object literals to a value. Peeks at their properties to assign inner names.
+See the ECMAScript Harmony Wiki
+for details.
+
compilePatternMatch: (o) ->
+ top = o.level is LEVEL_TOP
+ {value} = this
+ {objects} = @variable.base
+ unless olen = objects.length
+ code = value.compileToFragments o
+ return if o.level >= LEVEL_OP then @wrapInBraces code else code
+ isObject = @variable.isObject()
+ if top and olen is 1 and (obj = objects[0]) not instanceof Splat
+
@@ -2011,52 +2258,27 @@ is the index of the beginning.
-
An object literal, nothing fancy.
-
+
Unroll simplest cases: {v} = x
-> v = x.v
- exports.Obj = class Obj extends Base
- constructor: (props, @generated = false) ->
- @objects = @properties = props or []
-
- children: ['properties']
-
- compileNode: (o) ->
- props = @properties
- return [@makeCode(if @front then '({})' else '{}')] unless props.length
- if @generated
- for node in props when node instanceof Value
- node.error 'cannot have an implicit value in an implicit object'
- idt = o.indent += TAB
- lastNoncom = @lastNonComment @properties
- answer = []
- for prop, i in props
- join = if i is props.length - 1
- ''
- else if prop is lastNoncom or prop instanceof Comment
- '\n'
- else
- ',\n'
- indent = if prop instanceof Comment then '' else idt
- if prop instanceof Assign and prop.variable instanceof Value and prop.variable.hasProperties()
- prop.variable.error 'Invalid object key'
- if prop instanceof Value and prop.this
- prop = new Assign prop.properties[0].name, prop, 'object'
- if prop not instanceof Comment
- if prop not instanceof Assign
- prop = new Assign prop, prop, 'object'
- (prop.variable.base or prop.variable).asKey = yes
- if indent then answer.push @makeCode indent
- answer.push prop.compileToFragments(o, LEVEL_TOP)...
- if join then answer.push @makeCode join
- answer.unshift @makeCode "{#{ props.length and '\n' }"
- answer.push @makeCode "#{ props.length and '\n' + @tab }}"
- if @front then @wrapInBraces answer else answer
-
- assigns: (name) ->
- for prop in @properties when prop.assigns name then return yes
- no
+ if obj instanceof Assign
+ {variable: {base: idx}, value: obj} = obj
+ else
+ idx = if isObject
+ if obj.this then obj.properties[0].name else obj
+ else
+ new Literal 0
+ acc = IDENTIFIER.test idx.unwrap().value or 0
+ value = new Value value
+ value.properties.push new (if acc then Access else Index) idx
+ if obj.unwrap().value in RESERVED
+ obj.error "assignment to a reserved word: #{obj.compile o}"
+ return new Assign(obj, value, null, param: @param).compileToFragments o, LEVEL_TOP
+ vvar = value.compileToFragments o, LEVEL_LIST
+ vvarText = fragmentsToText vvar
+ assigns = []
+ expandedIdx = false
@@ -2064,13 +2286,19 @@ is the index of the beginning.
-
+
-
Arr
+
Make vvar into a simple variable if it isn't already.
+
if not IDENTIFIER.test(vvarText) or @variable.assigns(vvarText)
+ assigns.push [@makeCode("#{ ref = o.scope.freeVariable 'ref' } = "), vvar...]
+ vvar = [@makeCode ref]
+ vvarText = ref
+ for obj, i in objects
+
@@ -2080,40 +2308,13 @@ is the index of the beginning.
-
An array literal.
-
+
A regular array pattern-match.
- exports.Arr = class Arr extends Base
- constructor: (objs) ->
- @objects = objs or []
-
- children: ['objects']
-
- compileNode: (o) ->
- return [@makeCode '[]'] unless @objects.length
- o.indent += TAB
- answer = Splat.compileSplattedArray o, @objects
- return answer if answer.length
-
- answer = []
- compiledObjs = (obj.compileToFragments o, LEVEL_LIST for obj in @objects)
- for fragments, index in compiledObjs
- if index
- answer.push @makeCode ", "
- answer.push fragments...
- if fragmentsToText(answer).indexOf('\n') >= 0
- answer.unshift @makeCode "[\n#{o.indent}"
- answer.push @makeCode "\n#{@tab}]"
- else
- answer.unshift @makeCode "["
- answer.push @makeCode "]"
- answer
-
- assigns: (name) ->
- for obj in @objects when obj.assigns name then return yes
- no
+ idx = i
+ if isObject
+ if obj instanceof Assign
@@ -2121,13 +2322,16 @@ is the index of the beginning.
-
+
-
Class
+
A regular object pattern-match.
+
{variable: {base: idx}, value: obj} = obj
+ else
+
@@ -2137,19 +2341,51 @@ is the index of the beginning.
-
The CoffeeScript class definition.
-Initialize a Class with its name, an optional superclass, and a
-list of prototype property assignments.
-
+
A shorthand {a, b, @c} = val
pattern-match.
- exports.Class = class Class extends Base
- constructor: (@variable, @parent, @body = new Block) ->
- @boundFuncs = []
- @body.classBody = yes
-
- children: ['variable', 'parent', 'body']
+ if obj.base instanceof Parens
+ [obj, idx] = new Value(obj.unwrapAll()).cacheReference o
+ else
+ idx = if obj.this then obj.properties[0].name else obj
+ if not expandedIdx and obj instanceof Splat
+ name = obj.name.unwrap().value
+ obj = obj.unwrap()
+ val = "#{olen} <= #{vvarText}.length ? #{ utility 'slice' }.call(#{vvarText}, #{i}"
+ if rest = olen - i - 1
+ ivar = o.scope.freeVariable 'i'
+ val += ", #{ivar} = #{vvarText}.length - #{rest}) : (#{ivar} = #{i}, [])"
+ else
+ val += ") : []"
+ val = new Literal val
+ expandedIdx = "#{ivar}++"
+ else if not expandedIdx and obj instanceof Expansion
+ if rest = olen - i - 1
+ if rest is 1
+ expandedIdx = "#{vvarText}.length - 1"
+ else
+ ivar = o.scope.freeVariable 'i'
+ val = new Literal "#{ivar} = #{vvarText}.length - #{rest}"
+ expandedIdx = "#{ivar}++"
+ assigns.push val.compileToFragments o, LEVEL_LIST
+ continue
+ else
+ name = obj.unwrap().value
+ if obj instanceof Splat or obj instanceof Expansion
+ obj.error "multiple splats/expansions are disallowed in an assignment"
+ if typeof idx is 'number'
+ idx = new Literal expandedIdx or idx
+ acc = no
+ else
+ acc = isObject and IDENTIFIER.test idx.unwrap().value or 0
+ val = new Value new Literal(vvarText), [new (if acc then Access else Index) idx]
+ if name? and name in RESERVED
+ obj.error "assignment to a reserved word: #{obj.compile o}"
+ assigns.push new Assign(obj, val, null, param: @param, subpattern: yes).compileToFragments o, LEVEL_LIST
+ assigns.push vvar unless top or @subpattern
+ fragments = @joinFragmentArrays assigns, ', '
+ if o.level < LEVEL_LIST then fragments else @wrapInBraces fragments
@@ -2160,20 +2396,14 @@ list of prototype property assignments.
- Figure out the appropriate name for the constructor function of this class.
-
+ When compiling a conditional assignment, take care to ensure that the
+operands are only evaluated once, even though we have to reference them
+more than once.
- determineName: ->
- return null unless @variable
- decl = if tail = last @variable.properties
- tail instanceof Access and tail.name.value
- else
- @variable.base.value
- if decl in STRICT_PROSCRIBED
- @variable.error "class variable name may not be #{decl}"
- decl and= IDENTIFIER.test(decl) and decl
+ compileConditional: (o) ->
+ [left, right] = @variable.cacheReference o
@@ -2184,20 +2414,19 @@ list of prototype property assignments.
- For all this
-references and bound functions in the class definition,
-this
is the Class being constructed.
-
+ Disallow conditional assignment of undefined variables.
- setContext: (name) ->
- @body.traverseChildren false, (node) ->
- return false if node.classBody
- if node instanceof Literal and node.value is 'this'
- node.value = name
- else if node instanceof Code
- node.klass = name
- node.context = name if node.bound
+ if not left.properties.length and left.base instanceof Literal and
+ left.base.value != "this" and not o.scope.check left.base.value
+ @variable.error "the variable \"#{left.base.value}\" can't be assigned with #{@context} because it has not been declared before"
+ if "?" in @context
+ o.isExistentialEquals = true
+ new If(new Existence(left), right, type: 'if').addElse(new Assign(right, @value, '=')).compileToFragments o
+ else
+ fragments = new Op(@context[...-1], left, new Assign(right, @value, '=')).compileToFragments o
+ if o.level <= LEVEL_LIST then fragments else @wrapInBraces fragments
@@ -2208,17 +2437,14 @@ list of prototype property assignments.
- Ensure that all functions bound to the instance are proxied in the
-constructor.
-
+ Convert special math assignment operators like a **= b
to the equivalent
+extended form a = a ** b
and then compiles that.
- addBoundFunctions: (o) ->
- for bvar in @boundFuncs
- lhs = (new Value (new Literal "this"), [new Access bvar]).compile o
- @ctor.body.unshift new Literal "#{lhs} = #{utility 'bind'}(#{lhs}, this)"
- return
+ compileSpecialMath: (o) ->
+ [left, right] = @variable.cacheReference o
+ new Assign(left, new Op(@context[...-1], right, @value)).compileToFragments o
@@ -2229,41 +2455,31 @@ constructor.
- Merge the properties from a top-level object as prototypal properties
-on the class.
-
+ Compile the assignment from an array splice literal, using JavaScript's
+Array#splice
method.
- addProperties: (node, name, o) ->
- props = node.base.properties[..]
- exprs = while assign = props.shift()
- if assign instanceof Assign
- base = assign.variable.base
- delete assign.context
- func = assign.value
- if base.value is 'constructor'
- if @ctor
- assign.error 'cannot define more than one constructor in a class'
- if func.bound
- assign.error 'cannot define a constructor as a bound function'
- if func instanceof Code
- assign = @ctor = func
- else
- @externalCtor = o.scope.freeVariable 'class'
- assign = new Assign new Literal(@externalCtor), func
- else
- if assign.variable.this
- func.static = yes
- if func.bound
- func.context = name
- else
- assign.variable = new Value(new Literal(name), [(new Access new Literal 'prototype'), new Access base])
- if func instanceof Code and func.bound
- @boundFuncs.push base
- func.bound = no
- assign
- compact exprs
+ compileSplice: (o) ->
+ {range: {from, to, exclusive}} = @variable.properties.pop()
+ name = @variable.compile o
+ if from
+ [fromDecl, fromRef] = @cacheToCodeFragments from.cache o, LEVEL_OP
+ else
+ fromDecl = fromRef = '0'
+ if to
+ if from instanceof Value and from.isSimpleNumber() and
+ to instanceof Value and to.isSimpleNumber()
+ to = to.compile(o) - fromRef
+ to += 1 unless exclusive
+ else
+ to = to.compile(o, LEVEL_ACCESS) + ' - ' + fromRef
+ to += ' + 1' unless exclusive
+ else
+ to = "9e9"
+ [valDef, valRef] = @value.cache o, LEVEL_LIST
+ answer = [].concat @makeCode("[].splice.apply(#{name}, [#{fromDecl}, #{to}].concat("), valDef, @makeCode(")), "), valRef
+ if o.level > LEVEL_TOP then @wrapInBraces answer else answer
@@ -2274,22 +2490,26 @@ on the class.
- Walk the body of the class, looking for prototype properties to be converted.
-
+ Code
+A function definition. This is the only node that creates a new Scope.
+When for the purposes of walking the contents of a function body, the Code
+has no children -- they're within the inner scope.
- walkBody: (name, o) ->
- @traverseChildren false, (child) =>
- cont = true
- return false if child instanceof Class
- if child instanceof Block
- for node, i in exps = child.expressions
- if node instanceof Value and node.isObject(true)
- cont = false
- exps[i] = @addProperties node, name, o
- child.expressions = exps = flatten exps
- cont and child not instanceof Class
+ exports.Code = class Code extends Base
+ constructor: (params, body, tag) ->
+ @params = params or []
+ @body = body or new Block
+ @bound = tag is 'boundfunc'
+
+ children: ['params', 'body']
+
+ isStatement: -> !!@ctor
+
+ jumps: NO
+
+ makeScope: (parentScope) -> new Scope parentScope, @body, this
@@ -2300,19 +2520,18 @@ on the class.
- use strict
(and other directives) must be the first expression statement(s)
-of a function body. This method ensures the prologue is correctly positioned
-above the constructor
.
-
+ Compilation creates a new scope unless explicitly asked to share with the
+outer scope. Handles splat parameters in the parameter list by peeking at
+the JavaScript arguments
object. If the function is bound with the =>
+arrow, generates a wrapper that saves the current value of this
through
+a closure.
- hoistDirectivePrologue: ->
- index = 0
- {expressions} = @body
- ++index while (node = expressions[index]) and node instanceof Comment or
- node instanceof Value and node.isString()
- @directives = expressions.splice 0, index
+ compileNode: (o) ->
+
+ if @bound and o.scope.method?.bound
+ @context = o.scope.method.context
@@ -2323,31 +2542,72 @@ above the constructor
.
- Make sure that a constructor is defined for the class, and properly
-configured.
-
+ Handle bound functions early.
- ensureConstructor: (name, o) ->
- missing = not @ctor
- @ctor or= new Code
- @ctor.ctor = @ctor.name = name
- @ctor.klass = null
- @ctor.noReturn = yes
- if missing
- superCall = new Literal "#{name}.__super__.constructor.apply(this, arguments)" if @parent
- superCall = new Literal "#{@externalCtor}.apply(this, arguments)" if @externalCtor
- if superCall
- ref = new Literal o.scope.freeVariable 'ref'
- @ctor.body.unshift new Assign ref, superCall
- @addBoundFunctions o
- if superCall
- @ctor.body.push ref
- @ctor.body.makeReturn()
- @body.expressions.unshift @ctor
- else
- @addBoundFunctions o
+ if @bound and not @context
+ @context = '_this'
+ wrapper = new Code [new Param new Literal @context], new Block [this]
+ boundfunc = new Call(wrapper, [new Literal 'this'])
+ boundfunc.updateLocationDataIfMissing @locationData
+ return boundfunc.compileNode(o)
+
+ o.scope = del(o, 'classScope') or @makeScope o.scope
+ o.scope.shared = del(o, 'sharedScope')
+ o.indent += TAB
+ delete o.bare
+ delete o.isExistentialEquals
+ params = []
+ exprs = []
+ for param in @params when param not instanceof Expansion
+ o.scope.parameter param.asReference o
+ for param in @params when param.splat or param instanceof Expansion
+ for {name: p} in @params when param not instanceof Expansion
+ if p.this then p = p.properties[0].name
+ if p.value then o.scope.add p.value, 'var', yes
+ splats = new Assign new Value(new Arr(p.asReference o for p in @params)),
+ new Value new Literal 'arguments'
+ break
+ for param in @params
+ if param.isComplex()
+ val = ref = param.asReference o
+ val = new Op '?', ref, param.value if param.value
+ exprs.push new Assign new Value(param.name), val, '=', param: yes
+ else
+ ref = param
+ if param.value
+ lit = new Literal ref.name.value + ' == null'
+ val = new Assign new Value(param.name), param.value, '='
+ exprs.push new If lit, val
+ params.push ref unless splats
+ wasEmpty = @body.isEmpty()
+ exprs.unshift splats if splats
+ @body.expressions.unshift exprs... if exprs.length
+ for p, i in params
+ params[i] = p.compileToFragments o
+ o.scope.parameter fragmentsToText params[i]
+ uniqs = []
+ @eachParamName (name, node) ->
+ node.error "multiple parameters named '#{name}'" if name in uniqs
+ uniqs.push name
+ @body.makeReturn() unless wasEmpty or @noReturn
+ code = 'function'
+ code += ' ' + @name if @ctor
+ code += '('
+ answer = [@makeCode(code)]
+ for p, i in params
+ if i then answer.push @makeCode ", "
+ answer.push p...
+ answer.push @makeCode ') {'
+ answer = answer.concat(@makeCode("\n"), @body.compileWithDeclarations(o), @makeCode("\n#{@tab}")) unless @body.isEmpty()
+ answer.push @makeCode '}'
+
+ return [@makeCode(@tab), answer...] if @ctor
+ if @front or (o.level >= LEVEL_ACCESS) then @wrapInBraces answer else answer
+
+ eachParamName: (iterator) ->
+ param.eachName iterator for param in @params
@@ -2358,40 +2618,13 @@ configured.
- Instead of generating the JavaScript string directly, we build up the
-equivalent syntax tree and compile that, in pieces. You can see the
-constructor, property assignments, and inheritance getting built out below.
-
+ Short-circuit traverseChildren
method to prevent it from crossing scope boundaries
+unless crossScope
is true
.
- compileNode: (o) ->
- decl = @determineName()
- name = decl or '_Class'
- name = "_#{name}" if name.reserved
- lname = new Literal name
-
- @hoistDirectivePrologue()
- @setContext name
- @walkBody name, o
- @ensureConstructor name, o
- @body.spaced = yes
- @body.expressions.unshift @ctor unless @ctor instanceof Code
- @body.expressions.push lname
- @body.expressions.unshift @directives...
-
- call = Closure.wrap @body
-
- if @parent
- @superClass = new Literal o.scope.freeVariable 'super', no
- @body.expressions.unshift new Extends lname, @superClass
- call.args.push @parent
- params = call.variable.params or call.variable.base.params
- params.push new Param @superClass
-
- klass = new Parens call, yes
- klass = new Assign @variable, klass if @variable
- klass.compileToFragments o
+ traverseChildren: (crossScope, func) ->
+ super(crossScope, func) if crossScope
@@ -2399,13 +2632,43 @@ constructor, property assignments, and inheritance getting built out below.
-
+
-
Assign
+
Param
+
A parameter in a function definition. Beyond a typical Javascript parameter,
+these parameters can also attach themselves to the context of the function,
+as well as be a splat, gathering up a group of parameters into an array.
+
exports.Param = class Param extends Base
+ constructor: (@name, @value, @splat) ->
+ if (name = @name.unwrapAll().value) in STRICT_PROSCRIBED
+ @name.error "parameter name \"#{name}\" is not allowed"
+
+ children: ['name', 'value']
+
+ compileToFragments: (o) ->
+ @name.compileToFragments o, LEVEL_LIST
+
+ asReference: (o) ->
+ return @reference if @reference
+ node = @name
+ if node.this
+ node = node.properties[0].name
+ if node.value.reserved
+ node = new Literal o.scope.freeVariable node.value
+ else if node.isComplex()
+ node = new Literal o.scope.freeVariable 'arg'
+ node = new Value node
+ node = new Splat node if @splat
+ node.updateLocationDataIfMissing @locationData
+ @reference = node
+
+ isComplex: ->
+ @name.isComplex()
+
@@ -2415,30 +2678,19 @@ constructor, property assignments, and inheritance getting built out below.
-
The Assign is used to assign a local variable to value, or to set the
-property of an object -- including within object literals.
-
+
Iterates the name or names of a Param
.
+In a sense, a destructured parameter represents multiple JS parameters. This
+method allows to iterate them all.
+The iterator
function will be called as iterator(name, node)
where
+name
is the name of the parameter and node
is the AST node corresponding
+to that name.
- exports.Assign = class Assign extends Base
- constructor: (@variable, @value, @context, options) ->
- @param = options and options.param
- @subpattern = options and options.subpattern
- forbidden = (name = @variable.unwrapAll().value) in STRICT_PROSCRIBED
- if forbidden and @context isnt 'object'
- @variable.error "variable name may not be \"#{name}\""
-
- children: ['variable', 'value']
-
- isStatement: (o) ->
- o?.level is LEVEL_TOP and @context? and "?" in @context
-
- assigns: (name) ->
- @[if @context is 'object' then 'value' else 'variable'].assigns name
-
- unfoldSoak: (o) ->
- unfoldSoak o, this, 'variable'
+ eachName: (iterator, name = @name)->
+ atParam = (obj) ->
+ node = obj.properties[0].name
+ iterator node.value, node unless node.value.reserved
@@ -2449,37 +2701,13 @@ property of an object -- including within object literals.
- Compile an assignment, delegating to compilePatternMatch
or
-compileSplice
if appropriate. Keep track of the name of the base object
-we've been assigned to, for correct internal references. If the variable
-has not been seen yet within the current scope, declare it.
-
+
- compileNode: (o) ->
- if isValue = @variable instanceof Value
- return @compilePatternMatch o if @variable.isArray() or @variable.isObject()
- return @compileSplice o if @variable.isSplice()
- return @compileConditional o if @context in ['||=', '&&=', '?=']
- compiledName = @variable.compileToFragments o, LEVEL_LIST
- name = fragmentsToText compiledName
- unless @context
- varBase = @variable.unwrapAll()
- unless varBase.isAssignable()
- @variable.error "\"#{@variable.compile o}\" cannot be assigned"
- unless varBase.hasProperties?()
- if @param
- o.scope.add name, 'var'
- else
- o.scope.find name
- if @value instanceof Code and match = METHOD_DEF.exec name
- @value.klass = match[1] if match[1]
- @value.name = match[2] ? match[3] ? match[4] ? match[5]
- val = @value.compileToFragments o, LEVEL_LIST
- return (compiledName.concat @makeCode(": "), val) if @context is 'object'
- answer = compiledName.concat @makeCode(" #{ @context or '=' } "), val
- if o.level <= LEVEL_LIST then answer else @wrapInBraces answer
+ return iterator name.value, name if name instanceof Literal
@@ -2490,23 +2718,14 @@ has not been seen yet within the current scope, declare it.
- Brief implementation of recursive pattern matching, when assigning array or
-object literals to a value. Peeks at their properties to assign inner names.
-See the ECMAScript Harmony Wiki
-for details.
-
+
- compilePatternMatch: (o) ->
- top = o.level is LEVEL_TOP
- {value} = this
- {objects} = @variable.base
- unless olen = objects.length
- code = value.compileToFragments o
- return if o.level >= LEVEL_OP then @wrapInBraces code else code
- isObject = @variable.isObject()
- if top and olen is 1 and (obj = objects[0]) not instanceof Splat
+ return atParam name if name instanceof Value
+ for obj in name.objects
@@ -2517,28 +2736,14 @@ for details.
- Unroll simplest cases: {v} = x
-> v = x.v
-
+
+- assignments within destructured parameters
{foo:bar}
+
- if obj instanceof Assign
- {variable: {base: idx}, value: obj} = obj
- else
- idx = if isObject
- if obj.this then obj.properties[0].name else obj
- else
- new Literal 0
- acc = IDENTIFIER.test idx.unwrap().value or 0
- value = new Value value
- value.properties.push new (if acc then Access else Index) idx
- if obj.unwrap().value in RESERVED
- obj.error "assignment to a reserved word: #{obj.compile o}"
- return new Assign(obj, value, null, param: @param).compileToFragments o, LEVEL_TOP
- vvar = value.compileToFragments o, LEVEL_LIST
- vvarText = fragmentsToText vvar
- assigns = []
- splat = false
+ if obj instanceof Assign
+ @eachName iterator, obj.value.unwrap()
@@ -2549,16 +2754,16 @@ for details.
- Make vvar into a simple variable if it isn't already.
-
+
+- splats within destructured parameters
[xs...]
+
- if not IDENTIFIER.test(vvarText) or @variable.assigns(vvarText)
- assigns.push [@makeCode("#{ ref = o.scope.freeVariable 'ref' } = "), vvar...]
- vvar = [@makeCode ref]
- vvarText = ref
- for obj, i in objects
+ else if obj instanceof Splat
+ node = obj.name.unwrap()
+ iterator node.value, node
+ else if obj instanceof Value
@@ -2569,14 +2774,14 @@ for details.
- A regular array pattern-match.
-
+
+- destructured parameters within destructured parameters
[{a}]
+
- idx = i
- if isObject
- if obj instanceof Assign
+ if obj.isArray() or obj.isObject()
+ @eachName iterator, obj.base
@@ -2587,13 +2792,14 @@ for details.
- A regular object pattern-match.
-
+
+- at-params within destructured parameters
{@foo}
+
- {variable: {base: idx}, value: obj} = obj
- else
+ else if obj.this
+ atParam obj
@@ -2604,42 +2810,16 @@ for details.
- A shorthand {a, b, @c} = val
pattern-match.
-
+
+- simple destructured parameters {foo}
+
- if obj.base instanceof Parens
- [obj, idx] = new Value(obj.unwrapAll()).cacheReference o
- else
- idx = if obj.this then obj.properties[0].name else obj
- if not splat and obj instanceof Splat
- name = obj.name.unwrap().value
- obj = obj.unwrap()
- val = "#{olen} <= #{vvarText}.length ? #{ utility 'slice' }.call(#{vvarText}, #{i}"
- if rest = olen - i - 1
- ivar = o.scope.freeVariable 'i'
- val += ", #{ivar} = #{vvarText}.length - #{rest}) : (#{ivar} = #{i}, [])"
- else
- val += ") : []"
- val = new Literal val
- splat = "#{ivar}++"
- else
- name = obj.unwrap().value
- if obj instanceof Splat
- obj.error "multiple splats are disallowed in an assignment"
- if typeof idx is 'number'
- idx = new Literal splat or idx
- acc = no
- else
- acc = isObject and IDENTIFIER.test idx.unwrap().value or 0
- val = new Value new Literal(vvarText), [new (if acc then Access else Index) idx]
- if name? and name in RESERVED
- obj.error "assignment to a reserved word: #{obj.compile o}"
- assigns.push new Assign(obj, val, null, param: @param, subpattern: yes).compileToFragments o, LEVEL_LIST
- assigns.push vvar unless top or @subpattern
- fragments = @joinFragmentArrays assigns, ', '
- if o.level < LEVEL_LIST then fragments else @wrapInBraces fragments
+ else iterator obj.base.value, obj.base
+ else if obj not instanceof Expansion
+ obj.error "illegal parameter #{obj.compile()}"
+ return
@@ -2650,15 +2830,28 @@ for details.
- When compiling a conditional assignment, take care to ensure that the
-operands are only evaluated once, even though we have to reference them
-more than once.
-
+ Splat
+A splat, either as a parameter to a function, an argument to a call,
+or as part of a destructuring assignment.
- compileConditional: (o) ->
- [left, right] = @variable.cacheReference o
+ exports.Splat = class Splat extends Base
+
+ children: ['name']
+
+ isAssignable: YES
+
+ constructor: (name) ->
+ @name = if name.compile then name else new Literal name
+
+ assigns: (name) ->
+ @name.assigns name
+
+ compileToFragments: (o) ->
+ @name.compileToFragments o
+
+ unwrap: -> @name
@@ -2669,16 +2862,34 @@ more than once.
- Disallow conditional assignment of undefined variables.
-
+ Utility function that converts an arbitrary number of elements, mixed with
+splats, to a proper array.
- if not left.properties.length and left.base instanceof Literal and
- left.base.value != "this" and not o.scope.check left.base.value
- @variable.error "the variable \"#{left.base.value}\" can't be assigned with #{@context} because it has not been declared before"
- if "?" in @context then o.isExistentialEquals = true
- new Op(@context[...-1], left, new Assign(right, @value, '=')).compileToFragments o
+ @compileSplattedArray: (o, list, apply) ->
+ index = -1
+ continue while (node = list[++index]) and node not instanceof Splat
+ return [] if index >= list.length
+ if list.length is 1
+ node = list[0]
+ fragments = node.compileToFragments o, LEVEL_LIST
+ return fragments if apply
+ return [].concat node.makeCode("#{ utility 'slice' }.call("), fragments, node.makeCode(")")
+ args = list[index..]
+ for node, i in args
+ compiledNode = node.compileToFragments o, LEVEL_LIST
+ args[i] = if node instanceof Splat
+ then [].concat node.makeCode("#{ utility 'slice' }.call("), compiledNode, node.makeCode(")")
+ else [].concat node.makeCode("["), compiledNode, node.makeCode("]")
+ if index is 0
+ node = list[0]
+ concatPart = (node.joinFragmentArrays args[1..], ', ')
+ return args[0].concat node.makeCode(".concat("), concatPart, node.makeCode(")")
+ base = (node.compileToFragments o, LEVEL_LIST for node in list[...index])
+ base = list[0].joinFragmentArrays base, ', '
+ concatPart = list[index].joinFragmentArrays args, ', '
+ [].concat list[0].makeCode("["), base, list[index].makeCode("].concat("), concatPart, (last list).makeCode(")")
@@ -2689,31 +2900,23 @@ more than once.
- Compile the assignment from an array splice literal, using JavaScript's
-Array#splice
method.
-
+ Expansion
+Used to skip values inside an array destructuring (pattern matching) or
+parameter list.
- compileSplice: (o) ->
- {range: {from, to, exclusive}} = @variable.properties.pop()
- name = @variable.compile o
- if from
- [fromDecl, fromRef] = @cacheToCodeFragments from.cache o, LEVEL_OP
- else
- fromDecl = fromRef = '0'
- if to
- if from?.isSimpleNumber() and to.isSimpleNumber()
- to = +to.compile(o) - +fromRef
- to += 1 unless exclusive
- else
- to = to.compile(o, LEVEL_ACCESS) + ' - ' + fromRef
- to += ' + 1' unless exclusive
- else
- to = "9e9"
- [valDef, valRef] = @value.cache o, LEVEL_LIST
- answer = [].concat @makeCode("[].splice.apply(#{name}, [#{fromDecl}, #{to}].concat("), valDef, @makeCode(")), "), valRef
- if o.level > LEVEL_TOP then @wrapInBraces answer else answer
+ exports.Expansion = class Expansion extends Base
+
+ isComplex: NO
+
+ compileNode: (o) ->
+ @error 'Expansion must be used inside a destructuring assignment or parameter list'
+
+ asReference: (o) ->
+ this
+
+ eachName: (iterator) ->
@@ -2721,13 +2924,42 @@ more than once.
-
+
-
Code
+
While
+
A while loop, the only sort of low-level loop exposed by CoffeeScript. From
+it, all other loops can be manufactured. Useful in cases where you need more
+flexibility or more speed than a comprehension can provide.
+
exports.While = class While extends Base
+ constructor: (condition, options) ->
+ @condition = if options?.invert then condition.invert() else condition
+ @guard = options?.guard
+
+ children: ['condition', 'guard', 'body']
+
+ isStatement: YES
+
+ makeReturn: (res) ->
+ if res
+ super
+ else
+ @returns = not @jumps loop: yes
+ this
+
+ addBody: (@body) ->
+ this
+
+ jumps: ->
+ {expressions} = @body
+ return no unless expressions.length
+ for node in expressions
+ return jumpNode if jumpNode = node.jumps loop: yes
+ no
+
@@ -2737,25 +2969,33 @@ more than once.
-
A function definition. This is the only node that creates a new Scope.
-When for the purposes of walking the contents of a function body, the Code
-has no children -- they're within the inner scope.
-
+
The main difference from a JavaScript while is that the CoffeeScript
+while can be used as a part of a larger expression -- while loops may
+return an array containing the computed result of each iteration.
- exports.Code = class Code extends Base
- constructor: (params, body, tag) ->
- @params = params or []
- @body = body or new Block
- @bound = tag is 'boundfunc'
- @context = '_this' if @bound
-
- children: ['params', 'body']
-
- isStatement: -> !!@ctor
-
- jumps: NO
+ compileNode: (o) ->
+ o.indent += TAB
+ set = ''
+ {body} = this
+ if body.isEmpty()
+ body = @makeCode ''
+ else
+ if @returns
+ body.makeReturn rvar = o.scope.freeVariable 'results'
+ set = "#{@tab}#{rvar} = [];\n"
+ if @guard
+ if body.expressions.length > 1
+ body.expressions.unshift new If (new Parens @guard).invert(), new Literal "continue"
+ else
+ body = Block.wrap [new If @guard, body] if @guard
+ body = [].concat @makeCode("\n"), (body.compileToFragments o, LEVEL_TOP), @makeCode("\n#{@tab}")
+ answer = [].concat @makeCode(set + @tab + "while ("), @condition.compileToFragments(o, LEVEL_PAREN),
+ @makeCode(") {"), body, @makeCode("}")
+ if @returns
+ answer.push @makeCode "\n#{@tab}return #{rvar};"
+ answer
@@ -2766,77 +3006,25 @@ has no children -- they're within the inner scope.
- Compilation creates a new scope unless explicitly asked to share with the
-outer scope. Handles splat parameters in the parameter list by peeking at
-the JavaScript arguments
object. If the function is bound with the =>
-arrow, generates a wrapper that saves the current value of this
through
-a closure.
-
+ Op
+Simple Arithmetic and logical operations. Performs some conversion from
+CoffeeScript operations into their JavaScript equivalents.
- compileNode: (o) ->
- o.scope = new Scope o.scope, @body, this
- o.scope.shared = del(o, 'sharedScope')
- o.indent += TAB
- delete o.bare
- delete o.isExistentialEquals
- params = []
- exprs = []
- @eachParamName (name) ->
- unless o.scope.check name then o.scope.parameter name
- for param in @params when param.splat
- for {name: p} in @params
- if p.this then p = p.properties[0].name
- if p.value then o.scope.add p.value, 'var', yes
- splats = new Assign new Value(new Arr(p.asReference o for p in @params)),
- new Value new Literal 'arguments'
- break
- for param in @params
- if param.isComplex()
- val = ref = param.asReference o
- val = new Op '?', ref, param.value if param.value
- exprs.push new Assign new Value(param.name), val, '=', param: yes
- else
- ref = param
- if param.value
- lit = new Literal ref.name.value + ' == null'
- val = new Assign new Value(param.name), param.value, '='
- exprs.push new If lit, val
- params.push ref unless splats
- wasEmpty = @body.isEmpty()
- exprs.unshift splats if splats
- @body.expressions.unshift exprs... if exprs.length
- for p, i in params
- params[i] = p.compileToFragments o
- o.scope.parameter fragmentsToText params[i]
- uniqs = []
- @eachParamName (name, node) ->
- node.error "multiple parameters named '#{name}'" if name in uniqs
- uniqs.push name
- @body.makeReturn() unless wasEmpty or @noReturn
- if @bound
- if o.scope.parent.method?.bound
- @bound = @context = o.scope.parent.method.context
- else if not @static
- o.scope.parent.assign '_this', 'this'
- idt = o.indent
- code = 'function'
- code += ' ' + @name if @ctor
- code += '('
- answer = [@makeCode(code)]
- for p, i in params
- if i then answer.push @makeCode ", "
- answer.push p...
- answer.push @makeCode ') {'
- answer = answer.concat(@makeCode("\n"), @body.compileWithDeclarations(o), @makeCode("\n#{@tab}")) unless @body.isEmpty()
- answer.push @makeCode '}'
-
- return [@makeCode(@tab), answer...] if @ctor
- if @front or (o.level >= LEVEL_ACCESS) then @wrapInBraces answer else answer
-
- eachParamName: (iterator) ->
- param.eachName iterator for param in @params
+ exports.Op = class Op extends Base
+ constructor: (op, first, second, flip ) ->
+ return new In first, second if op is 'in'
+ if op is 'do'
+ return @generateDo first
+ if op is 'new'
+ return first.newInstance() if first instanceof Call and not first.do and not first.isNew
+ first = new Parens first if first instanceof Code and first.bound or first.do
+ @operator = CONVERSIONS[op] or op
+ @first = first
+ @second = second
+ @flip = !!flip
+ return this
@@ -2847,14 +3035,14 @@ a closure.
- Short-circuit traverseChildren
method to prevent it from crossing scope boundaries
-unless crossScope
is true
.
-
+ The map of conversions from CoffeeScript to JavaScript symbols.
- traverseChildren: (crossScope, func) ->
- super(crossScope, func) if crossScope
+ CONVERSIONS =
+ '==': '==='
+ '!=': '!=='
+ 'of': 'in'
@@ -2862,13 +3050,27 @@ unless crossScope
is true
.
-
+
-
Param
+
The map of invertible operators.
+
INVERSIONS =
+ '!==': '==='
+ '===': '!=='
+
+ children: ['first', 'second']
+
+ isSimpleNumber: NO
+
+ isUnary: ->
+ not @second
+
+ isComplex: ->
+ not (@isUnary() and @operator in ['+', '-']) or @first.isComplex()
+
@@ -2878,38 +3080,62 @@ unless
crossScope
is
true
.
-
A parameter in a function definition. Beyond a typical Javascript parameter,
-these parameters can also attach themselves to the context of the function,
-as well as be a splat, gathering up a group of parameters into an array.
-
+
Am I capable of
+Python-style comparison chaining?
- exports.Param = class Param extends Base
- constructor: (@name, @value, @splat) ->
- if (name = @name.unwrapAll().value) in STRICT_PROSCRIBED
- @name.error "parameter name \"#{name}\" is not allowed"
+ isChainable: ->
+ @operator in ['<', '>', '>=', '<=', '===', '!==']
- children: ['name', 'value']
+ invert: ->
+ if @isChainable() and @first.isChainable()
+ allInvertable = yes
+ curr = this
+ while curr and curr.operator
+ allInvertable and= (curr.operator of INVERSIONS)
+ curr = curr.first
+ return new Parens(this).invert() unless allInvertable
+ curr = this
+ while curr and curr.operator
+ curr.invert = !curr.invert
+ curr.operator = INVERSIONS[curr.operator]
+ curr = curr.first
+ this
+ else if op = INVERSIONS[@operator]
+ @operator = op
+ if @first.unwrap() instanceof Op
+ @first.invert()
+ this
+ else if @second
+ new Parens(this).invert()
+ else if @operator is '!' and (fst = @first.unwrap()) instanceof Op and
+ fst.operator in ['!', 'in', 'instanceof']
+ fst
+ else
+ new Op '!', this
- compileToFragments: (o) ->
- @name.compileToFragments o, LEVEL_LIST
+ unfoldSoak: (o) ->
+ @operator in ['++', '--', 'delete'] and unfoldSoak o, this, 'first'
- asReference: (o) ->
- return @reference if @reference
- node = @name
- if node.this
- node = node.properties[0].name
- if node.value.reserved
- node = new Literal o.scope.freeVariable node.value
- else if node.isComplex()
- node = new Literal o.scope.freeVariable 'arg'
- node = new Value node
- node = new Splat node if @splat
- @reference = node
+ generateDo: (exp) ->
+ passedParams = []
+ func = if exp instanceof Assign and (ref = exp.value.unwrap()) instanceof Code
+ ref
+ else
+ exp
+ for param in func.params or []
+ if param.value
+ passedParams.push param.value
+ delete param.value
+ else
+ passedParams.push param
+ call = new Call exp, passedParams
+ call.do = yes
+ call
- isComplex: ->
- @name.isComplex()
+ compileNode: (o) ->
+ isChain = @isChainable() and @first.isChainable()
@@ -2920,20 +3146,28 @@ as well as be a splat, gathering up a group of parameters into an array.
- Iterates the name or names of a Param
.
-In a sense, a destructured parameter represents multiple JS parameters. This
-method allows to iterate them all.
-The iterator
function will be called as iterator(name, node)
where
-name
is the name of the parameter and node
is the AST node corresponding
-to that name.
-
+ In chains, there's no need to wrap bare obj literals in parens,
+as the chained expression is wrapped.
- eachName: (iterator, name = @name)->
- atParam = (obj) ->
- node = obj.properties[0].name
- iterator node.value, node unless node.value.reserved
+ @first.front = @front unless isChain
+ if @operator is 'delete' and o.scope.check(@first.unwrapAll().value)
+ @error 'delete operand may not be argument or var'
+ if @operator in ['--', '++'] and @first.unwrapAll().value in STRICT_PROSCRIBED
+ @error "cannot increment/decrement \"#{@first.unwrapAll().value}\""
+ return @compileUnary o if @isUnary()
+ return @compileChain o if isChain
+ switch @operator
+ when '?' then @compileExistence o
+ when '**' then @compilePower o
+ when '//' then @compileFloorDivision o
+ when '%%' then @compileModulo o
+ else
+ lhs = @first.compileToFragments o, LEVEL_OP
+ rhs = @second.compileToFragments o, LEVEL_OP
+ answer = [].concat lhs, @makeCode(" #{@operator} "), rhs
+ if o.level <= LEVEL_OP then answer else @wrapInBraces answer
@@ -2944,13 +3178,19 @@ to that name.
-
-
+ Mimic Python's chained comparisons when multiple comparison operators are
+used sequentially. For example:
+bin/coffee -e 'console.log 50 < 65 > 10'
+true
+
- return iterator name.value, name if name instanceof Literal
+ compileChain: (o) ->
+ [@first.second, shared] = @first.second.cache o
+ fst = @first.compileToFragments o, LEVEL_OP
+ fragments = fst.concat @makeCode(" #{if @invert then '&&' else '||'} "),
+ (shared.compileToFragments o), @makeCode(" #{@operator} "), (@second.compileToFragments o, LEVEL_OP)
+ @wrapInBraces fragments
@@ -2961,14 +3201,18 @@ to that name.
-
+ Keep reference to the left expression, unless this an existential assignment
- return atParam name if name instanceof Value
- for obj in name.objects
+ compileExistence: (o) ->
+ if @first.isComplex()
+ ref = new Literal o.scope.freeVariable 'ref'
+ fst = new Parens new Assign ref, @first
+ else
+ fst = @first
+ ref = fst
+ new If(new Existence(fst), ref, type: 'if').addElse(@second).compileToFragments o
@@ -2979,14 +3223,29 @@ to that name.
-
-- assignments within destructured parameters
{foo:bar}
-
+ Compile a unary Op.
- if obj instanceof Assign
- @eachName iterator, obj.value.unwrap()
+ compileUnary: (o) ->
+ parts = []
+ op = @operator
+ parts.push [@makeCode op]
+ if op is '!' and @first instanceof Existence
+ @first.negated = not @first.negated
+ return @first.compileToFragments o
+ if o.level >= LEVEL_ACCESS
+ return (new Parens this).compileToFragments o
+ plusMinus = op in ['+', '-']
+ parts.push [@makeCode(' ')] if op in ['new', 'typeof', 'delete'] or
+ plusMinus and @first instanceof Op and @first.operator is op
+ if (plusMinus and @first instanceof Op) or (op is 'new' and @first.isStatement o)
+ @first = new Parens @first
+ parts.push @first.compileToFragments o, LEVEL_OP
+ parts.reverse() if @flip
+ @joinFragmentArrays parts, ''
+
+ compilePower: (o) ->
@@ -2997,16 +3256,24 @@ to that name.
-
-- splats within destructured parameters
[xs...]
-
+ Make a Math.pow call
- else if obj instanceof Splat
- node = obj.name.unwrap()
- iterator node.value, node
- else if obj instanceof Value
+ pow = new Value new Literal('Math'), [new Access new Literal 'pow']
+ new Call(pow, [@first, @second]).compileToFragments o
+
+ compileFloorDivision: (o) ->
+ floor = new Value new Literal('Math'), [new Access new Literal 'floor']
+ div = new Op '/', @first, @second
+ new Call(floor, [div]).compileToFragments o
+
+ compileModulo: (o) ->
+ mod = new Value new Literal utility 'modulo'
+ new Call(mod, [@first, @second]).compileToFragments o
+
+ toString: (idt) ->
+ super idt, @constructor.name + ' ' + @operator
@@ -3017,14 +3284,22 @@ to that name.
-
-- destructured parameters within destructured parameters
[{a}]
-
+ In
- if obj.isArray() or obj.isObject()
- @eachName iterator, obj.base
+ exports.In = class In extends Base
+ constructor: (@object, @array) ->
+
+ children: ['object', 'array']
+
+ invert: NEGATE
+
+ compileNode: (o) ->
+ if @array instanceof Value and @array.isArray() and @array.base.objects.length
+ for obj in @array.base.objects when obj instanceof Splat
+ hasSplat = yes
+ break
@@ -3035,14 +3310,32 @@ to that name.
-
-- at-params within destructured parameters
{@foo}
-
+ compileOrTest
only if we have an array literal with no splats
- else if obj.this
- atParam obj
+ return @compileOrTest o unless hasSplat
+ @compileLoopTest o
+
+ compileOrTest: (o) ->
+ [sub, ref] = @object.cache o, LEVEL_OP
+ [cmp, cnj] = if @negated then [' !== ', ' && '] else [' === ', ' || ']
+ tests = []
+ for item, i in @array.base.objects
+ if i then tests.push @makeCode cnj
+ tests = tests.concat (if i then ref else sub), @makeCode(cmp), item.compileToFragments(o, LEVEL_ACCESS)
+ if o.level < LEVEL_OP then tests else @wrapInBraces tests
+
+ compileLoopTest: (o) ->
+ [sub, ref] = @object.cache o, LEVEL_LIST
+ fragments = [].concat @makeCode(utility('indexOf') + ".call("), @array.compileToFragments(o, LEVEL_LIST),
+ @makeCode(", "), ref, @makeCode(") " + if @negated then '< 0' else '>= 0')
+ return fragments if fragmentsToText(sub) is fragmentsToText(ref)
+ fragments = sub.concat @makeCode(', '), fragments
+ if o.level < LEVEL_LIST then fragments else @wrapInBraces fragments
+
+ toString: (idt) ->
+ super idt, @constructor.name + if @negated then '!' else ''
@@ -3053,16 +3346,24 @@ to that name.
-
-- simple destructured parameters {foo}
-
+ Try
+A classic try/catch/finally block.
- else iterator obj.base.value, obj.base
- else
- obj.error "illegal parameter #{obj.compile()}"
- return
+ exports.Try = class Try extends Base
+ constructor: (@attempt, @errorVariable, @recovery, @ensure) ->
+
+ children: ['attempt', 'recovery', 'ensure']
+
+ isStatement: YES
+
+ jumps: (o) -> @attempt.jumps(o) or @recovery?.jumps(o)
+
+ makeReturn: (res) ->
+ @attempt = @attempt .makeReturn res if @attempt
+ @recovery = @recovery.makeReturn res if @recovery
+ this
@@ -3070,13 +3371,35 @@ to that name.
-
+
-
Splat
+
Compilation is more or less as you would expect -- the finally clause
+is optional, the catch is not.
+
compileNode: (o) ->
+ o.indent += TAB
+ tryPart = @attempt.compileToFragments o, LEVEL_TOP
+
+ catchPart = if @recovery
+ placeholder = new Literal '_error'
+ @recovery.unshift new Assign @errorVariable, placeholder if @errorVariable
+ [].concat @makeCode(" catch ("), placeholder.compileToFragments(o), @makeCode(") {\n"),
+ @recovery.compileToFragments(o, LEVEL_TOP), @makeCode("\n#{@tab}}")
+ else unless @ensure or @recovery
+ [@makeCode(' catch (_error) {}')]
+ else
+ []
+
+ ensurePart = if @ensure then ([].concat @makeCode(" finally {\n"), @ensure.compileToFragments(o, LEVEL_TOP),
+ @makeCode("\n#{@tab}}")) else []
+
+ [].concat @makeCode("#{@tab}try {\n"),
+ tryPart,
+ @makeCode("\n#{@tab}}"), catchPart, ensurePart
+
@@ -3086,28 +3409,18 @@ to that name.
-
A splat, either as a parameter to a function, an argument to a call,
-or as part of a destructuring assignment.
-
+
Throw
+
Simple node to throw an exception.
- exports.Splat = class Splat extends Base
+ exports.Throw = class Throw extends Base
+ constructor: (@expression) ->
- children: ['name']
+ children: ['expression']
- isAssignable: YES
-
- constructor: (name) ->
- @name = if name.compile then name else new Literal name
-
- assigns: (name) ->
- @name.assigns name
-
- compileToFragments: (o) ->
- @name.compileToFragments o
-
- unwrap: -> @name
+ isStatement: YES
+ jumps: NO
@@ -3118,35 +3431,14 @@ or as part of a destructuring assignment.
- Utility function that converts an arbitrary number of elements, mixed with
-splats, to a proper array.
-
+ A Throw is already a return, of sorts...
- @compileSplattedArray: (o, list, apply) ->
- index = -1
- continue while (node = list[++index]) and node not instanceof Splat
- return [] if index >= list.length
- if list.length is 1
- node = list[0]
- fragments = node.compileToFragments o, LEVEL_LIST
- return fragments if apply
- return [].concat node.makeCode("#{ utility 'slice' }.call("), fragments, node.makeCode(")")
- args = list[index..]
- for node, i in args
- compiledNode = node.compileToFragments o, LEVEL_LIST
- args[i] = if node instanceof Splat
- then [].concat node.makeCode("#{ utility 'slice' }.call("), compiledNode, node.makeCode(")")
- else [].concat node.makeCode("["), compiledNode, node.makeCode("]")
- if index is 0
- node = list[0]
- concatPart = (node.joinFragmentArrays args[1..], ', ')
- return args[0].concat node.makeCode(".concat("), concatPart, node.makeCode(")")
- base = (node.compileToFragments o, LEVEL_LIST for node in list[...index])
- base = list[0].joinFragmentArrays base, ', '
- concatPart = list[index].joinFragmentArrays args, ', '
- [].concat list[0].makeCode("["), base, list[index].makeCode("].concat("), concatPart, (last list).makeCode(")")
+ makeReturn: THIS
+
+ compileNode: (o) ->
+ [].concat @makeCode(@tab + "throw "), @expression.compileToFragments(o), @makeCode(";")
@@ -3154,13 +3446,31 @@ splats, to a proper array.
-
+
-
While
+
Existence
+
Checks a variable for existence -- not null and not undefined. This is
+similar to .nil?
in Ruby, and avoids having to consult a JavaScript truth
+table.
+
exports.Existence = class Existence extends Base
+ constructor: (@expression) ->
+
+ children: ['expression']
+
+ invert: NEGATE
+
+ compileNode: (o) ->
+ @expression.front = @front
+ code = @expression.compile o, LEVEL_OP
+ if IDENTIFIER.test(code) and not o.scope.check code
+ [cmp, cnj] = if @negated then ['===', '||'] else ['!==', '&&']
+ code = "typeof #{code} #{cmp} \"undefined\" #{cnj} #{code} #{cmp} null"
+ else
+
@@ -3170,38 +3480,12 @@ splats, to a proper array.
-
A while loop, the only sort of low-level loop exposed by CoffeeScript. From
-it, all other loops can be manufactured. Useful in cases where you need more
-flexibility or more speed than a comprehension can provide.
-
+
do not use strict equality here; it will break existing code
- exports.While = class While extends Base
- constructor: (condition, options) ->
- @condition = if options?.invert then condition.invert() else condition
- @guard = options?.guard
-
- children: ['condition', 'guard', 'body']
-
- isStatement: YES
-
- makeReturn: (res) ->
- if res
- super
- else
- @returns = not @jumps loop: yes
- this
-
- addBody: (@body) ->
- this
-
- jumps: ->
- {expressions} = @body
- return no unless expressions.length
- for node in expressions
- return node if node.jumps loop: yes
- no
+ code = "#{code} #{if @negated then '==' else '!='} null"
+ [@makeCode(if o.level <= LEVEL_COND then code else "(#{code})")]
@@ -3212,34 +3496,31 @@ flexibility or more speed than a comprehension can provide.
- The main difference from a JavaScript while is that the CoffeeScript
-while can be used as a part of a larger expression -- while loops may
-return an array containing the computed result of each iteration.
-
+ Parens
+An extra set of parentheses, specified explicitly in the source. At one time
+we tried to clean up the results by detecting and removing redundant
+parentheses, but no longer -- you can put in as many as you please.
+Parentheses are a good way to force any statement to become an expression.
- compileNode: (o) ->
- o.indent += TAB
- set = ''
- {body} = this
- if body.isEmpty()
- body = @makeCode ''
- else
- if @returns
- body.makeReturn rvar = o.scope.freeVariable 'results'
- set = "#{@tab}#{rvar} = [];\n"
- if @guard
- if body.expressions.length > 1
- body.expressions.unshift new If (new Parens @guard).invert(), new Literal "continue"
- else
- body = Block.wrap [new If @guard, body] if @guard
- body = [].concat @makeCode("\n"), (body.compileToFragments o, LEVEL_TOP), @makeCode("\n#{@tab}")
- answer = [].concat @makeCode(set + @tab + "while ("), @condition.compileToFragments(o, LEVEL_PAREN),
- @makeCode(") {"), body, @makeCode("}")
- if @returns
- answer.push @makeCode "\n#{@tab}return #{rvar};"
- answer
+ exports.Parens = class Parens extends Base
+ constructor: (@body) ->
+
+ children: ['body']
+
+ unwrap : -> @body
+ isComplex : -> @body.isComplex()
+
+ compileNode: (o) ->
+ expr = @body.unwrap()
+ if expr instanceof Value and expr.isAtomic()
+ expr.front = @front
+ return expr.compileToFragments o
+ fragments = expr.compileToFragments o, LEVEL_PAREN
+ bare = o.level < LEVEL_OP and (expr instanceof Op or expr instanceof Call or
+ (expr instanceof For and expr.returns))
+ if bare then fragments else @wrapInBraces fragments
@@ -3247,13 +3528,36 @@ return an array containing the computed result of each iteration.
-
+
-
Op
+
For
+
CoffeeScript's replacement for the for loop is our array and object
+comprehensions, that compile into for loops here. They also act as an
+expression, able to return the result of each filtered iteration.
+
Unlike Python array comprehensions, they can be multi-line, and you can pass
+the current index of the loop as a second parameter. Unlike Ruby blocks,
+you can map and filter in a single pass.
+
exports.For = class For extends While
+ constructor: (body, source) ->
+ {@source, @guard, @step, @name, @index} = source
+ @body = Block.wrap [body]
+ @own = !!source.own
+ @object = !!source.object
+ [@name, @index] = [@index, @name] if @object
+ @index.error 'index cannot be a pattern matching expression' if @index instanceof Value
+ @range = @source instanceof Value and @source.base instanceof Range and not @source.properties.length
+ @pattern = @name instanceof Value
+ @index.error 'indexes do not apply to range loops' if @range and @index
+ @name.error 'cannot pattern match over range loops' if @range and @pattern
+ @name.error 'cannot use own with for-in' if @own and not @object
+ @returns = false
+
+ children: ['body', 'source', 'guard', 'step']
+
@@ -3263,25 +3567,105 @@ return an array containing the computed result of each iteration.
-
Simple Arithmetic and logical operations. Performs some conversion from
-CoffeeScript operations into their JavaScript equivalents.
-
+
Welcome to the hairiest method in all of CoffeeScript. Handles the inner
+loop, filtering, stepping, and result saving for array, object, and range
+comprehensions. Some of the generated code can be shared in common, and
+some cannot.
- exports.Op = class Op extends Base
- constructor: (op, first, second, flip ) ->
- return new In first, second if op is 'in'
- if op is 'do'
- return @generateDo first
- if op is 'new'
- return first.newInstance() if first instanceof Call and not first.do and not first.isNew
- first = new Parens first if first instanceof Code and first.bound or first.do
- @operator = CONVERSIONS[op] or op
- @first = first
- @second = second
- @flip = !!flip
- return this
+ compileNode: (o) ->
+ body = Block.wrap [@body]
+ lastJumps = last(body.expressions)?.jumps()
+ @returns = no if lastJumps and lastJumps instanceof Return
+ source = if @range then @source.base else @source
+ scope = o.scope
+ name = @name and (@name.compile o, LEVEL_LIST) if not @pattern
+ index = @index and (@index.compile o, LEVEL_LIST)
+ scope.find(name) if name and not @pattern
+ scope.find(index) if index
+ rvar = scope.freeVariable 'results' if @returns
+ ivar = (@object and index) or scope.freeVariable 'i'
+ kvar = (@range and name) or index or ivar
+ kvarAssign = if kvar isnt ivar then "#{kvar} = " else ""
+ if @step and not @range
+ [step, stepVar] = @cacheToCodeFragments @step.cache o, LEVEL_LIST
+ stepNum = stepVar.match NUMBER
+ name = ivar if @pattern
+ varPart = ''
+ guardPart = ''
+ defPart = ''
+ idt1 = @tab + TAB
+ if @range
+ forPartFragments = source.compileToFragments merge(o, {index: ivar, name, @step})
+ else
+ svar = @source.compile o, LEVEL_LIST
+ if (name or @own) and not IDENTIFIER.test svar
+ defPart += "#{@tab}#{ref = scope.freeVariable 'ref'} = #{svar};\n"
+ svar = ref
+ if name and not @pattern
+ namePart = "#{name} = #{svar}[#{kvar}]"
+ if not @object
+ defPart += "#{@tab}#{step};\n" if step isnt stepVar
+ lvar = scope.freeVariable 'len' unless @step and stepNum and down = (parseNum(stepNum[0]) < 0)
+ declare = "#{kvarAssign}#{ivar} = 0, #{lvar} = #{svar}.length"
+ declareDown = "#{kvarAssign}#{ivar} = #{svar}.length - 1"
+ compare = "#{ivar} < #{lvar}"
+ compareDown = "#{ivar} >= 0"
+ if @step
+ if stepNum
+ if down
+ compare = compareDown
+ declare = declareDown
+ else
+ compare = "#{stepVar} > 0 ? #{compare} : #{compareDown}"
+ declare = "(#{stepVar} > 0 ? (#{declare}) : #{declareDown})"
+ increment = "#{ivar} += #{stepVar}"
+ else
+ increment = "#{if kvar isnt ivar then "++#{ivar}" else "#{ivar}++"}"
+ forPartFragments = [@makeCode("#{declare}; #{compare}; #{kvarAssign}#{increment}")]
+ if @returns
+ resultPart = "#{@tab}#{rvar} = [];\n"
+ returnResult = "\n#{@tab}return #{rvar};"
+ body.makeReturn rvar
+ if @guard
+ if body.expressions.length > 1
+ body.expressions.unshift new If (new Parens @guard).invert(), new Literal "continue"
+ else
+ body = Block.wrap [new If @guard, body] if @guard
+ if @pattern
+ body.expressions.unshift new Assign @name, new Literal "#{svar}[#{kvar}]"
+ defPartFragments = [].concat @makeCode(defPart), @pluckDirectCall(o, body)
+ varPart = "\n#{idt1}#{namePart};" if namePart
+ if @object
+ forPartFragments = [@makeCode("#{kvar} in #{svar}")]
+ guardPart = "\n#{idt1}if (!#{utility 'hasProp'}.call(#{svar}, #{kvar})) continue;" if @own
+ bodyFragments = body.compileToFragments merge(o, indent: idt1), LEVEL_TOP
+ if bodyFragments and (bodyFragments.length > 0)
+ bodyFragments = [].concat @makeCode("\n"), bodyFragments, @makeCode("\n")
+ [].concat defPartFragments, @makeCode("#{resultPart or ''}#{@tab}for ("),
+ forPartFragments, @makeCode(") {#{guardPart}#{varPart}"), bodyFragments,
+ @makeCode("#{@tab}}#{returnResult or ''}")
+
+ pluckDirectCall: (o, body) ->
+ defs = []
+ for expr, idx in body.expressions
+ expr = expr.unwrapAll()
+ continue unless expr instanceof Call
+ val = expr.variable?.unwrapAll()
+ continue unless (val instanceof Code) or
+ (val instanceof Value and
+ val.base?.unwrapAll() instanceof Code and
+ val.properties.length is 1 and
+ val.properties[0].name?.value in ['call', 'apply'])
+ fn = val.base?.unwrapAll() or val
+ ref = new Literal o.scope.freeVariable 'fn'
+ base = new Value ref
+ if val.base
+ [val.base, base] = [base, val]
+ body.expressions[idx] = new Call base, expr.args
+ defs = defs.concat @makeCode(@tab), (new Assign(ref, fn).compileToFragments(o, LEVEL_TOP)), @makeCode(';\n')
+ defs
@@ -3292,15 +3676,48 @@ CoffeeScript operations into their JavaScript equivalents.
- The map of conversions from CoffeeScript to JavaScript symbols.
-
+ Switch
+A JavaScript switch statement. Converts into a returnable expression on-demand.
- CONVERSIONS =
- '==': '==='
- '!=': '!=='
- 'of': 'in'
+ exports.Switch = class Switch extends Base
+ constructor: (@subject, @cases, @otherwise) ->
+
+ children: ['subject', 'cases', 'otherwise']
+
+ isStatement: YES
+
+ jumps: (o = {block: yes}) ->
+ for [conds, block] in @cases
+ return jumpNode if jumpNode = block.jumps o
+ @otherwise?.jumps o
+
+ makeReturn: (res) ->
+ pair[1].makeReturn res for pair in @cases
+ @otherwise or= new Block [new Literal 'void 0'] if res
+ @otherwise?.makeReturn res
+ this
+
+ compileNode: (o) ->
+ idt1 = o.indent + TAB
+ idt2 = o.indent = idt1 + TAB
+ fragments = [].concat @makeCode(@tab + "switch ("),
+ (if @subject then @subject.compileToFragments(o, LEVEL_PAREN) else @makeCode "false"),
+ @makeCode(") {\n")
+ for [conditions, block], i in @cases
+ for cond in flatten [conditions]
+ cond = cond.invert() unless @subject
+ fragments = fragments.concat @makeCode(idt1 + "case "), cond.compileToFragments(o, LEVEL_PAREN), @makeCode(":\n")
+ fragments = fragments.concat body, @makeCode('\n') if (body = block.compileToFragments o, LEVEL_TOP).length > 0
+ break if i is @cases.length - 1 and not @otherwise
+ expr = @lastNonComment block.expressions
+ continue if expr instanceof Return or (expr instanceof Literal and expr.jumps() and expr.value isnt 'debugger')
+ fragments.push cond.makeCode(idt2 + 'break;\n')
+ if @otherwise and @otherwise.expressions.length
+ fragments.push @makeCode(idt1 + "default:\n"), (@otherwise.compileToFragments o, LEVEL_TOP)..., @makeCode("\n")
+ fragments.push @makeCode @tab + '}'
+ fragments
@@ -3311,24 +3728,25 @@ CoffeeScript operations into their JavaScript equivalents.
- The map of invertible operators.
-
+ If
+If/else statements. Acts as an expression by pushing down requested returns
+to the last line of each clause.
+Single-expression Ifs are compiled into conditional operators if possible,
+because ternaries are already proper expressions, and don't need conversion.
- INVERSIONS =
- '!==': '==='
- '===': '!=='
+ exports.If = class If extends Base
+ constructor: (condition, @body, options = {}) ->
+ @condition = if options.type is 'unless' then condition.invert() else condition
+ @elseBody = null
+ @isChain = false
+ {@soak} = options
- children: ['first', 'second']
+ children: ['condition', 'body', 'elseBody']
- isSimpleNumber: NO
-
- isUnary: ->
- not @second
-
- isComplex: ->
- not (@isUnary() and @operator in ['+', '-']) or @first.isComplex()
+ bodyNode: -> @body?.unwrap()
+ elseBodyNode: -> @elseBody?.unwrap()
@@ -3339,63 +3757,18 @@ CoffeeScript operations into their JavaScript equivalents.
- Am I capable of
-Python-style comparison chaining?
-
+ Rewrite a chain of Ifs to add a default case as the final else.
- isChainable: ->
- @operator in ['<', '>', '>=', '<=', '===', '!==']
-
- invert: ->
- if @isChainable() and @first.isChainable()
- allInvertable = yes
- curr = this
- while curr and curr.operator
- allInvertable and= (curr.operator of INVERSIONS)
- curr = curr.first
- return new Parens(this).invert() unless allInvertable
- curr = this
- while curr and curr.operator
- curr.invert = !curr.invert
- curr.operator = INVERSIONS[curr.operator]
- curr = curr.first
- this
- else if op = INVERSIONS[@operator]
- @operator = op
- if @first.unwrap() instanceof Op
- @first.invert()
- this
- else if @second
- new Parens(this).invert()
- else if @operator is '!' and (fst = @first.unwrap()) instanceof Op and
- fst.operator in ['!', 'in', 'instanceof']
- fst
- else
- new Op '!', this
-
- unfoldSoak: (o) ->
- @operator in ['++', '--', 'delete'] and unfoldSoak o, this, 'first'
-
- generateDo: (exp) ->
- passedParams = []
- func = if exp instanceof Assign and (ref = exp.value.unwrap()) instanceof Code
- ref
- else
- exp
- for param in func.params or []
- if param.value
- passedParams.push param.value
- delete param.value
- else
- passedParams.push param
- call = new Call exp, passedParams
- call.do = yes
- call
-
- compileNode: (o) ->
- isChain = @isChainable() and @first.isChainable()
+ addElse: (elseBody) ->
+ if @isChain
+ @elseBodyNode().addElse elseBody
+ else
+ @isChain = elseBody instanceof If
+ @elseBody = @ensureBlock elseBody
+ @elseBody.updateLocationDataIfMissing elseBody.locationData
+ this
@@ -3406,23 +3779,28 @@ CoffeeScript operations into their JavaScript equivalents.
- In chains, there's no need to wrap bare obj literals in parens,
-as the chained expression is wrapped.
-
+ The If only compiles into a statement if either of its bodies needs
+to be a statement. Otherwise a conditional operator is safe.
- @first.front = @front unless isChain
- if @operator is 'delete' and o.scope.check(@first.unwrapAll().value)
- @error 'delete operand may not be argument or var'
- if @operator in ['--', '++'] and @first.unwrapAll().value in STRICT_PROSCRIBED
- @error "cannot increment/decrement \"#{@first.unwrapAll().value}\""
- return @compileUnary o if @isUnary()
- return @compileChain o if isChain
- return @compileExistence o if @operator is '?'
- answer = [].concat @first.compileToFragments(o, LEVEL_OP), @makeCode(' ' + @operator + ' '),
- @second.compileToFragments(o, LEVEL_OP)
- if o.level <= LEVEL_OP then answer else @wrapInBraces answer
+ isStatement: (o) ->
+ o?.level is LEVEL_TOP or
+ @bodyNode().isStatement(o) or @elseBodyNode()?.isStatement(o)
+
+ jumps: (o) -> @body.jumps(o) or @elseBody?.jumps(o)
+
+ compileNode: (o) ->
+ if @isStatement o then @compileStatement o else @compileExpression o
+
+ makeReturn: (res) ->
+ @elseBody or= new Block [new Literal 'void 0'] if res
+ @body and= new Block [@body.makeReturn res]
+ @elseBody and= new Block [@elseBody.makeReturn res]
+ this
+
+ ensureBlock: (node) ->
+ if node instanceof Block then node else new Block [node]
@@ -3433,21 +3811,31 @@ as the chained expression is wrapped.
- Mimic Python's chained comparisons when multiple comparison operators are
-used sequentially. For example:
-
-
-bin/coffee -e 'console.log 50 < 65 > 10'
-true
+ Compile the If
as a regular if-else statement. Flattened chains
+force inner else bodies into statement form.
- compileChain: (o) ->
- [@first.second, shared] = @first.second.cache o
- fst = @first.compileToFragments o, LEVEL_OP
- fragments = fst.concat @makeCode(" #{if @invert then '&&' else '||'} "),
- (shared.compileToFragments o), @makeCode(" #{@operator} "), (@second.compileToFragments o, LEVEL_OP)
- @wrapInBraces fragments
+ compileStatement: (o) ->
+ child = del o, 'chainChild'
+ exeq = del o, 'isExistentialEquals'
+
+ if exeq
+ return new If(@condition.invert(), @elseBodyNode(), type: 'if').compileToFragments o
+
+ indent = o.indent + TAB
+ cond = @condition.compileToFragments o, LEVEL_PAREN
+ body = @ensureBlock(@body).compileToFragments merge o, {indent}
+ ifPart = [].concat @makeCode("if ("), cond, @makeCode(") {\n"), body, @makeCode("\n#{@tab}}")
+ ifPart.unshift @makeCode @tab unless child
+ return ifPart unless @elseBody
+ answer = ifPart.concat @makeCode(' else ')
+ if @isChain
+ o.chainChild = yes
+ answer = answer.concat @elseBody.unwrap().compileToFragments o, LEVEL_TOP
+ else
+ answer = answer.concat @makeCode("{\n"), @elseBody.compileToFragments(merge(o, {indent}), LEVEL_TOP), @makeCode("\n#{@tab}}")
+ answer
@@ -3458,19 +3846,19 @@ true