• Jump To … +
    browser.coffee cake.coffee coffee-script.coffee command.coffee grammar.coffee helpers.coffee iced.coffee icedlib.coffee index.coffee lexer.coffee nodes.coffee optparse.coffee repl.coffee rewriter.coffee scope.litcoffee sourcemap.litcoffee
  • nodes.coffee

  • ¶

    nodes.coffee contains all of the node classes for the syntax tree. Most nodes are created as the result of actions in the grammar, but some are created by other nodes as a method of code generation. To convert the syntax tree into a string of JavaScript code, call compile() on the root.

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

    Import the helpers we plan to use.

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

    Functions required by parser

    exports.extend = extend
    exports.addLocationDataFn = addLocationDataFn
  • ¶

    Constant functions for nodes that don't need customization.

    YES     = -> yes
    NO      = -> no
    THIS    = -> this
    NEGATE  = -> @negated = not @negated; this
    NULL    = -> new Value new Literal 'null'
  • ¶

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

    Convert an array of CodeFragments into a string.

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

    Base

    The Base is the abstract base class for all nodes in the syntax tree. Each subclass implements the compileNode method, which performs the 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.

    exports.Base = class Base
    
      constructor: ->
        @icedContinuationBlock = null
  • ¶

    iced AST node flags -- since we make several passes through the tree setting these bits, we'll actually just flip bits in the nodes, rather than setting function pointers to YES or NO.

        @icedLoopFlag        = false
        @icedNodeFlag        = false
        @icedGotCpsSplitFlag = false
        @icedCpsPivotFlag    = false
        @icedHasAutocbFlag   = false
        @icedFoundArguments  = false
        @icedParentAwait     = null
        @icedCallContinuationFlag = false
  • ¶

    Common logic for determining whether to wrap this node in a closure before

      compile: (o, lvl) ->
        fragmentsToText @compileToFragments o, lvl
  • ¶

    Common logic for determining whether to wrap this node in a closure before compiling it, or to compile directly. We need to wrap if this node is a statement, and it's not a pureStatement, and we're not at 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 node.icedHasContinuation() and not node.icedGotCpsSplitFlag
          node.icedCompileCps o
        else if o.level is LEVEL_TOP or not node.isStatement(o)
          node.compileNode o
        else
          node.compileClosure o
  • ¶

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

      compileClosure: (o) ->
        if jumpNode = @jumps()
          jumpNode.error 'cannot use a pure statement in an expression'
        o.sharedScope = yes
  • ¶

    Solution for an iced corner case:

    foo = (autocb) -> x = (i for i in [0..10]) x

    We don't want the autocb to fire in the evaluation of the list comprehension on the RHS.

        @icedClearAutocbFlags()
        Closure.wrap(this).compileNode o
  • ¶

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

    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.

      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])]
  • ¶

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

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

    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.

      contains: (pred) ->
        node = undefined
        @traverseChildren no, (n) ->
          if pred n
            node = n
            return no
        node
  • ¶

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

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

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

      toString: (idt = '', name = @constructor.name) ->
        extras = [] 
        extras.push "A" if @icedNodeFlag
        extras.push "L" if @icedLoopFlag
        extras.push "P" if @icedCpsPivotFlag
        extras.push "C" if @icedHasAutocbFlag
        extras.push "D" if @icedParentAwait
        extras.push "G" if @icedFoundArguments
        if extras.length
          extras = " (" + extras.join('') + ")"
        tree = '\n' + idt + name
        tree = '\n' + idt + name
        tree += '?' if @soak
        tree += extras
        @eachChild (node) -> tree += node.toString idt + TAB
        if @icedContinuationBlock
          idt += TAB
          tree += '\n' + idt + "Continuation"
          tree += @icedContinuationBlock.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
  • ¶

    Start iced additions...

    Don't try this at home with actual human kids. Added for iced for slightly different tree traversal mechanics.

      flattenChildren : ->
        out = []
        for attr in @children when @[attr]
          for child in flatten [@[attr]]
            out.push (child)
        out
  • ¶

    Statements that need CPS translation will have to be split into pieces like so.

      icedCompileCps : (o) ->
        @icedGotCpsSplitFlag = true
        code = CpsCascade.wrap this, @icedContinuationBlock, null, o
        code.compileNode o
  • ¶

    If the code generation wishes to use the result of a complex expression

    AST Walking Routines for CPS Pivots, etc.

    There are three passes: 1. Find await's and trace upward. 2. Find loops found in #1, and flood downward 3. Find break/continue found in #2, and trace upward

    icedWalkAst

    Walk the AST looking for taming. Mark a node as with iced flags if any of its children are iced, but don't cross scope boundary when considering the children.

    The paremeter p is the parent await. All nodes beneath the first await in a function scope should point to its highest parent await. This is so in the case of nested awaits, they're really pulled out and run in sequence as the level of the topmost await.

    The parameter o is a global object, passed through all without copies, to push information up and down the AST. This parameter is used with subfields:

     o.foundAutocb    -- on if the parent function has an autocb
     o.foundDefer     -- on if defer() was found anywhere in the AST
     o.foundAwait     -- on if await... was found anywhere in the AST
     o.foundAwaitFunc -- on if await found in this func
     o.currFunc       -- the current func we're in
     o.foundArguments -- on if we found reference to 'arguments'
      icedWalkAst : (p, o) ->
        @icedParentAwait = p
        @icedHasAutocbFlag = o.foundAutocb
        for child in @flattenChildren()
          @icedNodeFlag = true if child.icedWalkAst p, o
        @icedNodeFlag
  • ¶

    icedWalkAstLoops Walk all loops that are marked as "iced" and mark their children as being children in a iced loop. They'll need more translations than other nodes. Eventually, "switch" statements might also be "loops"

      icedWalkAstLoops : (flood) ->
        flood = true if  @isLoop() and @icedNodeFlag
        flood = false if @isLoop() and not @icedNodeFlag
        @icedLoopFlag = flood
        for child in @flattenChildren()
          @icedLoopFlag = true if child.icedWalkAstLoops flood
        @icedLoopFlag
  • ¶

    icedWalkCpsPivots A node is marked as a "cpsPivot" of it is (a) a 'iced' node, (b) a jump node in a iced while loop; or (c) an ancestor of (a) or (b).

      icedWalkCpsPivots : ->
        @icedCpsPivotFlag = true if @icedNodeFlag or (@icedLoopFlag and @icedIsJump())
        for child in @flattenChildren()
          @icedCpsPivotFlag = true if child.icedWalkCpsPivots()
        @icedCpsPivotFlag
    
      icedClearAutocbFlags : ->
        @icedHasAutocbFlag = false
        @traverseChildren false, (node) ->
          node.icedHasAutocbFlag = false
          true
  • ¶

    A generic iced AST rotation is just to push down to its children

      icedCpsRotate: ->
        for child in @flattenChildren()
          child.icedCpsRotate()
        this
    
      icedIsCpsPivot            :     -> @icedCpsPivotFlag
      icedNestContinuationBlock : (b) -> @icedContinuationBlock = b
      icedHasContinuation       :     -> (!!@icedContinuationBlock)
      icedCallContinuation      :     -> @icedCallContinuationFlag = true
      icedWrapContinuation      :     NO
      icedIsJump                :     NO
    
      icedUnwrap: (e) ->
        if e.icedHasContinuation() and @icedHasContinuation()
          this
        else
          if @icedHasContinuation()
            e.icedContinuationBlock = @icedContinuationBlock
          e
    
      icedStatementAssertion : () ->
        @error "await'ed statements can't act as expressions" if @icedIsCpsPivot()
  • ¶

    End iced additions...

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

      children: []
    
      isStatement     : NO
      jumps           : NO
      isComplex       : YES
      isChainable     : NO
      isAssignable    : NO
      isLoop          : NO
    
      unwrap     : THIS
      unfoldSoak : NO
  • ¶

    Is this node used to assign a certain variable?

      assigns: NO
  • ¶

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

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

    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(')')
  • ¶

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

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

    Block

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

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

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

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

    Remove and return the last expression of this expression list.

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

    Add an expression at the beginning of this expression list.

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

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

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

    Is this an empty block of code?

      isEmpty: ->
        not @expressions.length
    
      isStatement: (o) ->
        for exp in @expressions when exp.isStatement o
          return yes
        no
    
      jumps: (o) ->
        for exp in @expressions
          return exp if exp.jumps o
  • ¶

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

      makeReturn: (res) ->
        len = @expressions.length
        foundReturn = false
        while len--
          expr = @expressions[len]
          if expr not instanceof Comment
            @expressions[len] = expr.makeReturn res
            if expr instanceof Return and
               not expr.expression and not expr.icedHasAutocbFlag
              @expressions.splice(len, 1)
              foundReturn = true
            else if not (expr instanceof If) or expr.elseBody
              foundReturn = true
            break
        if @icedHasAutocbFlag and not @icedNodeFlag and not foundReturn
          @expressions.push(new Return null, true)
        this
  • ¶

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

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

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

      compileNode: (o) ->
        @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
  • ¶

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

            compiledNodes.push node.compileNode o
          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
  • ¶

    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.

      compileRoot: (o) ->
        o.indent  = if o.bare then '' else TAB
        o.level   = LEVEL_TOP
        @spaced   = yes
        o.scope   = new Scope null, this, null
  • ¶

    Mark given local variables in the root scope as parameters so they don't end up being declared on this 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")
  • ¶

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

      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
  • ¶

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

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

    Start iced additions

    When returning from a block, we need to maybe call into a continuation. This call will thread that "return / call-continuation" through this block.

      icedThreadReturn: (call)  ->
        call = call || new IcedTailCall
        len = @expressions.length
        while len--
          expr = @expressions[len]
  • ¶

    If the last expression in the block is either a bonafide statement or if it's going to be pivoted, then don't thread the return value through the IcedTailCall, just bolt it onto the end.

          if expr.isStatement()
            break
  • ¶

    In this case, we have a value that we're going to return out of the block, so apply the IcedTamilCall onto the value

          if expr not instanceof Comment and expr not instanceof Return
            call.assignValue expr
            @expressions[len] = call
            return
  • ¶

    if nothing was found, just push the call on

        @expressions.push call
  • ¶

    Optimization! Blocks typically don't need their own cpsCascading. This saves wasted code.

      icedCompileCps : (o) ->
        @icedGotCpsSplitFlag = true
        if @expressions.length > 1
          super o
        else
          @compileNode o
  • ¶

    icedCpsRotate -- This is the key abstract syntax tree rotation of the CPS translation. Take a block with a bunch of sequential statements and "pivot" the AST on the first available pivot. The expressions on the LHS of the pivot stay where the are. The expressions on the RHS of the pivot become the pivot's continuation. And the process is applied recursively.

      icedCpsRotate : ->
        pivot = null
  • ¶

    Go ahead an look for a pivot

        for e,i in @expressions
          if e.icedIsCpsPivot()
            pivot = e
  • ¶

    The pivot value needs to call the currently active continuation after it's all done. For things like if..else.. this does something interesting and pushes the continuation down both branches. Note that it's convenient to do this before anything is rotated.

            pivot.icedCallContinuation()
  • ¶

    Recursively rotate the children, in depth-first order.

          e.icedCpsRotate()
  • ¶

    If we've found a pivot, then we break out of here, and then handle the rest of these children

          break if pivot
  • ¶

    If there's no pivot, then the above should be as in the base class, and it's safe to return out of here.

    We find a pivot if this node has taming, and it's not an Await itself.

        return this unless pivot
  • ¶

    We should never have a continuation here, even though we rotated this guy above. This is true because: 1. The Pivot must be a statement.... 2. If pivot is a statement, then the continuation will be in the grandchild Block node

        if pivot.icedContinuationBlock
          throw SyntaxError "unexpected continuation block in node"
  • ¶

    These are the expressions on the RHS of the pivot split

        rest = @expressions.slice(i+1)
  • ¶

    Leave the pivot in the list of expressions

        @expressions = @expressions.slice(0,i+1)
  • ¶

    If there are elements in rest, then we need to nest a continuation block

        if rest.length
          child = new Block rest
          pivot.icedNestContinuationBlock child
  • ¶

    Pass our node bits onto our new children

          for e in rest
            child.icedNodeFlag = true      if e.icedNodeFlag
            child.icedLoopFlag = true      if e.icedLoopFlag
            child.icedCpsPivotFlag = true  if e.icedCpsPivotFlag
            child.icedHasAutocbFlag = true if e.icedHasAutocbFlag
  • ¶

    now recursive apply the transformation to the new child, this being especially important in blocks that have multiple awaits on the same level

          child.icedCpsRotate()
  • ¶

    return this for chaining

        this
  • ¶

    Paste in a require or inline code, depending on the strategy requested

      icedAddRuntime : (foundDefer, foundAwait) ->
        index = 0
        while (node = @expressions[index]) and node instanceof Comment or
            node instanceof Value and node.isString()
          index++
        @expressions.splice index, 0, (new IcedRuntime foundDefer, foundAwait)
  • ¶

    Perform all steps of the Iced transform

      icedTransform : (opts) ->
  • ¶

    we need to do at least 1 walk -- do the most important walk first

        obj = {}
        @icedWalkAst null, obj
  • ¶

    Add a runtime if necessary, but don't add a runtime for the REPL. For some reason, even outputting an empty runtime doesn't work as far as the REPL is concerned.

        @icedAddRuntime obj.foundDefer, obj.foundAwait unless opts?.repl
  • ¶

    short-circuit here for optimization. If we didn't find await then no need to iced anything in this AST

        if obj.foundAwait
          @icedWalkAstLoops false
          @icedWalkCpsPivots()
          @icedCpsRotate()
    
        this
  • ¶

    Like unwrap, but will return if not a single

      icedGetSingle : ->
        if @expressions.length is 1 then @expressions[0] else null
  • ¶

    end iced additions

    Literal

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

    exports.Literal = class Literal extends Base
      constructor: (@value) ->
        super()
    
      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) ->
  • ¶

    Don't tun this code through @makeCode below....

        return @icedCompileIced o if @icedLoopFlag and @icedIsJump()
    
        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 + '"'
    
      icedWalkAst : (parent, o) ->
        if @value is 'arguments' and o.foundAwaitFunc
          o.foundArguments = true
          @value = "_arguments"
        false
    
      icedIsJump : -> @isStatement()
    
      icedCompileIced: (o) ->
        d =
          'continue' : iced.const.c_while
          'break'    : iced.const.b_while
        l = d[@value]
        func = new Value new Literal l
        call = new Call func, []
        return call.compileNode o
    
    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) ->
  • ¶

    Return

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

    exports.Return = class Return extends Base
      constructor: (expr, auto) ->
        super()
        @icedHasAutocbFlag = auto
        @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) ->
        return @icedCompileIced o if @icedHasAutocbFlag
    
        answer = []
  • ¶

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

        answer.push @makeCode @tab + "return#{if @expression then " " else ""}"
        if @expression
          answer = answer.concat @expression.compileToFragments o, LEVEL_PAREN
        answer.push @makeCode ";"
        return answer
    
      icedCompileIced : (o) ->
        cb = new Value new Literal iced.const.autocb
        args = if @expression then [ @expression ] else []
        call = new Call cb, args
        ret = new Literal "return"
        block = new Block [ call, ret];
        block.compileNode o
  • ¶

    Value

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

    exports.Value = class Value extends Base
      constructor: (base, props, tag) ->
        super()
        return base if not props and base instanceof Value
        @base       = base
        @properties = props or []
        @[tag]      = true if tag
        return this
    
      children: ['base', 'properties']
  • ¶

    Minor iced addition for convenience

      copy : -> new Value @base, @properties
  • ¶

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

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

    Some boolean checks for the benefit of other nodes.

      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
    
      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
  • ¶

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

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

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

      cacheReference: (o) ->
        name = last @properties
        if @properties.length < 2 and not @base.isComplex() and not name?.isComplex()
          return [this, this]  # `a` `a.b`
        base = new Value @base, @properties[...-1]
        if base.isComplex()  # `a().b`
          bref = new Literal o.scope.freeVariable 'base'
          base = new Value new Parens new Assign bref, base
        return [base, bref] unless name  # `a()`
        if name.isComplex()  # `a[b()]`
          nref = new Literal o.scope.freeVariable 'name'
          name = new Index new Assign nref, name.index
          nref = new Index nref
        [base.add(name), new Value(bref or base.base, [nref or name])]
  • ¶

    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
  • ¶

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

      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
  • ¶

    If this value is being used as a slot for the purposes of a defer then export it here

      icedToSlot : (i) ->
        return @base.icedToSlot i if @base instanceof Obj
        sufffix = null
        if @properties and @properties.length
          suffix = @properties.pop()
        return new Slot i, this, suffix
    
      icedToSlotAccess : () ->
  • ¶

    See bug #78 in the ICS repository. We're concerned about this case: await foo defer { @x } In this situation, @x will be represented as a value with the this property set to true, and properties[0] will have the name of the dictionary key that's needed (already as an Access instance)

        if @this then @properties[0]
        else new Access @
  • ¶

    Comment

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

    exports.Comment = class Comment extends Base
      constructor: (@comment) ->
        super()
    
      isStatement:     YES
      makeReturn:      THIS
    
      compileNode: (o, level) ->
        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)]
  • ¶

    Call

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

    exports.Call = class Call extends Base
      constructor: (variable, @args = [], @soak) ->
        super()
        @isNew    = false
        @isSuper  = variable is 'super'
        @variable = if @isSuper then null else variable
    
      children: ['variable', 'args']
  • ¶

    Tag this invocation as creating a new instance.

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

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

      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.'
  • ¶

    The appropriate this value for a super call.

      superThis : (o) ->
        if o.scope.icedgen then "_this"
        else
          method = o.scope.method
          (method and not method.klass and method.context) or "this"
  • ¶

    Soaked chained invocations unfold into if/else ternary structures.

      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
  • ¶

    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
          arg.icedStatementAssertion()
          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
  • ¶

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

      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(")")
  • ¶

    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) ->
        super()
    
      children: ['child', 'parent']
  • ¶

    Hooks one constructor into another's prototype chain.

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

    Access

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

    exports.Access = class Access extends Base
      constructor: (@name, tag) ->
        super()
        @name.asKey = yes
        @soak  = tag is 'soak'
    
      children: ['name']
    
      compileToFragments: (o) ->
        name = @name.compileToFragments o
        if (IDENTIFIER.test fragmentsToText name) or @name instanceof Defer
          name.unshift @makeCode "."
        else
          name.unshift @makeCode "["
          name.push @makeCode "]"
        name
    
      isComplex: NO
  • ¶

    Index

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

    exports.Index = class Index extends Base
      constructor: (@index) ->
        super()
    
      children: ['index']
    
      compileToFragments: (o) ->
        [].concat @makeCode("["), @index.compileToFragments(o, LEVEL_PAREN), @makeCode("]")
    
      isComplex: ->
        @index.isComplex()
  • ¶

    Range

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

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

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

      compileVariables: (o) ->
        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
  • ¶

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

    Set up endpoints.

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

    Generate the condition.

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

    Generate the step.

        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
  • ¶

    The final loop body.

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

    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 (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 ? ''})"]
  • ¶

    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.Slice = class Slice extends Base
    
      children: ['range']
    
      constructor: (@range) ->
        super()
  • ¶

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

      compileNode: (o) ->
        {to, from} = @range
        fromCompiled = from and from.compileToFragments(o, LEVEL_PAREN) or [@makeCode '0']
  • ¶

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

        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 '' })"]
  • ¶

    Obj

    An object literal, nothing fancy.

    exports.Obj = class Obj extends Base
      constructor: (props, @generated = false) ->
        @objects = @properties = props or []
        super()
    
      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
    
      icedToSlot : (i) ->
        for prop in @properties
          if prop instanceof Assign
            (prop.value.icedToSlot i).addAccess prop.variable.icedToSlotAccess()
          else if prop instanceof Value
            access = prop.icedToSlotAccess()
            (prop.icedToSlot i).addAccess access
  • ¶

    Arr

    An array literal.

    exports.Arr = class Arr extends Base
      constructor: (objs) ->
        @objects = objs or []
        super()
    
      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
  • ¶

    Class

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

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

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

      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
  • ¶

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

      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
  • ¶

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

      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
  • ¶

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

      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
  • ¶

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

      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
  • ¶

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

      hoistDirectivePrologue: ->
        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
  • ¶

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

      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
  • ¶

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

      compileNode: (o) ->
        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
  • ¶

    Assign

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

    exports.Assign = class Assign extends Base
      constructor: (@variable, @value, @context, options) ->
        super()
        @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}\""
        @icedlocal = options and options.icedlocal        
    
      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'
  • ¶

    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) ->
  • ¶

    Start here for now, we're going to need a lot more of these.

        @value.icedStatementAssertion()
    
        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 or @icedlocal
              o.scope.add name, 'var', @icedlocal
            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
  • ¶

    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
  • ¶

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

          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
  • ¶

    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
  • ¶

    A regular array pattern-match.

          idx = i
          if isObject
            if obj instanceof Assign
  • ¶

    A regular object pattern-match.

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

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

              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
  • ¶

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

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

    Disallow conditional assignment of undefined variables.

        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
  • ¶

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

      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
  • ¶

    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.

    exports.Code = class Code extends Base
      constructor: (params, body, tag) ->
        super()
        @params  = params or []
        @body    = body or new Block
        @icedgen = tag is 'icedgen'
        @bound   = tag is 'boundfunc' or @icedgen
        @context = '_this' if @bound or @icedgen
        @icedPassedDeferral = null
    
      children: ['params', 'body']
    
      isStatement: -> !!@ctor
    
      jumps: NO
  • ¶

    Compilation creates a new scope unless explicitly asked to share with the outer scope. Handles splat parameters in the parameter list by peeking at the JavaScript arguments object. If the function is bound with the => arrow, generates a wrapper that saves the current value of this through a closure.

      compileNode: (o) ->
        o.scope         = new Scope o.scope, @body, this
        o.scope.shared  = del(o, 'sharedScope') or @icedgen
        o.scope.icedgen = @icedgen
        o.indent        += TAB
        delete o.bare
        delete o.isExistentialEquals
        params = []
        exprs  = []
        @eachParamName (name) -> # this step must be performed before the others
          unless o.scope.check name then o.scope.parameter name
        for param in @params when param.splat
          for {name: p} in @params
            if p.this then p = p.properties[0].name
            if p.value then o.scope.add p.value, 'var', yes
          splats = new Assign new Value(new Arr(p.asReference o for p in @params)),
                              new Value new Literal 'arguments'
          break
        for param in @params
          if param.isComplex()
            val = ref = param.asReference o
            val = new Op '?', ref, param.value if param.value
            exprs.push new Assign new Value(param.name), val, '=', param: yes
          else
            ref = param
            if param.value
              lit = new Literal ref.name.value + ' == null'
              val = new Assign new Value(param.name), param.value, '='
              exprs.push new If lit, val
          params.push ref unless splats
        wasEmpty = @body.isEmpty()
        exprs.unshift splats if splats
        @body.expressions.unshift exprs... if exprs.length
        for p, i in params
          params[i] = p.compileToFragments o
          o.scope.parameter fragmentsToText params[i]
        uniqs = []
        @eachParamName (name, node) ->
          node.error "multiple parameters named '#{name}'" if name in uniqs
          uniqs.push name
    
        wasEmpty = false if @icedHasAutocbFlag
    
        @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 ') {'
  • ¶

    Augment @body with iced-specific features

        @icedPatchBody o
    
        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
  • ¶

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

      traverseChildren: (crossScope, func) ->
        super(crossScope, func) if crossScope
     
      icedPatchBody : (o) ->
  • ¶

    Some iced functions need to squirrel away the original arguments.

        if @icedFoundArguments and @icedNodeFlag
          o.scope.assign '_arguments', 'arguments'
    
        if @icedNodeFlag and not @icedgen
  • ¶

    Find the tamecb if possible, and do this before we update the scope, below...

          @icedPassedDeferral = o.scope.freeVariable iced.const.passed_deferral
          lhs = new Value new Literal @icedPassedDeferral
          f = new Value new Literal iced.const.ns
          f.add new Access new Value new Literal iced.const.findDeferral
          rhs = new Call f, [ new Value new Literal 'arguments' ]
          @body.unshift(new Assign lhs, rhs)
  • ¶

    There are two important cases to consider in terms of autocb; In the case of an explicit call to return, we handle it in 'new Return' constructor. The subtler case is when control falls off the end of a function. But that's just the top-level continuation within the function. So we assign it to the autocb here. There's a slight scoping hack, to supply { icedlocal : yes }, which forces iced_k to be locally scoped. To have it global is a real disaster of subtle bugs. But wait, there's yet more!! Recall that icedgen functions share scope with their parent function. That means, they'll insert an 'iced_k' in the parent scope as a type == 'param' !! Meaning, it won't be output at the high-level function that contains them (since only 'var's) are output. Thus, we had to make a hack to scope.coffee to support this particular case.

        if @icedNodeFlag and not @icedgen
          r = if @icedHasAutocbFlag then iced.const.autocb else iced.const.k_noop
          rhs = new Value new Literal r
          lhs = new Value new Literal iced.const.k
          @body.unshift(new Assign lhs, rhs, null, { icedlocal : yes } )
  • ¶

    we are icing as a feature of all of our children. However, if we are iced, it's not the case that our parent is iced!

      icedWalkAst : (parent, o) ->
        @icedParentAwait = parent
        fa_prev = o.foundAutocb
        cf_prev = o.currFunc
        fg_prev = o.foundArguments
        faf_prev = o.foundAwaitFunc
        o.foundAutocb = false
        o.foundArguments = false
        o.foundAwaitFunc = false
        o.currFunc = @
        for param in @params
          if param.name instanceof Literal and param.name.value is iced.const.autocb
            o.foundAutocb = true
            break
        @icedHasAutocbFlag = o.foundAutocb
        super parent, o
        @icedFoundArguments = o.foundArguments
        o.foundAwaitFunc = faf_prev
        o.foundArguments = fg_prev
        o.foundAutocb = fa_prev
        o.currFunc = cf_prev
        false
    
      icedWalkAstLoops : (flood) ->
        @icedLoopFlag = true if super false
        false
    
      icedWalkCpsPivots: ->
        super()
        @icedCpsPivotFlag = false
    
      icedTraceName : ->
        parts = []
        parts.push @klass if @klass
        parts.push @name if @name
        parts.join '.'
  • ¶

    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) ->
        super()
        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
        @reference = node
    
      isComplex: ->
        @name.isComplex()
  • ¶

    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.

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

    Splat

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

    exports.Splat = class Splat extends Base
    
      children: ['name']
    
      isAssignable: YES
    
      constructor: (name) ->
        super()
        @name = if name.compile then name else new Literal name
    
      assigns: (name) ->
        @name.assigns name
    
      compileToFragments: (o) ->
        @name.compileToFragments o
    
      unwrap: -> @name
  • ¶

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

      @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(")")
    
      icedToSlot: (i) ->
        new Slot(i, new Value(@name), null, true)
  • ¶

    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
      isLoop : 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
  • ¶

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

      compileNode: (o) ->
        @condition.icedStatementAssertion()
        return @icedCompileIced o if @icedNodeFlag
        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
          if @icedHasAutocbFlag
            answer.push @makeCode "\n#{@tab}#{iced.const.autocb}(#{rvar});"
            answer.push @makeCode "\n#{@tab}return;"
          else
            answer.push @makeCode "\n#{@tab}return #{rvar};"
        answer
    
      icedWrap : (d) ->
        condition = d.condition
        body = d.body
        rvar = d.rvar
        outStatements = []
    
        if rvar
          rvar_value = new Value new Literal rvar
  • ¶

    Set up all of the IDs

        top_id = new Value new Literal iced.const.t_while
        k_id = new Value new Literal iced.const.k
        k_param = new Param new Literal iced.const.k
  • ¶

    Break will just call the parent continuation, but in some cases, there will be a return value, so then we have to pass that back out. Hence the split below:

        break_id = new Value new Literal iced.const.b_while
        if rvar
          break_expr = new Call k_id, [ rvar_value ]
          break_block = new Block [ break_expr ]
          break_body = new Code [], break_block, 'icedgen'
          break_assign = new Assign break_id, break_body, null, { icedlocal : yes }
        else
          break_assign = new Assign break_id, k_id, null, { icedlocal : yes }
  • ¶

    The continue assignment is the increment at the end of the loop (if it's there), and also the recursive call back to the top.

        continue_id = new Value new Literal iced.const.c_while
        continue_block_inner = new Block [ new Call top_id, [ k_id ] ]
        continue_block_inner.unshift d.step if d.step
        continue_fn = new Code [], continue_block_inner
        tramp = new Value new Literal iced.const.ns
        tramp.add(new Access new Value new Literal iced.const.trampoline)
        continue_block = new Block [ new Call tramp, [ continue_fn ] ]
        continue_body = new Code [], continue_block, 'icedgen'
        continue_assign = new Assign continue_id, continue_body, null, { icedlocal : yes }
  • ¶

    Next is like continue, but it also squirrels away the return value, if required!

        next_id = new Value new Literal iced.const.n_while
        if rvar
          next_arg = new Param new Literal iced.const.n_arg
          f = rvar_value.copy()
          f.add new Access new Value new Literal 'push'
          call1 = new Call f, [ next_arg ]
          call2 = new Call continue_id, []
          next_block = new Block [ call1, call2 ]
          next_body = new Code [ next_arg ], next_block, 'icedgen'
          next_assign = new Assign next_id, next_body, null, { icedlocal : yes }
        else
          next_assign = new Assign next_id, continue_id
  • ¶

    The whole body is wrapped in an if, with the positive condition being the loop, and the negative condition being the break out of the loop

        cond = new If condition.invert(), new Block [ new Call break_id, [] ]
        if d.guard
          continue_block = new Block [ new Call continue_id, [] ]
          guard_if = new If d.guard, body
          guard_if.addElse continue_block
          cond.addElse new Block [ d.pre_body, guard_if ]
        else
          cond.addElse new Block [ d.pre_body, body ]
  • ¶

    The top of the loop construct.

        top_body = new Block [ break_assign, continue_assign, next_assign, cond ]
        top_func = new Code [ k_param ], top_body, 'icedgen'
        top_assign = new Assign top_id, top_func, null, { icedlocal : yes }
        top_call = new Call top_id, [ k_id ]
        top_statements = []
        top_statements = top_statements.concat d.init if d.init
        if rvar
          rvar_init = new Assign rvar_value, new Arr
          top_statements.push rvar_init
        top_statements = top_statements.concat [ top_assign, top_call ]
        top_block = new Block top_statements
    
      icedCallContinuation : ->
        @body.icedThreadReturn new IcedTailCall iced.const.n_while
    
      icedCompileIced: (o) ->
        opts = { @condition, @body, @guard }
        if @returns
          opts.rvar = o.scope.freeVariable 'results'
        b = @icedWrap opts
        return b.compileNode o
  • ¶

    Op

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

    exports.Op = class Op extends Base
      constructor: (op, first, second, flip ) ->
        super()
        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
  • ¶

    The map of conversions from CoffeeScript to JavaScript symbols.

      CONVERSIONS =
        '==': '==='
        '!=': '!=='
        'of': 'in'
  • ¶

    The map of invertible operators.

      INVERSIONS =
        '!==': '==='
        '===': '!=='
    
      children: ['first', 'second']
    
      isSimpleNumber: NO
    
      isUnary: ->
        not @second
    
      isComplex: ->
        not (@isUnary() and @operator in ['+', '-']) or @first.isComplex()
  • ¶

    Am I capable of Python-style comparison chaining?

      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()
  • ¶

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

        @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
  • ¶

    Mimic Python's chained comparisons when multiple comparison operators are used sequentially. For example:

    bin/coffee -e 'console.log 50 < 65 > 10'
    true
      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
  • ¶

    Keep reference to the left expression, unless this an existential assignment

      compileExistence: (o) ->
        if !o.isExistentialEquals and @first.isComplex()
          ref = new Literal o.scope.freeVariable 'ref'
          fst = new Parens new Assign ref, @first
        else
          fst = @first
          ref = fst
        new If(new Existence(fst), ref, type: 'if').addElse(@second).compileToFragments o
  • ¶

    Compile a unary Op.

      compileUnary: (o) ->
        parts = []
        op = @operator
        parts.push [@makeCode op]
        if op is '!' and @first instanceof Existence
          @first.negated = not @first.negated
          return @first.compileToFragments o
        if o.level >= LEVEL_ACCESS
          return (new Parens this).compileToFragments o
        plusMinus = op in ['+', '-']
        parts.push [@makeCode(' ')] if op in ['new', 'typeof', 'delete'] or
                          plusMinus and @first instanceof Op and @first.operator is op
        if (plusMinus and @first instanceof Op) or (op is 'new' and @first.isStatement o)
          @first = new Parens @first
        parts.push @first.compileToFragments o, LEVEL_OP
        parts.reverse() if @flip
        @joinFragmentArrays parts, ''
    
      toString: (idt) ->
        super idt, @constructor.name + ' ' + @operator
    
      icedWrapContinuation : -> @icedCallContinuationFlag
  • ¶

    In

    exports.In = class In extends Base
      constructor: (@object, @array) ->
        super()
    
      children: ['object', 'array']
    
      invert: NEGATE
    
      compileNode: (o) ->
        if @array instanceof Value and @array.isArray()
          for obj in @array.base.objects when obj instanceof Splat
            hasSplat = yes
            break
  • ¶

    compileOrTest only if we have an array literal with no splats

          return @compileOrTest o unless hasSplat
        @compileLoopTest o
    
      compileOrTest: (o) ->
        return [@makeCode("#{!!@negated}")] if @array.base.objects.length is 0
        [sub, ref] = @object.cache o, LEVEL_OP
        [cmp, cnj] = if @negated then [' !== ', ' && '] else [' === ', ' || ']
        tests = []
        for item, i in @array.base.objects
          if i then tests.push @makeCode cnj
          tests = tests.concat (if i then ref else sub), @makeCode(cmp), item.compileToFragments(o, LEVEL_ACCESS)
        if o.level < LEVEL_OP then tests else @wrapInBraces tests
    
      compileLoopTest: (o) ->
        [sub, ref] = @object.cache o, LEVEL_LIST
        fragments = [].concat @makeCode(utility('indexOf') + ".call("), @array.compileToFragments(o, LEVEL_LIST),
          @makeCode(", "), ref, @makeCode(") " + if @negated then '< 0' else '>= 0')
        return fragments if fragmentsToText(sub) is fragmentsToText(ref)
        fragments = sub.concat @makeCode(', '), fragments
        if o.level < LEVEL_LIST then fragments else @wrapInBraces fragments
    
      toString: (idt) ->
        super idt, @constructor.name + if @negated then '!' else ''
  • ¶

    Slot

    A Slot is an argument passed to defer(..). It's a bit different from a normal parameters, since it's trying to implement pass-by-reference. It's used only in concert with the Defer class. Splats and Values can be converted to slots with the icedToSlot method.

    exports.Slot = class Slot extends Base
      constructor : (index, value, suffix, splat) ->
        super()
        @index = index
        @value = value
        @suffix = suffix
        @splat = splat
        @access = null
    
      addAccess : (a) ->
        @access = a
        this
    
      children : [ 'value', 'suffix' ]
  • ¶

    Defer

    exports.Defer = class Defer extends Base
      constructor : (args, @lineno) ->
        super()
        @slots = flatten (a.icedToSlot i for a,i in args)
        @params = []
        @vars = []
        @custom = false
    
      children : ['slots' ]
  • ¶

    Most deferrals are not "custom", meaning they assume __iced_deferrals as a this object. Rendezvous and others are custom, since there is an object that's acting as this

      setCustom : () ->
        @custom = true
        @
  • ¶

    Count hidden parameters up from 1. Make a note of which parameter we passed out. Return a copy of that parameter, in case we mutate it later before we output it.

      newParam : ->
        l = "#{iced.const.slot}_#{@params.length + 1}"
        @params.push new Param new Literal l
        new Value new Literal l
  • ¶

    makeAssignFn - Implement C++-style pass-by-reference in Coffee

    the 'assign_fn' returned by here will set all parameters to defer() to have the appropriate values after the defer is fulfilled. The four cases to consider are listed in the following call:

    defer(x, a.b, c.d[i], rest...)

    Case 1 -- defer(x) -- Regular assignment to a local variable Case 2 -- defer(a.b) -- Assignment to an object; must capture object when defer() is called Case 3 -- defer(c.d[i]) -- Assignment to an array slot; must capture array and slot index with defer() is called Case 4 -- defer(rest...) -- rest is an array, assign it to all leftover arguments.

    There is a special subcase of Case 1, which we call case 1(b):

    defer _

    In this case, the slot used is the return value for the surrounding await call, for cases such as:

    x = await foo defer _

      makeAssignFn : (o) ->
        return null if @slots.length is 0
        assignments = []
        args = []
        i = 0
        for s in @slots
          i = s.index
          a = new Value new Literal "arguments"
          i_lit = new Value new Literal i
          if s.splat # case 4
            func = new Value new Literal(utility 'slice')
            func.add new Access new Value new Literal 'call'
            call = new Call func, [ a, i_lit ]
            slot = s.value
            @vars.push slot
            assign = new Assign slot, call
          else
            a.add new Index i_lit
            if s.access
              a.add s.access
            if not s.suffix # case 1
              lit = s.value.compile o, LEVEL_TOP
              if lit is "_"
                slot = new Value new Literal iced.const.deferrals
                slot.add new Access new Value new Literal iced.const.retslot
              else
                slot = s.value
                @vars.push slot
            else
              args.push s.value
              slot = @newParam()
              if s.suffix instanceof Index # case 3
                prop = new Index @newParam()
                args.push s.suffix.index
              else # case 2
                prop = s.suffix
              slot.add prop
            assign = new Assign slot, a
          assignments.push assign
    
        block = new Block assignments
        inner_fn = new Code [], block, 'icedgen'
        outer_block = new Block [ new Return inner_fn ]
        outer_fn = new Code @params, outer_block, 'icedgen'
        call = new Call outer_fn, args
    
      transform : (o) ->
        meth = new Value new Literal iced.const.defer_method
  • ¶

    In the custom case, there's a foo.defer, and we're going to use the foo as the this object. Otherwise, we'll use the __iced_deferrals in the current scope as the this object

        if @custom
          fn = meth
        else
          fn = new Value new Literal iced.const.deferrals
  • ¶

    now, fn is '__iced_deferrals.defer'

          fn.add new Access meth
  • ¶

    There is one argument to Deferrals.defer(), which is a dictionary. The dictionary currently only has one slot: assign_fn, which indicates a function. More slots will be needed if we ever want to keep track of iced-aware stack traces.

        assignments = []
        if (assign_fn = @makeAssignFn o)
          assignments.push new Assign(new Value(new Literal(iced.const.assign_fn)),
                                      assign_fn, "object")
        ln_lhs = new Value new Literal iced.const.lineno
        ln_rhs = new Value new Literal @lineno
        ln_assign = new Assign ln_lhs, ln_rhs, "object"
        assignments.push ln_assign
        if @custom
          context_lhs = new Value new Literal iced.const.context
          context_rhs = new Value new Literal iced.const.deferrals
          context_assign = new Assign context_lhs, context_rhs, "object"
          assignments.push context_assign
        o = new Obj assignments
  • ¶

    Return the final call

        new Call fn, [ new Value o ]
    
      compileNode : (o) ->
        call = @transform o
        for v in @vars
          name = v.compile o, LEVEL_LIST
          scope = o.scope
          scope.add name, 'var'
        call.compileNode o
    
      icedWalkAst : (p, o) ->
        @icedHasAutocbFlag = o.foundAutocb
        o.foundDefer = true
        @parentFunc = o.currFunc
        super p, o
  • ¶

    Await

    exports.Await = class Await extends Base
      constructor : (@body) ->
        super()
    
      transform : (o) ->
        body = @body
        name = iced.const.deferrals
        o.scope.add name, 'var'
        lhs = new Value new Literal name
        cls = new Value new Literal iced.const.ns
        cls.add(new Access(new Value new Literal iced.const.Deferrals))
    
        assignments = []
        if n = @parentFunc?.icedPassedDeferral
          cb_lhs = new Value new Literal iced.const.parent
          cb_rhs = new Value new Literal n
          cb_assignment = new Assign cb_lhs, cb_rhs, "object"
          assignments.push cb_assignment
    
        if o.filename?
          fn_lhs = new Value new Literal iced.const.filename
  • ¶

    Replace '\' with '\' to make the emitted code safe for Windows paths. See Issue #84. Thanks to @Deathspike for this patch

          fn_rhs = new Value new Literal '"' + o.filename.replace('\\', '\\\\') + '"'
          fn_assignment = new Assign fn_lhs, fn_rhs, "object"
          assignments.push fn_assignment
    
        if n = @parentFunc?.icedTraceName()
          func_lhs = new Value new Literal iced.const.funcname
          func_rhs = new Value new Literal '"' + n + '"'
          func_assignment = new Assign func_lhs, func_rhs, "object"
          assignments.push func_assignment
    
        trace = new Obj assignments, true
        call = new Call cls, [ (new Value new Literal iced.const.k), trace ]
        rhs = new Op "new", call
        assign = new Assign lhs, rhs
        body.unshift assign
        meth = lhs.copy().add new Access new Value new Literal iced.const.fulfill
        call = new Call meth, []
        body.push (call)
        @body = body
    
      children: ['body']
  • ¶

    ??? Revisit!

      isStatement: -> YES
    
      makeReturn : THIS
    
      compileNode: (o) ->
        @transform(o)
        @body.compileNode o
  • ¶

    We still need to walk our children to see if there are any embedded function which might also be iced. But we're always going to report to our parent that we are iced, since we are!

      icedWalkAst : (p, o) ->
        @icedHasAutocbFlag = o.foundAutocb
        @parentFunc = o.currFunc
        p = p || this
        @icedParentAwait = p
        super p, o
        @icedNodeFlag = o.foundAwaitFunc = o.foundAwait = true
  • ¶

    IcedRuntime

    By default, the iced libraries are require'd via nodejs' require. You can change this behavior on the command line:

    -I inline --- inlines a simplified runtime to the output file -I node --- force node.js inclusion -I window --- attach the inlined runtime to the window.* object -I none --- no inclusion, do it yourself...

    class IcedRuntime extends Block
      constructor: (@foundDefer, @foundAwait) ->
        super()
    
      compileNode: (o, level) ->
        @expressions = []
    
        v = if o.runtime    then o.runtime
        else if o.bare      then "none"
        else if @foundDefer then "node"
        else                     "none"
    
        if o.runtime and not @foundDefer and not o.runforce
          v = "none"
    
        window_mode = false
        window_val = null
    
        inc = null
        inc = switch (v)
          when "inline", "window"
            window_mode = true if v is "window"
            if window_mode
              window_val = new Value new Literal v
            InlineRuntime.generate(if window_val then window_val.copy() else null)
          when "node", "browserify"
            if v is "browserify"
              modname = "iced-coffee-script/lib/coffee-script/iced"
              accessname = iced.const.runtime
            else
              modname = "iced-coffee-script"
              accessname = iced.const.ns
            file = new Literal "'#{modname}'"
            access = new Access new Literal accessname
            req = new Value new Literal "require"
            call = new Call req, [ file ]
            callv = new Value call
            callv.add access
            ns = new Value new Literal iced.const.ns
            new Assign ns, callv
          when "none" then null
          else throw SyntaxError "unexpected flag IcedRuntime #{v}"
    
        @push inc if inc
    
        if @foundAwait
  • ¶

    Emit iced_k = iced_k_noop = function(){}

          rhs = new Code [], new Block []
    
          lhs_vec = []
          for k in [ iced.const.k_noop, iced.const.k ]
            val = new Value new Literal k
  • ¶

    Add window. if necessary

            if window_val
              klass = window_val.copy()
              klass.add new Access val
              val = klass
              
            lhs_vec.push val
              
          assign = rhs
          for v in lhs_vec
            assign = new Assign v, assign
          @push assign
    
        if @isEmpty() then []
        else               super o
    
      icedWalkAst : (p,o) ->
        @icedHasAutocbFlag = o.foundAutocb
        super p, o
  • ¶

    Try

    A classic try/catch/finally block.

    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
  • ¶

    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
  • ¶

    Throw

    Simple node to throw an exception.

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

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

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

    Existence

    Checks a variable for existence -- not null and not undefined. This is similar to .nil? in Ruby, and avoids having to consult a JavaScript truth table.

    exports.Existence = class Existence extends Base
      constructor: (@expression) ->
        super()
    
      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
  • ¶

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

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

    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.

    exports.Parens = class Parens extends Base
      constructor: (@body) ->
        super()
    
      children: ['body']
    
      unwrap    : -> @body
      isComplex : -> @body.isComplex()
    
      compileNode: (o) ->
        expr = @body.unwrap()
        if expr instanceof Value and expr.isAtomic()
          expr.front = @front
          return expr.compileToFragments o
        fragments = expr.compileToFragments o, LEVEL_PAREN
        bare = o.level < LEVEL_OP and (expr instanceof Op or expr instanceof Call or
          (expr instanceof For and expr.returns))
        if bare then fragments else @wrapInBraces fragments
  • ¶

    For

    CoffeeScript's replacement for the for loop is our array and object comprehensions, that compile into for loops here. They also act as an expression, able to return the result of each filtered iteration.

    Unlike Python array comprehensions, they can be multi-line, and you can pass the current index of the loop as a second parameter. Unlike Ruby blocks, you can map and filter in a single pass.

    exports.For = class For extends While
      constructor: (body, source) ->
        super()
        {@source, @guard, @step, @name, @index} = source
        @body    = Block.wrap [body]
        @own     = !!source.own
        @object  = !!source.object
        [@name, @index] = [@index, @name] if @object
        @index.error 'index cannot be a pattern matching expression' if @index instanceof Value
        @range   = @source instanceof Value and @source.base instanceof Range and not @source.properties.length
        @pattern = @name instanceof Value
        @index.error 'indexes do not apply to range loops' if @range and @index
        @name.error 'cannot pattern match over range loops' if @range and @pattern
        @index.error 'cannot use own with for-in' if @own and not @object
        @returns = false
    
      children: ['body', 'source', 'guard', 'step']
  • ¶

    Welcome to the hairiest method in all of CoffeeScript. Handles the inner loop, filtering, stepping, and result saving for array, object, and range comprehensions. Some of the generated code can be shared in common, and some cannot.

      compileNode: (o) ->
        body      = Block.wrap [@body]
        lastJumps = last(body.expressions)?.jumps()
        @returns  = no if lastJumps and lastJumps instanceof Return
        source    = if @range then @source.base else @source
        scope     = o.scope
        name      = @name  and (@name.compile o, LEVEL_LIST)
        index     = @index and (@index.compile o, LEVEL_LIST)
        scope.find(name)  if name and not @pattern
        scope.find(index) if index
        rvar      = scope.freeVariable 'results' if @returns
        ivar      = (@object and index) or scope.freeVariable 'i'
        kvar      = (@range and name) or index or ivar
        kvarAssign = if kvar isnt ivar then "#{kvar} = " else ""
        if @step and not @range
          [step, stepVar] = @cacheToCodeFragments @step.cache o, LEVEL_LIST
          stepNum = stepVar.match SIMPLENUM
        name      = ivar if @pattern
        varPart   = ''
        guardPart = ''
        defPart   = ''
        idt1      = @tab + TAB
    
        source.icedStatementAssertion()
    
        return @icedCompileIced(o, { stepVar, body, rvar, kvar, @guard }) if @icedNodeFlag
    
        if @range
          forPartFragments = source.compileToFragments merge(o, {index: ivar, name, @step})
        else
          svar    = @source.compile o, LEVEL_LIST
          if (name or @own) and not IDENTIFIER.test svar
            defPart    += "#{@tab}#{ref = scope.freeVariable 'ref'} = #{svar};\n"
            svar       = ref
          if name and not @pattern
            namePart   = "#{name} = #{svar}[#{kvar}]"
          if not @object
            defPart += "#{@tab}#{step};\n" if step isnt stepVar
            lvar = scope.freeVariable 'len' unless @step and stepNum and down = (+stepNum < 0)
            declare = "#{kvarAssign}#{ivar} = 0, #{lvar} = #{svar}.length"
            declareDown = "#{kvarAssign}#{ivar} = #{svar}.length - 1"
            compare = "#{ivar} < #{lvar}"
            compareDown = "#{ivar} >= 0"
            if @step
              if stepNum
                if down
                  compare = compareDown
                  declare = declareDown
              else
                compare = "#{stepVar} > 0 ? #{compare} : #{compareDown}"
                declare = "(#{stepVar} > 0 ? (#{declare}) : #{declareDown})"
              increment = "#{ivar} += #{stepVar}"
            else
              increment = "#{if kvar isnt ivar then "++#{ivar}" else "#{ivar}++"}"
            forPartFragments  = [@makeCode("#{declare}; #{compare}; #{kvarAssign}#{increment}")]
        if @returns
          resultPart   = "#{@tab}#{rvar} = [];\n"
          returnResult = if @icedHasAutocbFlag then "\n#{@tab}#{iced.const.autocb}(#{rvar}); return;"
          else "\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
    
      icedCompileIced: (o, d) ->
        body = d.body
        condition = null
        init = []
        step = null
        scope = o.scope
        pre_body = new Block []
  • ¶

    Handle 'for k,v of obj'

        if @object
  • ¶

    _ref = source

          ref = scope.freeVariable 'ref'
          ref_val = new Value new Literal ref
          a1 = new Assign ref_val, @source
  • ¶

    keys = for k of _ref k

          keys = scope.freeVariable 'keys'
          keys_val = new Value new Literal keys
          key = scope.freeVariable 'k'
          key_lit = new Literal key
          key_val = new Value key_lit
          empty_arr = new Value new Arr
          loop_body = new Block [ key_val ]
          loop_source = { object : yes, name : key_lit, source : ref_val }
          loop_keys = new For loop_body, loop_source
          a2 = new Assign keys_val, loop_keys
  • ¶

    _i = 0

          iname = scope.freeVariable 'i'
          ival = new Value new Literal iname
          a3 = new Assign ival, new Value new Literal 0
    
          init = [ a1, a2, a3 ]
  • ¶

    _i < keys.length

          keys_len = keys_val.copy()
          keys_len.add new Access new Value new Literal "length"
          condition = new Op '<', ival, keys_len
  • ¶

    _i++

          step = new Op '++', ival
  • ¶

    value = _ref[name]

          if @name
            source_access = ref_val.copy()
            source_access.add new Index @index
            a5 = new Assign @name, source_access
            pre_body.unshift a5
  • ¶

    key = keys[_i]

          keys_access = keys_val.copy()
          keys_access.add new Index ival
          a4 = new Assign @index, keys_access
          pre_body.unshift a4
  • ¶

    Handle the case of 'for i in [0..10]'

        else if @range and @name
          rop = if @source.base.exclusive then '<' else '<='
          condition = new Op rop, @name, @source.base.to
          init = [ new Assign @name, @source.base.from ]
          if @step?
            step = new Op "+=", @name, @step
          else
            step = new Op '++', @name
  • ¶

    Handle the case of 'for i,blah in arr'

        else if ! @range and @name
          kval = new Value new Literal d.kvar
          len = scope.freeVariable 'len'
          ref = scope.freeVariable 'ref'
          ref_val = new Value new Literal ref
          len_val = new Value new Literal len
          a1 = new Assign ref_val, @source
          len_rhs = ref_val.copy().add new Access new Value new Literal "length"
          a2 = new Assign len_val, len_rhs
          a3 = new Assign kval, new Value new Literal 0
          init = [ a1, a2, a3 ]
          condition = new Op '<', kval, len_val
          step = new Op '++', kval
          ref_val_copy = ref_val.copy()
          ref_val_copy.add new Index kval
          a4 = new Assign @name, ref_val_copy
          pre_body.unshift a4
    
        rvar = d.rvar
        guard = d.guard
        b = @icedWrap { condition, body, init, step, rvar, guard, pre_body }
        b.compileNode o
  • ¶

    Switch

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

    exports.Switch = class Switch extends Base
      constructor: (@subject, @cases, @otherwise) ->
        super()
    
      children: ['subject', 'cases', 'otherwise']
    
      isStatement: YES
    
      jumps: (o = {block: yes}) ->
        for [conds, block] in @cases
          return block if block.jumps o
        @otherwise?.jumps o
    
      makeReturn: (res) ->
        pair[1].makeReturn res for pair in @cases
        @otherwise or= new Block [new Literal 'void 0'] if res
        @otherwise?.makeReturn res
        this
    
      compileNode: (o) ->
        @subject.icedStatementAssertion() if @subject
        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
    
      icedCallContinuation : ->
        for [condition,block] in @cases
          block.icedThreadReturn()
        if @otherwise?
          @otherwise.icedThreadReturn()
        else
  • ¶

    See github issue #55. If no else: was specified, we still need to call back the current continuation

          @otherwise = new Block [ new IcedTailCall ]
  • ¶

    If

    If/else statements. Acts as an expression by pushing down requested returns to the last line of each clause.

    Single-expression Ifs are compiled into conditional operators if possible, because ternaries are already proper expressions, and don't need conversion.

    exports.If = class If extends Base
      constructor: (condition, @body, options = {}) ->
        super()
        @condition = if options.type is 'unless' then condition.invert() else condition
        @elseBody  = null
        @isChain   = false
        {@soak}    = options
    
      children: ['condition', 'body', 'elseBody']
    
      bodyNode:     -> @body?.unwrap()
      elseBodyNode: -> @elseBody?.unwrap()
  • ¶

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

      addElse: (elseBody) ->
        if @isChain
          @elseBodyNode().addElse elseBody
        else
          @isChain  = elseBody instanceof If
          @elseBody = @ensureBlock elseBody
          @elseBody.updateLocationDataIfMissing elseBody.locationData
        this
  • ¶

    The If only compiles into a statement if either of its bodies needs to be a statement. Otherwise a conditional operator is safe.

      isStatement: (o) ->
        o?.level is LEVEL_TOP or
          @bodyNode().isStatement(o) or @elseBodyNode()?.isStatement(o)
    
      jumps: (o) -> @body.jumps(o) or @elseBody?.jumps(o)
    
      compileNode: (o) ->
        @condition.icedStatementAssertion()
        if @isStatement o or @icedIsCpsPivot() then @compileStatement o else @compileExpression o
    
      makeReturn: (res) ->
        @elseBody  or= new Block [new Literal 'void 0'] if res
        @body     and= new Block [@body.makeReturn res]
        @elseBody and= new Block [@elseBody.makeReturn res]
        this
    
      ensureBlock: (node) ->
        if node instanceof Block then node else new Block [node]
  • ¶

    Compile the If as a regular if-else statement. Flattened chains force inner else bodies into statement form.

      compileStatement: (o) ->
        child    = del o, 'chainChild'
        exeq     = del o, 'isExistentialEquals'
    
        if exeq
          return new If(@condition.invert(), @elseBodyNode(), type: 'if').compileToFragments o
    
        indent   = o.indent + TAB
        cond     = @condition.compileToFragments o, LEVEL_PAREN
        body     = @ensureBlock(@body).compileToFragments merge o, {indent}
        ifPart   = [].concat @makeCode("if ("), cond, @makeCode(") {\n"), body, @makeCode("\n#{@tab}}")
        ifPart.unshift @makeCode @tab unless child
        return ifPart unless @elseBody
        answer = ifPart.concat @makeCode(' else ')
        if @isChain
          o.chainChild = yes
          answer = answer.concat @elseBody.unwrap().compileToFragments o, LEVEL_TOP
        else
          answer = answer.concat @makeCode("{\n"), @elseBody.compileToFragments(merge(o, {indent}), LEVEL_TOP), @makeCode("\n#{@tab}}")
        answer
  • ¶

    Compile the If as a conditional operator.

      compileExpression: (o) ->
        cond = @condition.compileToFragments o, LEVEL_COND
        body = @bodyNode().compileToFragments o, LEVEL_LIST
        alt  = if @elseBodyNode() then @elseBodyNode().compileToFragments(o, LEVEL_LIST) else [@makeCode('void 0')]
        fragments = cond.concat @makeCode(" ? "), body, @makeCode(" : "), alt
        if o.level >= LEVEL_COND then @wrapInBraces fragments else fragments
    
      unfoldSoak: ->
        @soak and this
  • ¶

    propogate the closing continuation call down both branches of the if. note this prevents if ...else if... inline chaining, and makes it fully nested if { .. } else { if { } ..} ..'s

      icedCallContinuation : ->
        if @elseBody
          @elseBody.icedThreadReturn()
          @isChain = false
        else
          @addElse new IcedTailCall
        @body.icedThreadReturn()
  • ¶

    Faux-Nodes

  • ¶

    Faux-nodes are never created by the grammar, but are used during code generation to generate other combinations of nodes.

    Closure

    A faux-node used to wrap an expressions body in a closure.

    Closure =
  • ¶

    Wrap the expressions body, unless it contains a pure statement, in which case, no dice. If the body mentions this or arguments, then make sure that the closure wrapper preserves the original values.

      wrap: (expressions, statement, noReturn) ->
        return expressions if expressions.jumps()
        func = new Code [], Block.wrap [expressions]
        args = []
        argumentsNode = expressions.contains @isLiteralArguments
        if argumentsNode and expressions.classBody
          argumentsNode.error "Class bodies shouldn't reference arguments"
        if argumentsNode or expressions.contains @isLiteralThis
          meth = new Literal if argumentsNode then 'apply' else 'call'
          args = [new Literal 'this']
          args.push new Literal 'arguments' if argumentsNode
          func = new Value func, [new Access meth]
        func.noReturn = noReturn
        call = new Call func, args
        if statement then Block.wrap [call] else call
    
      isLiteralArguments: (node) ->
        node instanceof Literal and node.value is 'arguments' and not node.asKey
    
      isLiteralThis: (node) ->
        (node instanceof Literal and node.value is 'this' and not node.asKey) or
          (node instanceof Code and node.bound) or
          (node instanceof Call and node.isSuper)
  • ¶

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

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

    CpsCascade

    CpsCascade =
    
      wrap: (statement, rest, returnValue, o) ->
        func = new Code [ new Param new Literal iced.const.k ],
          (Block.wrap [ statement ]), 'icedgen'
        args = []
        if returnValue
          returnValue.bindName o
          args.push returnValue
    
        block = Block.wrap [ rest ]
  • ¶

    Optimization! If the block is just a tail call to another continuation that can be inlined, then we just call that call directly.

        if (e = block.icedGetSingle()) and e instanceof IcedTailCall and e.canInline()
          cont = e.extractFunc()
        else
          cont = new Code args, block, 'icedgen'
    
        call = new Call func, [ cont ]
        new Block [ call ]
  • ¶

    TailCall

    At the end of a iced if, loop, or switch statement, we tail call off to the next continuation

    class IcedTailCall extends Base
      constructor : (@func, val = null) ->
        super()
        @func = iced.const.k unless @func
        @value = val
    
      children : [ 'value' ]
    
      assignValue : (v) ->
        @value = v
    
      canInline : ->
        return not @value or @value instanceof IcedReturnValue
    
      literalFunc: -> new Literal @func
      extractFunc: -> new Value @literalFunc()
    
      compileNode : (o) ->
        f = @literalFunc()
        out = if o.level is LEVEL_TOP
          if @value
            new Block [ @value, new Call f ]
          else
            new Call f
        else
          args = if @value then [ @value ] else []
          new Call f, args
        out.compileNode o
  • ¶

    IcedReturnValue

    A variable reference to a deferred computation

    class IcedReturnValue extends Param
      @counter : 0
      constructor : () ->
        super null, null, no
    
      bindName : (o) ->
        l = "#{o.scope.freeVariable iced.const.param, no}_#{IcedReturnValue.counter++}"
        @name = new Literal l
    
      compile : (o) ->
        @bindName o if not @name
        super o
  • ¶

    Runtime class and funcs, the most basic one...

    InlineRuntime =
  • ¶

    Generate this code, inline. Is there a better way?

    iced = Deferrals : class constructor: (@continuation) -> @count = 1 @ret = null _fulfill : -> @continuation @ret if not --@count defer : (defer_params) -> @count++ (inner_params...) => defer_params?.assign_fn?.apply(null, inner_params) @_fulfill() findDeferral : (args) -> null

      generate : (ns_window) ->
        k = new Literal "continuation"
        cnt = new Literal "count"
        cn = new Value new Literal iced.const.Deferrals
        ns = new Value new Literal iced.const.ns
        if ns_window # window.iced = ...
          ns_window.add new Access ns
          ns = ns_window
  • ¶

    make the constructor:

    constructor: (@continuation) -> @count = 1 @ret = null

        k_member = new Value new Literal "this"
        k_member.add new Access k
        p1 = new Param k_member
        cnt_member = new Value new Literal "this"
        cnt_member.add new Access cnt
        ret_member = new Value new Literal "this"
        ret_member.add new Access new Value new Literal iced.const.retslot
        a1 = new Assign cnt_member, new Value new Literal 1
        a2 = new Assign ret_member, NULL()
        constructor_params = [ p1 ]
        constructor_body = new Block [ a1, a2 ]
        constructor_code = new Code constructor_params, constructor_body
        constructor_name = new Value new Literal "constructor"
        constructor_assign = new Assign constructor_name, constructor_code
  • ¶

    make the _fulfill member:

    _fulfill : -> @continuation @ret if not --@count

        if_expr = new Call k_member, [ ret_member ]
        if_body = new Block [ if_expr ]
        decr = new Op '--', cnt_member
        if_cond = new Op '!', decr
        my_if = new If if_cond, if_body
        _fulfill_body = new Block [ my_if ]
        _fulfill_code = new Code [], _fulfill_body
        _fulfill_name = new Value new Literal iced.const.fulfill
        _fulfill_assign = new Assign _fulfill_name, _fulfill_code
  • ¶

    Make the defer member: defer : (defer_params) -> @count++ (inner_params...) -> defer_params?.assign_fn?.apply(null, inner_params) @_fulfill()

        inc = new Op "++", cnt_member
        ip = new Literal "inner_params"
        dp = new Literal "defer_params"
        dp_value = new Value dp
        call_meth = new Value dp
        af = new Literal iced.const.assign_fn
        call_meth.add new Access af, "soak"
        my_apply = new Literal "apply"
        call_meth.add new Access my_apply, "soak"
        my_null = NULL()
        apply_call = new Call call_meth, [ my_null, new Value ip ]
        _fulfill_method = new Value new Literal "this"
        _fulfill_method.add new Access new Literal iced.const.fulfill
        _fulfill_call = new Call _fulfill_method, []
        inner_body = new Block [ apply_call, _fulfill_call ]
        inner_params = [ new Param ip, null, on ]
        inner_code = new Code inner_params, inner_body, "boundfunc"
        defer_body = new Block [ inc, inner_code ]
        defer_params = [ new Param dp ]
        defer_code = new Code defer_params, defer_body
        defer_name = new Value new Literal iced.const.defer_method
        defer_assign = new Assign defer_name, defer_code
  • ¶

    Piece the class together

        assignments = [ constructor_assign, _fulfill_assign, defer_assign ]
        obj = new Obj assignments, true
        body = new Block [ new Value obj ]
        klass = new Class null, null, body
        klass_assign = new Assign cn, klass, "object"
  • ¶

    A stub so that the function still works findDeferral : (args) -> null

        outer_block = new Block [ NULL() ]
        fn_code = new Code [ ], outer_block
        fn_name = new Value new Literal iced.const.findDeferral
        fn_assign = new Assign fn_name, fn_code, "object"
  • ¶

    A stub trampoline so that it strill works: trampoline : (fn) -> fn()

        fn = new Literal "_fn"
        tr_block = new Block [ new Call (new Value fn), [] ]
        tr_params = [ new Param fn ]
        tr_code = new Code tr_params, tr_block
        tr_name = new Value new Literal iced.const.trampoline
        tr_assign = new Assign tr_name, tr_code, "object"
  • ¶

    iced = Deferrals : findDeferral : trampoline :

        ns_obj = new Obj [ klass_assign, fn_assign, tr_assign ], true
        ns_val = new Value ns_obj
        new Assign ns, ns_val
  • ¶

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

    Constants

  • ¶
    UTILITIES =
  • ¶

    Correctly set up a prototype chain for inheritance, including a reference to the superclass for super() calls, and copies of any static properties.

      extends: -> """
        function(child, parent) { for (var key in parent) { if (#{utility 'hasProp'}.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }
      """
  • ¶

    Create a function bound to the current value of "this".

      bind: -> '''
        function(fn, me){ return function(){ return fn.apply(me, arguments); }; }
      '''
  • ¶

    Discover if an item is in an array.

      indexOf: -> """
        [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }
      """
  • ¶

    Shortcuts to speed up the lookup time for native functions.

      hasProp: -> '{}.hasOwnProperty'
      slice  : -> '[].slice'
  • ¶

    Levels indicate a node's position in the AST. Useful for knowing if parens are necessary or superfluous.

    LEVEL_TOP    = 1  # ...;
    LEVEL_PAREN  = 2  # (...)
    LEVEL_LIST   = 3  # [...]
    LEVEL_COND   = 4  # ... ? x : y
    LEVEL_OP     = 5  # !...
    LEVEL_ACCESS = 6  # ...[0]
  • ¶

    Tabs are two spaces for pretty printing.

    TAB = '  '
    
    IDENTIFIER_STR = "[$A-Za-z_\\x7f-\\uffff][$\\w\\x7f-\\uffff]*"
    IDENTIFIER = /// ^ #{IDENTIFIER_STR} $ ///
    SIMPLENUM  = /^[+-]?\d+$/
    METHOD_DEF = ///
      ^
        (?:
          (#{IDENTIFIER_STR})
          \.prototype
          (?:
            \.(#{IDENTIFIER_STR})
          | \[("(?:[^\\"\r\n]|\\.)*"|'(?:[^\\'\r\n]|\\.)*')\]
          | \[(0x[\da-fA-F]+ | \d*\.?\d+ (?:[eE][+-]?\d+)?)\]
          )
        )
      |
        (#{IDENTIFIER_STR})
      $
    ///
  • ¶

    Is a literal value a string?

    IS_STRING = /^['"]/
  • ¶

    Utility Functions

  • ¶

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

    utility = (name) ->
      ref = "__#{name}"
      Scope.root.assign ref, UTILITIES[name]()
      ref
    
    multident = (code, tab) ->
      code = code.replace /\n/g, '$&' + tab
      code.replace /\s+$/, ''