• 跳至… +
    browser.coffee cake.coffee coffee-script.coffee command.coffee grammar.coffee helpers.coffee index.coffee lexer.coffee nodes.coffee optparse.coffee register.coffee repl.coffee rewriter.coffee scope.litcoffee sourcemap.litcoffee
  • nodes.coffee

  • ¶

    nodes.coffee 包含語法樹的所有節點類別。大部分節點都是 語法 中動作的結果,但有些節點是由其他節點作為程式碼產生方法所建立。若要將語法樹轉換成 JavaScript 程式碼字串,請對根呼叫 compile()。

    Error.stackTraceLimit = Infinity
    
    {Scope} = require './scope'
    {isUnassignable, JS_FORBIDDEN} = require './lexer'
  • ¶

    匯入我們計畫使用的輔助程式。

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

    剖析器所需的函式

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

    不需要自訂的節點的常數函式。

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

    CodeFragment

  • ¶

    下面定義的各種節點都編譯成 CodeFragment 物件的集合。CodeFragments 是已產生程式碼的區塊,以及程式碼在原始檔中的位置。CodeFragments 可以透過依序串接所有 CodeFragments 的 code 片段組成可運作的程式碼。

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

    將 CodeFragments 陣列轉換成字串。

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

    Base

  • ¶

    Base 是語法樹中所有節點的抽象基底類別。每個子類別都實作 compileNode 方法,該方法會執行該節點的程式碼產生。若要將節點編譯成 JavaScript,請在節點上呼叫 compile,它會將 compileNode 包裝在一些通用的額外智慧中,以了解何時需要將產生的程式碼包裝在封閉中。會傳遞並複製一個選項雜湊,其中包含來自樹中較高層級的環境資訊(例如,周圍函式是否要求傳回值)、目前範圍的資訊,以及縮排層級。

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

    在編譯此節點之前,決定是否將此節點包裝在封閉中,或直接編譯的共用邏輯。如果此節點是陳述式,且不是純粹陳述式,而且我們不在區塊的最上層(這是不必要的),而且我們尚未被要求傳回結果(因為陳述式知道如何傳回結果),則我們需要包裝。

      compileToFragments: (o, lvl) ->
        o        = extend {}, o
        o.level  = lvl if lvl
        node     = @unfoldSoak(o) or this
        node.tab = o.indent
        if o.level is LEVEL_TOP or not node.isStatement(o)
          node.compileNode o
        else
          node.compileClosure o
  • ¶

    透過封閉包裝轉換成表達式的陳述式與其父封閉共用一個範圍物件,以保留預期的詞彙範圍。

      compileClosure: (o) ->
        if jumpNode = @jumps()
          jumpNode.error 'cannot use a pure statement in an expression'
        o.sharedScope = yes
        func = new Code [], Block.wrap [this]
        args = []
        if (argumentsNode = @contains isLiteralArguments) or @contains isLiteralThis
          args = [new ThisLiteral]
          if argumentsNode
            meth = 'apply'
            args.push new IdentifierLiteral 'arguments'
          else
            meth = 'call'
          func = new Value func, [new Access new PropertyName meth]
        parts = (new Call func, args).compileNode o
        if func.isGenerator or func.base?.isGenerator
          parts.unshift @makeCode "(yield* "
          parts.push    @makeCode ")"
        parts
  • ¶

    如果程式碼產生希望在多個地方使用複雜表達式的結果,請確保表達式只評估一次,方法是將其指定給暫時變數。傳遞一個層級進行預編譯。

    如果傳遞了 level,則會傳回 [val, ref],其中 val 是編譯後的數值,而 ref 是編譯後的參照。如果未傳遞 level,則會傳回 [val, ref],其中這兩個數值是尚未編譯的原始節點。

      cache: (o, level, isComplex) ->
        complex = if isComplex? then isComplex this else @isComplex()
        if complex
          ref = new IdentifierLiteral o.scope.freeVariable 'ref'
          sub = new Assign ref, this
          if level then [sub.compileToFragments(o, level), [@makeCode(ref.value)]] else [sub, ref]
        else
          ref = if level then @compileToFragments o, level else this
          [ref, ref]
    
      cacheToCodeFragments: (cacheValues) ->
        [fragmentsToText(cacheValues[0]), fragmentsToText(cacheValues[1])]
  • ¶

    建構一個節點,傳回目前的節點結果。請注意,對於許多陳述節點(例如 If、For)會覆寫此節點以產生更聰明的行為…

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

    此節點或其任何子節點是否包含某種類型的節點?遞迴地向下遍歷 子節點 並傳回第一個驗證 pred 的節點。否則,傳回未定義。contains 不會跨越範圍界線。

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

    從節點清單中取出最後一個非註解節點。

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

    節點的 toString 表示形式,用於檢查解析樹。這是 coffee --nodes 列印出來的內容。

      toString: (idt = '', name = @constructor.name) ->
        tree = '\n' + idt + name
        tree += '?' if @soak
        @eachChild (node) -> tree += node.toString idt + TAB
        tree
  • ¶

    將每個子節點傳遞給函式,當函式傳回 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
  • ¶

    常見節點屬性和方法的預設實作。節點會根據需要使用自訂邏輯覆寫這些屬性和方法。

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

    此節點是否用於指定某個變數?

      assigns: NO
  • ¶

    對於此節點及其所有後代,如果尚未設定位置資料,則將位置資料設定為 locationData。

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

    擲出與這個節點位置相關的語法錯誤。

      error: (message) ->
        throwSyntaxError message, @locationData
    
      makeCode: (code) ->
        new CodeFragment this, code
    
      wrapInBraces: (fragments) ->
        [].concat @makeCode('('), fragments, @makeCode(')')
  • ¶

    fragmentsList 是片段陣列的陣列。fragmentsList 中的每個陣列會串接在一起,並在每個陣列之間加入 joinStr,以產生片段的最終平面陣列。

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

    區塊

  • ¶

    區塊是形成縮排程式碼區塊主體的表達式清單,也就是函式的實作、if、switch 或 try 中的子句,等等…

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

    將一個表達式附加到這個表達式清單的結尾。

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

    移除並傳回這個表達式清單的最後一個表達式。

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

    在這個表達式清單的開頭加入一個表達式。

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

    如果這個區塊只包含一個節點,請將其解開並拉出。

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

    這是一個空的程式碼區塊嗎?

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

    區塊節點不會傳回其整個主體,而是確保傳回最後一個表達式。

      makeReturn: (res) ->
        len = @expressions.length
        while len--
          expr = @expressions[len]
          if expr not instanceof Comment
            @expressions[len] = expr.makeReturn res
            @expressions.splice(len, 1) if expr instanceof Return and not expr.expression
            break
        this
  • ¶

    區塊是唯一可以作為根節點的節點。

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

    編譯區塊主體中的所有表達式。如果我們需要傳回結果,而且它是一個表達式,請直接傳回它。如果它是一個陳述式,請要求陳述式執行此操作。

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

    這是一個巢狀區塊。我們不會在此執行任何特殊操作,例如將其封裝在新範圍中;我們只會編譯這個區塊中的陳述式以及我們自己的陳述式

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

    如果我們碰巧是頂層 Block,請將所有內容包在安全閉包中,除非有要求不要這麼做。最好一開始就不要產生它們,但現在,請清除明顯的雙括號。

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

    將根範圍中給定的區域變數標記為參數,以免最後在這個區塊中宣告它們。

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

    編譯函式內容的表達式主體,並將所有內部變數的宣告推到最上面。

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

    將給定的節點包成 Block,除非它已經是一個區塊。

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

    文字

  • ¶

    Literal 是靜態值的基礎類別,這些值可以直接傳遞到 JavaScript 中而不需要轉譯,例如:字串、數字、true、false、null…

    exports.Literal = class Literal extends Base
      constructor: (@value) ->
    
      isComplex: NO
    
      assigns: (name) ->
        name is @value
    
      compileNode: (o) ->
        [@makeCode @value]
    
      toString: ->
        " #{if @isStatement() then super else @constructor.name}: #{@value}"
    
    exports.NumberLiteral = class NumberLiteral extends Literal
    
    exports.InfinityLiteral = class InfinityLiteral extends NumberLiteral
      compileNode: ->
        [@makeCode '2e308']
    
    exports.NaNLiteral = class NaNLiteral extends NumberLiteral
      constructor: ->
        super 'NaN'
    
      compileNode: (o) ->
        code = [@makeCode '0/0']
        if o.level >= LEVEL_OP then @wrapInBraces code else code
    
    exports.StringLiteral = class StringLiteral extends Literal
    
    exports.RegexLiteral = class RegexLiteral extends Literal
    
    exports.PassthroughLiteral = class PassthroughLiteral extends Literal
    
    exports.IdentifierLiteral = class IdentifierLiteral extends Literal
      isAssignable: YES
    
    exports.PropertyName = class PropertyName extends Literal
      isAssignable: YES
    
    exports.StatementLiteral = class StatementLiteral extends Literal
      isStatement: YES
    
      makeReturn: THIS
    
      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) ->
        [@makeCode "#{@tab}#{@value};"]
    
    exports.ThisLiteral = class ThisLiteral extends Literal
      constructor: ->
        super 'this'
    
      compileNode: (o) ->
        code = if o.scope.method?.bound then o.scope.method.context else @value
        [@makeCode code]
    
    exports.UndefinedLiteral = class UndefinedLiteral extends Literal
      constructor: ->
        super 'undefined'
    
      compileNode: (o) ->
        [@makeCode if o.level >= LEVEL_ACCESS then '(void 0)' else 'void 0']
    
    exports.NullLiteral = class NullLiteral extends Literal
      constructor: ->
        super 'null'
    
    exports.BooleanLiteral = class BooleanLiteral extends Literal
  • ¶

    傳回

  • ¶

    return 是 pureStatement – 將它包在閉包中沒有意義。

    exports.Return = class Return extends Base
      constructor: (@expression) ->
    
      children: ['expression']
    
      isStatement:     YES
      makeReturn:      THIS
      jumps:           THIS
    
      compileToFragments: (o, level) ->
        expr = @expression?.makeReturn()
        if expr and expr not instanceof Return then expr.compileToFragments o, level else super o, level
    
      compileNode: (o) ->
        answer = []
  • ¶

    待辦事項:如果我們在此呼叫 expression.compile() 兩次,有時會得到不同的結果!

        answer.push @makeCode @tab + "return#{if @expression then " " else ""}"
        if @expression
          answer = answer.concat @expression.compileToFragments o, LEVEL_PAREN
        answer.push @makeCode ";"
        return answer
  • ¶

    yield return 的運作方式與 return 完全相同,不同之處在於它會將函式轉換為產生器。

    exports.YieldReturn = class YieldReturn extends Return
      compileNode: (o) ->
        unless o.scope.parent?
          @error 'yield can only occur inside functions'
        super
  • ¶

    值

  • ¶

    值、變數或文字,或以括號括住、編入索引或點入,或香草。

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

    將屬性 (或屬性 ) Access 新增至清單。

      add: (props) ->
        @properties = @properties.concat props
        this
    
      hasProperties: ->
        [email protected]
    
      bareLiteral: (type) ->
        not @properties.length and @base instanceof type
  • ¶

    一些布林檢查,供其他節點使用。

      isArray        : -> @bareLiteral(Arr)
      isRange        : -> @bareLiteral(Range)
      isComplex      : -> @hasProperties() or @base.isComplex()
      isAssignable   : -> @hasProperties() or @base.isAssignable()
      isNumber       : -> @bareLiteral(NumberLiteral)
      isString       : -> @bareLiteral(StringLiteral)
      isRegex        : -> @bareLiteral(RegexLiteral)
      isUndefined    : -> @bareLiteral(UndefinedLiteral)
      isNull         : -> @bareLiteral(NullLiteral)
      isBoolean      : -> @bareLiteral(BooleanLiteral)
      isAtomic       : ->
        for node in @properties.concat @base
          return no if node.soak or node instanceof Call
        yes
    
      isNotCallable  : -> @isNumber() or @isString() or @isRegex() or
                          @isArray() or @isRange() or @isSplice() or @isObject() or
                          @isUndefined() or @isNull() or @isBoolean()
    
      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: ->
        [..., lastProp] = @properties
        lastProp instanceof Slice
    
      looksStatic: (className) ->
        @base.value is className and @properties.length is 1 and
          @properties[0].name?.value isnt 'prototype'
  • ¶

    如果沒有附加屬性,則可以將值解開為其內部節點。

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

    參考具有基本部分 (this 值) 和名稱部分。我們將它們分別快取,以編譯複雜的表達式。a()[b()] ?= c -> (_base = a())[_name = b()] ? _base[_name] = c

      cacheReference: (o) ->
        [..., name] = @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 IdentifierLiteral 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 IdentifierLiteral 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])]
  • ¶

    我們透過編譯和合併每個屬性,將值編譯為 JavaScript。如果屬性鏈包含浸泡運算子 ?.,事情會變得更有趣。然後,我們必須小心,在建立浸泡鏈時不要意外地評估兩次。

      compileNode: (o) ->
        @base.front = @front
        props = @properties
        fragments = @base.compileToFragments o, (if props.length then LEVEL_ACCESS else null)
        if props.length and SIMPLENUM.test fragmentsToText fragments
          fragments.push @makeCode '.'
        for prop in props
          fragments.push (prop.compileToFragments o)...
        fragments
  • ¶

    將浸泡展開成 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 IdentifierLiteral o.scope.freeVariable 'ref'
              fst = new Parens new Assign ref, fst
              snd.base = ref
            return new If new Existence(fst), snd, soak: on
          no
  • ¶

    註解

  • ¶

    CoffeeScript 將區塊註解傳遞為 JavaScript 區塊註解,位置相同。

    exports.Comment = class Comment extends Base
      constructor: (@comment) ->
    
      isStatement:     YES
      makeReturn:      THIS
    
      compileNode: (o, level) ->
        comment = @comment.replace /^(\s*)#(?=\s)/gm, "$1 *"
        code = "/*#{multident comment, @tab}#{if '\n' in comment then "\n#{@tab}" else ''} */"
        code = o.indent + code if (level or o.level) is LEVEL_TOP
        [@makeCode("\n"), @makeCode(code)]
  • ¶

    呼叫

  • ¶

    函式呼叫的節點。

    exports.Call = class Call extends Base
      constructor: (@variable, @args = [], @soak) ->
        @isNew    = false
        if @variable instanceof Value and @variable.isNotCallable()
          @variable.error "literal is not a function"
    
      children: ['variable', 'args']
  • ¶

    在設定位置時,我們有時需要更新開始位置,以考量我們左側新發現的 new 運算子。這會擴充左側的範圍,但不會擴充右側。

      updateLocationDataIfMissing: (locationData) ->
        if @locationData and @needsUpdatedStartLocation
          @locationData.first_line = locationData.first_line
          @locationData.first_column = locationData.first_column
          base = @variable?.base or @variable
          if base.needsUpdatedStartLocation
            @variable.locationData.first_line = locationData.first_line
            @variable.locationData.first_column = locationData.first_column
            base.updateLocationDataIfMissing locationData
          delete @needsUpdatedStartLocation
        super
  • ¶

    將此呼叫標記為建立新執行個體。

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

    浸泡鏈式呼叫展開成 if/else 三元結構。

      unfoldSoak: (o) ->
        if @soak
          if this instanceof SuperCall
            left = new Literal @superReference o
            rite = new Value left
          else
            return ifn if ifn = unfoldSoak o, this, 'variable'
            [left, rite] = new Value(@variable).cacheReference o
          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
  • ¶

    編譯香草函式呼叫。

      compileNode: (o) ->
        @variable?.front = @front
        compiledArray = Splat.compileSplattedArray o, @args, true
        if compiledArray.length
          return @compileSplat o, compiledArray
        compiledArgs = []
        for arg, argIndex in @args
          if argIndex then compiledArgs.push @makeCode ", "
          compiledArgs.push (arg.compileToFragments o, LEVEL_LIST)...
    
        fragments = []
        if this instanceof SuperCall
          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
  • ¶

    如果你使用 splat 呼叫函式,它會轉換成 JavaScript .apply() 呼叫,允許傳遞陣列參數。如果它是建構函式,那事情就變得非常棘手。我們必須注入一個內部建構函式,才能傳遞可變參數。

    splatArgs 是要放入「套用」的 CodeFragments 陣列。

      compileSplat: (o, splatArgs) ->
        if this instanceof SuperCall
          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(")")
  • ¶

    Super

  • ¶

    負責將 super() 呼叫轉換成對同名原型函式的呼叫。

    exports.SuperCall = class SuperCall extends Call
      constructor: (args) ->
        super null, args ? [new Splat new IdentifierLiteral 'arguments']
  • ¶

    允許辨識沒有括號和參數的 super 呼叫。

        @isBare = args?
  • ¶

    取得超類別對目前方法執行的參考。

      superReference: (o) ->
        method = o.scope.namedMethod()
        if method?.klass
          {klass, name, variable} = method
          if klass.isComplex()
            bref = new IdentifierLiteral o.scope.parent.freeVariable 'base'
            base = new Value new Parens new Assign bref, klass
            variable.base = base
            variable.properties.splice 0, klass.properties.length
          if name.isComplex() or (name instanceof Index and name.index.isAssignable())
            nref = new IdentifierLiteral o.scope.parent.freeVariable 'name'
            name = new Index new Assign nref, name.index
            variable.properties.pop()
            variable.properties.push name
          accesses = [new Access new PropertyName '__super__']
          accesses.push new Access new PropertyName 'constructor' if method.static
          accesses.push if nref? then new Index nref else name
          (new Value bref ? klass, accesses).compile o
        else if method?.ctor
          "#{method.name}.__super__.constructor"
        else
          @error 'cannot call super outside of an instance method.'
  • ¶

    super 呼叫的適當 this 值。

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

    RegexWithInterpolations

  • ¶

    帶有內插的正規表示式實際上只是 Call 的變體(精確來說是 RegExp() 呼叫),內含 StringWithInterpolations。

    exports.RegexWithInterpolations = class RegexWithInterpolations extends Call
      constructor: (args = []) ->
        super (new Value new IdentifierLiteral 'RegExp'), args, false
  • ¶

    TaggedTemplateCall

    exports.TaggedTemplateCall = class TaggedTemplateCall extends Call
      constructor: (variable, arg, soak) ->
        arg = new StringWithInterpolations Block.wrap([ new Value arg ]) if arg instanceof StringLiteral
        super variable, [ arg ], soak
    
      compileNode: (o) ->
  • ¶

    告訴 StringWithInterpolations 是否要編譯為 ES2015;CoffeeScript 2 中會移除。

        o.inTaggedTemplateCall = yes
        @variable.compileToFragments(o, LEVEL_ACCESS).concat @args[0].compileToFragments(o, LEVEL_LIST)
  • ¶

    Extends

  • ¶

    節點,用祖先物件延伸物件的原型。來自 Closure Library 的 goog.inherits 之後。

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

    將一個建構函式掛接到另一個建構函式的原型鏈。

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

    Access

  • ¶

    . 存取值中的屬性,或 :: 簡寫為存取物件的原型。

    exports.Access = class Access extends Base
      constructor: (@name, tag) ->
        @soak  = tag is 'soak'
    
      children: ['name']
    
      compileToFragments: (o) ->
        name = @name.compileToFragments o
        node = @name.unwrap()
        if node instanceof PropertyName
          if node.value in JS_FORBIDDEN
            [@makeCode('["'), name..., @makeCode('"]')]
          else
            [@makeCode('.'), name...]
        else
          [@makeCode('['), name..., @makeCode(']')]
    
      isComplex: NO
  • ¶

    索引

  • ¶

    [ ... ] 索引存取陣列或物件。

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

    範圍

  • ¶

    範圍文字。範圍可用於萃取陣列的部分(切片),指定理解的範圍,或作為值,在執行時期擴充為對應的整數陣列。

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

    編譯範圍的來源變數 – 它從哪裡開始和在哪裡結束。但僅在需要快取時才編譯,以避免重複評估。

      compileVariables: (o) ->
        o = merge o, top: true
        isComplex = del o, 'isComplex'
        [@fromC, @fromVar]  =  @cacheToCodeFragments @from.cache o, LEVEL_LIST, isComplex
        [@toC, @toVar]      =  @cacheToCodeFragments @to.cache o, LEVEL_LIST, isComplex
        [@step, @stepVar]   =  @cacheToCodeFragments step.cache o, LEVEL_LIST, isComplex if step = del o, 'step'
        @fromNum = if @from.isNumber() then Number @fromVar else null
        @toNum   = if @to.isNumber()   then Number @toVar   else null
        @stepNum = if step?.isNumber() then Number @stepVar else null
  • ¶

    正常編譯時,範圍會傳回在範圍中反覆運算值所需的 for 迴圈 內容。由理解使用。

      compileNode: (o) ->
        @compileVariables o unless @fromVar
        return @compileArray(o) unless o.index
  • ¶

    設定端點。

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

    產生條件。

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

    產生步驟。

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

    最後的迴圈主體。

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

    當用作值時,將範圍擴充為等效陣列。

      compileArray: (o) ->
        known = @fromNum? and @toNum?
        if known 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', single: true
        result = o.scope.freeVariable 'results'
        pre    = "\n#{idt}#{result} = [];"
        if known
          o.index = i
          body    = fragmentsToText @compileNode o
        else
          vars    = "#{i} = #{@fromC}" + if @toC isnt @toVar then ", #{@toC}" else ''
          cond    = "#{@fromVar} <= #{@toVar}"
          body    = "var #{vars}; #{cond} ? #{i} <#{@equals} #{@toVar} : #{i} >#{@equals} #{@toVar}; #{cond} ? #{i}++ : #{i}--"
        post   = "{ #{result}.push(#{i}); }\n#{idt}return #{result};\n#{o.indent}"
        hasArgs = (node) -> node?.contains isLiteralArguments
        args   = ', arguments' if hasArgs(@from) or hasArgs(@to)
        [@makeCode "(function() {#{pre}\n#{idt}for (#{body})#{post}}).apply(this#{args ? ''})"]
  • ¶

    切片

  • ¶

    陣列切片文字。與 JavaScript 的 Array#slice 不同,第二個參數指定切片結束的索引,就像第一個參數是開始的索引一樣。

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

    我們必須小心嘗試切片到陣列的結尾,使用 9e9 是因為並非所有實作都尊重 undefined 或 1/0。9e9 應該是安全的,因為 9e9 > 2**32,最大陣列長度。

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

    待辦事項:jwalton - 將此移至「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 to.isNumber()
              "#{+compiledText + 1}"
            else
              compiled = to.compileToFragments o, LEVEL_ACCESS
              "+#{fragmentsToText compiled} + 1 || 9e9"
        [@makeCode ".slice(#{ fragmentsToText fromCompiled }#{ toStr or '' })"]
  • ¶

    Obj

  • ¶

    物件文字,沒有任何花俏的東西。

    exports.Obj = class Obj extends Base
      constructor: (props, @generated = false) ->
        @objects = @properties = props or []
    
      children: ['properties']
    
      compileNode: (o) ->
        props = @properties
        if @generated
          for node in props when node instanceof Value
            node.error 'cannot have an implicit value in an implicit object'
        break for prop, dynamicIndex in props when (prop.variable or prop).base instanceof Parens
        hasDynamic  = dynamicIndex < props.length
        idt         = o.indent += TAB
        lastNoncom  = @lastNonComment @properties
        answer = []
        if hasDynamic
          oref = o.scope.freeVariable 'obj'
          answer.push @makeCode "(\n#{idt}#{oref} = "
        answer.push @makeCode "{#{if props.length is 0 or dynamicIndex is 0 then '}' else '\n'}"
        for prop, i in props
          if i is dynamicIndex
            answer.push @makeCode "\n#{idt}}" unless i is 0
            answer.push @makeCode ',\n'
          join = if i is props.length - 1 or i is dynamicIndex - 1
            ''
          else if prop is lastNoncom or prop instanceof Comment
            '\n'
          else
            ',\n'
          indent = if prop instanceof Comment then '' else idt
          indent += TAB if hasDynamic and i < dynamicIndex
          if prop instanceof Assign
            if prop.context isnt 'object'
              prop.operatorToken.error "unexpected #{prop.operatorToken.value}"
            if 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 i < dynamicIndex
              if prop not instanceof Assign
                prop = new Assign prop, prop, 'object'
            else
              if prop instanceof Assign
                key = prop.variable
                value = prop.value
              else
                [key, value] = prop.base.cache o
                key = new PropertyName key.value if key instanceof IdentifierLiteral
              prop = new Assign (new Value (new IdentifierLiteral oref), [new Access key]), value
          if indent then answer.push @makeCode indent
          answer.push prop.compileToFragments(o, LEVEL_TOP)...
          if join then answer.push @makeCode join
        if hasDynamic
          answer.push @makeCode ",\n#{idt}#{oref}\n#{@tab})"
        else
          answer.push @makeCode "\n#{@tab}}" unless props.length is 0
        if @front and not hasDynamic then @wrapInBraces answer else answer
    
      assigns: (name) ->
        for prop in @properties when prop.assigns name then return yes
        no
  • ¶

    Arr

  • ¶

    陣列文字。

    exports.Arr = class Arr extends Base
      constructor: (objs) ->
        @objects = objs or []
    
      children: ['objects']
    
      compileNode: (o) ->
        return [@makeCode '[]'] unless @objects.length
        o.indent += TAB
        answer = Splat.compileSplattedArray o, @objects
        return answer if answer.length
    
        answer = []
        compiledObjs = (obj.compileToFragments o, LEVEL_LIST for obj in @objects)
        for fragments, index in compiledObjs
          if index
            answer.push @makeCode ", "
          answer.push fragments...
        if fragmentsToText(answer).indexOf('\n') >= 0
          answer.unshift @makeCode "[\n#{o.indent}"
          answer.push @makeCode "\n#{@tab}]"
        else
          answer.unshift @makeCode "["
          answer.push @makeCode "]"
        answer
    
      assigns: (name) ->
        for obj in @objects when obj.assigns name then return yes
        no
  • ¶

    Class

  • ¶

    CoffeeScript 類別定義。使用其名稱、一個選用的超類別,以及原型屬性指派清單來初始化一個**類別**。

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

    找出此類別的建構函式函式的適當名稱。

      determineName: ->
        return @defaultClassVariableName unless @variable
        [..., tail] = @variable.properties
        node = if tail
          tail instanceof Access and tail.name
        else
          @variable.base
        unless node instanceof IdentifierLiteral or node instanceof PropertyName
          return @defaultClassVariableName
        name = node.value
        unless tail
          message = isUnassignable name
          @variable.error message if message
        if name in JS_FORBIDDEN then "_#{name}" else name
  • ¶

    對於類別定義中的所有this參考和繫結函式,this是正在建構的類別。

      setContext: (name) ->
        @body.traverseChildren false, (node) ->
          return false if node.classBody
          if node instanceof ThisLiteral
            node.value    = name
          else if node instanceof Code
            node.context  = name if node.bound
  • ¶

    確保繫結至執行個體的所有函式都在建構函式中被代理。

      addBoundFunctions: (o) ->
        for bvar in @boundFuncs
          lhs = (new Value (new ThisLiteral), [new Access bvar]).compile o
          @ctor.body.unshift new Literal "#{lhs} = #{utility 'bind', o}(#{lhs}, this)"
        return
  • ¶

    將來自頂層物件的屬性合併為類別上的原型屬性。

      addProperties: (node, name, o) ->
        props = node.base.properties[..]
        exprs = while assign = props.shift()
          if assign instanceof Assign
            base = assign.variable.base
            delete assign.context
            func = assign.value
            if base.value is 'constructor'
              if @ctor
                assign.error 'cannot define more than one constructor in a class'
              if func.bound
                assign.error 'cannot define a constructor as a bound function'
              if func instanceof Code
                assign = @ctor = func
              else
                @externalCtor = o.classScope.freeVariable 'ctor'
                assign = new Assign new IdentifierLiteral(@externalCtor), func
            else
              if assign.variable.this
                func.static = yes
              else
                acc = if base.isComplex() then new Index base else new Access base
                assign.variable = new Value(new IdentifierLiteral(name), [(new Access new PropertyName 'prototype'), acc])
                if func instanceof Code and func.bound
                  @boundFuncs.push base
                  func.bound = no
          assign
        compact exprs
  • ¶

    遍歷類別的主體,尋找要轉換的原型屬性,並標記靜態指派。

      walkBody: (name, o) ->
        @traverseChildren false, (child) =>
          cont = true
          return false if child instanceof Class
          if child instanceof Block
            for node, i in exps = child.expressions
              if node instanceof Assign and node.variable.looksStatic name
                node.value.static = yes
              else if node instanceof Value and node.isObject(true)
                cont = false
                exps[i] = @addProperties node, name, o
            child.expressions = exps = flatten exps
          cont and child not instanceof Class
  • ¶

    use strict(以及其他指令)必須是函式主體的第一個表達式陳述式。此方法可確保序言正確置於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
  • ¶

    確保已為類別定義建構函式,並正確設定。

      ensureConstructor: (name) ->
        if not @ctor
          @ctor = new Code
          if @externalCtor
            @ctor.body.push new Literal "#{@externalCtor}.apply(this, arguments)"
          else if @parent
            @ctor.body.push new Literal "#{name}.__super__.constructor.apply(this, arguments)"
          @ctor.body.makeReturn()
          @body.expressions.unshift @ctor
        @ctor.ctor = @ctor.name = name
        @ctor.klass = null
        @ctor.noReturn = yes
  • ¶

    我們並非直接產生 JavaScript 字串,而是建立等效的語法樹,並分批編譯。您可以在下方看到建構函式、屬性指派和繼承的建立方式。

      compileNode: (o) ->
        if jumpNode = @body.jumps()
          jumpNode.error 'Class bodies cannot contain pure statements'
        if argumentsNode = @body.contains isLiteralArguments
          argumentsNode.error "Class bodies shouldn't reference arguments"
    
        name  = @determineName()
        lname = new IdentifierLiteral name
        func  = new Code [], Block.wrap [@body]
        args  = []
        o.classScope = func.makeScope o.scope
    
        @hoistDirectivePrologue()
        @setContext name
        @walkBody name, o
        @ensureConstructor name
        @addBoundFunctions o
        @body.spaced = yes
        @body.expressions.push lname
    
        if @parent
          superClass = new IdentifierLiteral o.classScope.freeVariable 'superClass', reserve: no
          @body.expressions.unshift new Extends lname, superClass
          func.params.push new Param superClass
          args.push @parent
    
        @body.expressions.unshift @directives...
    
        klass = new Parens new Call func, args
        klass = new Assign @variable, klass, null, { @moduleDeclaration } if @variable
        klass.compileToFragments o
  • ¶

    匯入和匯出

    exports.ModuleDeclaration = class ModuleDeclaration extends Base
      constructor: (@clause, @source) ->
        @checkSource()
    
      children: ['clause', 'source']
    
      isStatement: YES
      jumps:       THIS
      makeReturn:  THIS
    
      checkSource: ->
        if @source? and @source instanceof StringWithInterpolations
          @source.error 'the name of the module to be imported from must be an uninterpolated string'
    
      checkScope: (o, moduleDeclarationType) ->
        if o.indent.length isnt 0
          @error "#{moduleDeclarationType} statements must be at top-level scope"
    
    exports.ImportDeclaration = class ImportDeclaration extends ModuleDeclaration
      compileNode: (o) ->
        @checkScope o, 'import'
        o.importedSymbols = []
    
        code = []
        code.push @makeCode "#{@tab}import "
        code.push @clause.compileNode(o)... if @clause?
    
        if @source?.value?
          code.push @makeCode ' from ' unless @clause is null
          code.push @makeCode @source.value
    
        code.push @makeCode ';'
        code
    
    exports.ImportClause = class ImportClause extends Base
      constructor: (@defaultBinding, @namedImports) ->
    
      children: ['defaultBinding', 'namedImports']
    
      compileNode: (o) ->
        code = []
    
        if @defaultBinding?
          code.push @defaultBinding.compileNode(o)...
          code.push @makeCode ', ' if @namedImports?
    
        if @namedImports?
          code.push @namedImports.compileNode(o)...
    
        code
    
    exports.ExportDeclaration = class ExportDeclaration extends ModuleDeclaration
      compileNode: (o) ->
        @checkScope o, 'export'
    
        code = []
        code.push @makeCode "#{@tab}export "
        code.push @makeCode 'default ' if @ instanceof ExportDefaultDeclaration
    
        if @ not instanceof ExportDefaultDeclaration and
           (@clause instanceof Assign or @clause instanceof Class)
  • ¶

    防止匯出匿名類別;所有匯出的成員都必須命名

          if @clause instanceof Class and not @clause.variable
            @clause.error 'anonymous classes cannot be exported'
  • ¶

    當 ES2015 class 關鍵字受支援時,不要在此處新增 var

          code.push @makeCode 'var '
          @clause.moduleDeclaration = 'export'
    
        if @clause.body? and @clause.body instanceof Block
          code = code.concat @clause.compileToFragments o, LEVEL_TOP
        else
          code = code.concat @clause.compileNode o
    
        code.push @makeCode " from #{@source.value}" if @source?.value?
        code.push @makeCode ';'
        code
    
    exports.ExportNamedDeclaration = class ExportNamedDeclaration extends ExportDeclaration
    
    exports.ExportDefaultDeclaration = class ExportDefaultDeclaration extends ExportDeclaration
    
    exports.ExportAllDeclaration = class ExportAllDeclaration extends ExportDeclaration
    
    exports.ModuleSpecifierList = class ModuleSpecifierList extends Base
      constructor: (@specifiers) ->
    
      children: ['specifiers']
    
      compileNode: (o) ->
        code = []
        o.indent += TAB
        compiledList = (specifier.compileToFragments o, LEVEL_LIST for specifier in @specifiers)
    
        if @specifiers.length isnt 0
          code.push @makeCode "{\n#{o.indent}"
          for fragments, index in compiledList
            code.push @makeCode(",\n#{o.indent}") if index
            code.push fragments...
          code.push @makeCode "\n}"
        else
          code.push @makeCode '{}'
        code
    
    exports.ImportSpecifierList = class ImportSpecifierList extends ModuleSpecifierList
    
    exports.ExportSpecifierList = class ExportSpecifierList extends ModuleSpecifierList
    
    exports.ModuleSpecifier = class ModuleSpecifier extends Base
      constructor: (@original, @alias, @moduleDeclarationType) ->
  • ¶

    進入區域範圍的變數名稱

        @identifier = if @alias? then @alias.value else @original.value
    
      children: ['original', 'alias']
    
      compileNode: (o) ->
        o.scope.find @identifier, @moduleDeclarationType
        code = []
        code.push @makeCode @original.value
        code.push @makeCode " as #{@alias.value}" if @alias?
        code
    
    exports.ImportSpecifier = class ImportSpecifier extends ModuleSpecifier
      constructor: (imported, local) ->
        super imported, local, 'import'
    
      compileNode: (o) ->
  • ¶

    根據規範,符號無法多次匯入(例如:import { foo, foo } from 'lib' 無效)

        if @identifier in o.importedSymbols or o.scope.check(@identifier)
          @error "'#{@identifier}' has already been declared"
        else
          o.importedSymbols.push @identifier
        super o
    
    exports.ImportDefaultSpecifier = class ImportDefaultSpecifier extends ImportSpecifier
    
    exports.ImportNamespaceSpecifier = class ImportNamespaceSpecifier extends ImportSpecifier
    
    exports.ExportSpecifier = class ExportSpecifier extends ModuleSpecifier
      constructor: (local, exported) ->
        super local, exported, 'export'
  • ¶

    指定

  • ¶

    指定用於將區域變數指定給值,或設定物件的屬性,包括在物件文字中。

    exports.Assign = class Assign extends Base
      constructor: (@variable, @value, @context, options = {}) ->
        {@param, @subpattern, @operatorToken, @moduleDeclaration} = options
    
      children: ['variable', 'value']
    
      isStatement: (o) ->
        o?.level is LEVEL_TOP and @context? and (@moduleDeclaration or "?" in @context)
    
      checkAssignability: (o, varBase) ->
        if Object::hasOwnProperty.call(o.scope.positions, varBase.value) and
           o.scope.variables[o.scope.positions[varBase.value]].type is 'import'
          varBase.error "'#{varBase.value}' is read-only"
    
      assigns: (name) ->
        @[if @context is 'object' then 'value' else 'variable'].assigns name
    
      unfoldSoak: (o) ->
        unfoldSoak o, this, 'variable'
  • ¶

    編譯指定,適當時委派給 compilePatternMatch 或 compileSplice。追蹤我們已指定到的基礎物件名稱,以取得正確的內部參照。如果目前範圍內尚未看到變數,請宣告它。

      compileNode: (o) ->
        if isValue = @variable instanceof Value
          return @compilePatternMatch o if @variable.isArray() or @variable.isObject()
          return @compileSplice       o if @variable.isSplice()
          return @compileConditional  o if @context in ['||=', '&&=', '?=']
          return @compileSpecialMath  o if @context in ['**=', '//=', '%%=']
        if @value instanceof Code
          if @value.static
            @value.klass = @variable.base
            @value.name  = @variable.properties[0]
            @value.variable = @variable
          else if @variable.properties?.length >= 2
            [properties..., prototype, name] = @variable.properties
            if prototype.name?.value is 'prototype'
              @value.klass = new Value @variable.base, properties
              @value.name  = name
              @value.variable = @variable
        unless @context
          varBase = @variable.unwrapAll()
          unless varBase.isAssignable()
            @variable.error "'#{@variable.compile o}' can't be assigned"
          unless varBase.hasProperties?()
  • ¶

    moduleDeclaration 可以是 'import' 或 'export'

            if @moduleDeclaration
              @checkAssignability o, varBase
              o.scope.add varBase.value, @moduleDeclaration
            else if @param
              o.scope.add varBase.value, 'var'
            else
              @checkAssignability o, varBase
              o.scope.find varBase.value
    
        val = @value.compileToFragments o, LEVEL_LIST
        @variable.front = true if isValue and @variable.base instanceof Obj
        compiledName = @variable.compileToFragments o, LEVEL_LIST
    
        if @context is 'object'
          if fragmentsToText(compiledName) in JS_FORBIDDEN
            compiledName.unshift @makeCode '"'
            compiledName.push @makeCode '"'
          return compiledName.concat @makeCode(": "), val
    
        answer = compiledName.concat @makeCode(" #{ @context or '=' } "), val
        if o.level <= LEVEL_LIST then answer else @wrapInBraces answer
  • ¶

    當將陣列或物件文字指定給值時,簡要實作遞迴模式比對。查看其屬性以指定內部名稱。

      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
        [obj] = objects
        if olen is 1 and obj instanceof Expansion
          obj.error 'Destructuring assignment has no target'
        isObject = @variable.isObject()
        if top and olen is 1 and obj not instanceof Splat
  • ¶

    當只有一個要挑選時,直接從值中挑選屬性(不需要將值快取到變數中)。

          defaultValue = null
          if obj instanceof Assign and obj.context is 'object'
  • ¶

    常規物件模式比對。

            {variable: {base: idx}, value: obj} = obj
            if obj instanceof Assign
              defaultValue = obj.value
              obj = obj.variable
          else
            if obj instanceof Assign
              defaultValue = obj.value
              obj = obj.variable
            idx = if isObject
  • ¶

    簡寫 {a, b, @c} = val 模式比對。

              if obj.this
                obj.properties[0].name
              else
                new PropertyName obj.unwrap().value
            else
  • ¶

    常規陣列模式比對。

              new NumberLiteral 0
          acc   = idx.unwrap() instanceof PropertyName
          value = new Value value
          value.properties.push new (if acc then Access else Index) idx
          message = isUnassignable obj.unwrap().value
          obj.error message if message
          value = new Op '?', value, defaultValue if defaultValue
          return new Assign(obj, value, null, param: @param).compileToFragments o, LEVEL_TOP
        vvar     = value.compileToFragments o, LEVEL_LIST
        vvarText = fragmentsToText vvar
        assigns  = []
        expandedIdx = false
  • ¶

    如果 vvar 不是簡單變數,請將其變成簡單變數。

        if value.unwrap() not instanceof IdentifierLiteral or @variable.assigns(vvarText)
          assigns.push [@makeCode("#{ ref = o.scope.freeVariable 'ref' } = "), vvar...]
          vvar = [@makeCode ref]
          vvarText = ref
        for obj, i in objects
          idx = i
          if not expandedIdx and obj instanceof Splat
            name = obj.name.unwrap().value
            obj = obj.unwrap()
            val = "#{olen} <= #{vvarText}.length ? #{ utility 'slice', o }.call(#{vvarText}, #{i}"
            if rest = olen - i - 1
              ivar = o.scope.freeVariable 'i', single: true
              val += ", #{ivar} = #{vvarText}.length - #{rest}) : (#{ivar} = #{i}, [])"
            else
              val += ") : []"
            val   = new Literal val
            expandedIdx = "#{ivar}++"
          else if not expandedIdx and obj instanceof Expansion
            if rest = olen - i - 1
              if rest is 1
                expandedIdx = "#{vvarText}.length - 1"
              else
                ivar = o.scope.freeVariable 'i', single: true
                val = new Literal "#{ivar} = #{vvarText}.length - #{rest}"
                expandedIdx = "#{ivar}++"
                assigns.push val.compileToFragments o, LEVEL_LIST
            continue
          else
            if obj instanceof Splat or obj instanceof Expansion
              obj.error "multiple splats/expansions are disallowed in an assignment"
            defaultValue = null
            if obj instanceof Assign and obj.context is 'object'
  • ¶

    常規物件模式比對。

              {variable: {base: idx}, value: obj} = obj
              if obj instanceof Assign
                defaultValue = obj.value
                obj = obj.variable
            else
              if obj instanceof Assign
                defaultValue = obj.value
                obj = obj.variable
              idx = if isObject
  • ¶

    簡寫 {a, b, @c} = val 模式比對。

                if obj.this
                  obj.properties[0].name
                else
                  new PropertyName obj.unwrap().value
              else
  • ¶

    常規陣列模式比對。

                new Literal expandedIdx or idx
            name = obj.unwrap().value
            acc = idx.unwrap() instanceof PropertyName
            val = new Value new Literal(vvarText), [new (if acc then Access else Index) idx]
            val = new Op '?', val, defaultValue if defaultValue
          if name?
            message = isUnassignable name
            obj.error message if message
          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
  • ¶

    編譯條件指定時,請確保運算元只評估一次,即使我們必須參照它們多次。

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

    禁止條件式指派未定義的變數。

        if not left.properties.length and left.base instanceof Literal and
               left.base not instanceof ThisLiteral and not o.scope.check left.base.value
          @variable.error "the variable \"#{left.base.value}\" can't be assigned with #{@context} because it has not been declared before"
        if "?" in @context
          o.isExistentialEquals = true
          new If(new Existence(left), right, type: 'if').addElse(new Assign(right, @value, '=')).compileToFragments o
        else
          fragments = new Op(@context[...-1], left, new Assign(right, @value, '=')).compileToFragments o
          if o.level <= LEVEL_LIST then fragments else @wrapInBraces fragments
  • ¶

    將特殊數學指派運算子(例如 a **= b)轉換成等效的延伸形式 a = a ** b,然後編譯它。

      compileSpecialMath: (o) ->
        [left, right] = @variable.cacheReference o
        new Assign(left, new Op(@context[...-1], right, @value)).compileToFragments o
  • ¶

    使用 JavaScript 的 Array#splice 方法,從陣列切片文字編譯指派。

      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?.isNumber() and to.isNumber()
            to = to.compile(o) - fromRef
            to += 1 unless exclusive
          else
            to = to.compile(o, LEVEL_ACCESS) + ' - ' + fromRef
            to += ' + 1' unless exclusive
        else
          to = "9e9"
        [valDef, valRef] = @value.cache o, LEVEL_LIST
        answer = [].concat @makeCode("[].splice.apply(#{name}, [#{fromDecl}, #{to}].concat("), valDef, @makeCode(")), "), valRef
        if o.level > LEVEL_TOP then @wrapInBraces answer else answer
  • ¶

    程式碼

  • ¶

    函式定義。這是唯一會建立新作用域的節點。當為了遍歷函式主體的內容時,程式碼沒有任何 子節點,它們在內部作用域中。

    exports.Code = class Code extends Base
      constructor: (params, body, tag) ->
        @params      = params or []
        @body        = body or new Block
        @bound       = tag is 'boundfunc'
        @isGenerator = [email protected] (node) ->
          (node instanceof Op and node.isYield()) or node instanceof YieldReturn
    
      children: ['params', 'body']
    
      isStatement: -> !!@ctor
    
      jumps: NO
    
      makeScope: (parentScope) -> new Scope parentScope, @body, this
  • ¶

    編譯會建立一個新作用域,除非明確要求與外部作用域共用。透過窺探 JavaScript arguments 物件,處理參數清單中的展開參數。如果函式使用 => 箭號繫結,會產生一個包裝器,透過封閉來儲存 this 的目前值。

      compileNode: (o) ->
    
        if @bound and o.scope.method?.bound
          @context = o.scope.method.context
  • ¶

    提早處理繫結函式。

        if @bound and not @context
          @context = '_this'
          wrapper = new Code [new Param new IdentifierLiteral @context], new Block [this]
          boundfunc = new Call(wrapper, [new ThisLiteral])
          boundfunc.updateLocationDataIfMissing @locationData
          return boundfunc.compileNode(o)
    
        o.scope         = del(o, 'classScope') or @makeScope o.scope
        o.scope.shared  = del(o, 'sharedScope')
        o.indent        += TAB
        delete o.bare
        delete o.isExistentialEquals
        params = []
        exprs  = []
        for param in @params when param not instanceof Expansion
          o.scope.parameter param.asReference o
        for param in @params when param.splat or param instanceof Expansion
          for p in @params when p not instanceof Expansion and p.name.value
            o.scope.add p.name.value, 'var', yes
          splats = new Assign new Value(new Arr(p.asReference o for p in @params)),
                              new Value new IdentifierLiteral 'arguments'
          break
        for param in @params
          if param.isComplex()
            val = ref = param.asReference o
            val = new Op '?', ref, param.value if param.value
            exprs.push new Assign new Value(param.name), val, '=', param: yes
          else
            ref = param
            if param.value
              lit = new Literal ref.name.value + ' == null'
              val = new Assign new Value(param.name), param.value, '='
              exprs.push new If lit, val
          params.push ref unless splats
        wasEmpty = @body.isEmpty()
        exprs.unshift splats if splats
        @body.expressions.unshift exprs... if exprs.length
        for p, i in params
          params[i] = p.compileToFragments o
          o.scope.parameter fragmentsToText params[i]
        uniqs = []
        @eachParamName (name, node) ->
          node.error "multiple parameters named #{name}" if name in uniqs
          uniqs.push name
        @body.makeReturn() unless wasEmpty or @noReturn
        code = 'function'
        code += '*' if @isGenerator
        code += ' ' + @name if @ctor
        code += '('
        answer = [@makeCode(code)]
        for p, i in params
          if i then answer.push @makeCode ", "
          answer.push p...
        answer.push @makeCode ') {'
        answer = answer.concat(@makeCode("\n"), @body.compileWithDeclarations(o), @makeCode("\n#{@tab}")) unless @body.isEmpty()
        answer.push @makeCode '}'
    
        return [@makeCode(@tab), answer...] if @ctor
        if @front or (o.level >= LEVEL_ACCESS) then @wrapInBraces answer else answer
    
      eachParamName: (iterator) ->
        param.eachName iterator for param in @params
  • ¶

    短路 traverseChildren 方法,以防止它跨越作用域邊界,除非 crossScope 為 true。

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

    參數

  • ¶

    函式定義中的參數。除了典型的 JavaScript 參數之外,這些參數還可以附加到函式的內容,也可以是展開參數,將一群參數收集到陣列中。

    exports.Param = class Param extends Base
      constructor: (@name, @value, @splat) ->
        message = isUnassignable @name.unwrapAll().value
        @name.error message if message
        if @name instanceof Obj and @name.generated
          token = @name.objects[0].operatorToken
          token.error "unexpected #{token.value}"
    
      children: ['name', 'value']
    
      compileToFragments: (o) ->
        @name.compileToFragments o, LEVEL_LIST
    
      asReference: (o) ->
        return @reference if @reference
        node = @name
        if node.this
          name = node.properties[0].name.value
          name = "_#{name}" if name in JS_FORBIDDEN
          node = new IdentifierLiteral o.scope.freeVariable name
        else if node.isComplex()
          node = new IdentifierLiteral o.scope.freeVariable 'arg'
        node = new Value node
        node = new Splat node if @splat
        node.updateLocationDataIfMissing @locationData
        @reference = node
    
      isComplex: ->
        @name.isComplex()
  • ¶

    反覆運算 Param 的名稱或名稱。在某種意義上,解構參數表示多個 JS 參數。此方法允許反覆運算所有參數。iterator 函數會以 iterator(name, node) 的形式呼叫,其中 name 是參數的名稱,而 node 是對應於該名稱的 AST 節點。

      eachName: (iterator, name = @name)->
        atParam = (obj) -> iterator "@#{obj.properties[0].name.value}", obj
  • ¶
    • 簡單字面值 foo
        return iterator name.value, name if name instanceof Literal
  • ¶
    • at 參數 @foo
        return atParam name if name instanceof Value
        for obj in name.objects ? []
  • ¶
    • 具有預設值之解構參數
          if obj instanceof Assign and not obj.context?
            obj = obj.variable
  • ¶
    • 解構參數內的指定 {foo:bar}
          if obj instanceof Assign
  • ¶

    … 可能具有預設值

            if obj.value instanceof Assign
              obj = obj.value
            @eachName iterator, obj.value.unwrap()
  • ¶
    • 解構參數內的 splat [xs...]
          else if obj instanceof Splat
            node = obj.name.unwrap()
            iterator node.value, node
          else if obj instanceof Value
  • ¶
    • 解構參數內的解構參數 [{a}]
            if obj.isArray() or obj.isObject()
              @eachName iterator, obj.base
  • ¶
    • 解構參數內的 at 參數 {@foo}
            else if obj.this
              atParam obj
  • ¶
    • 簡單解構參數 {foo}
            else iterator obj.base.value, obj.base
          else if obj not instanceof Expansion
            obj.error "illegal parameter #{obj.compile()}"
        return
  • ¶

    Splat

  • ¶

    Splat,作為函數的參數、呼叫的引數,或解構指定的一部分。

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

    將任意數量的元素(與 splat 混合)轉換為適當陣列的實用函數。

      @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', o }.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', o }.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, ', '
        [..., last] = list
        [].concat list[0].makeCode("["), base, list[index].makeCode("].concat("), concatPart, last.makeCode(")")
  • ¶

    展開

  • ¶

    用於略過陣列解構(模式比對)或參數清單中的值。

    exports.Expansion = class Expansion extends Base
    
      isComplex: NO
    
      compileNode: (o) ->
        @error 'Expansion must be used inside a destructuring assignment or parameter list'
    
      asReference: (o) ->
        this
    
      eachName: (iterator) ->
  • ¶

    While

  • ¶

    While 迴圈,是 CoffeeScript 唯一公開的低階迴圈。從此迴圈,可以建構所有其他迴圈。在需要比理解能提供的更大彈性或速度時,此迴圈很有用。

    exports.While = class While extends Base
      constructor: (condition, options) ->
        @condition = if options?.invert then condition.invert() else condition
        @guard     = options?.guard
    
      children: ['condition', 'guard', 'body']
    
      isStatement: YES
    
      makeReturn: (res) ->
        if res
          super
        else
          @returns = not @jumps loop: yes
          this
    
      addBody: (@body) ->
        this
    
      jumps: ->
        {expressions} = @body
        return no unless expressions.length
        for node in expressions
          return jumpNode if jumpNode = node.jumps loop: yes
        no
  • ¶

    與 JavaScript 中的 while 主要不同之處在於,CoffeeScript 中的 while 可用於較大的表達式中 – while 迴圈可能會傳回包含每次反覆運算計算結果的陣列。

      compileNode: (o) ->
        o.indent += TAB
        set      = ''
        {body}   = this
        if body.isEmpty()
          body = @makeCode ''
        else
          if @returns
            body.makeReturn rvar = o.scope.freeVariable 'results'
            set  = "#{@tab}#{rvar} = [];\n"
          if @guard
            if body.expressions.length > 1
              body.expressions.unshift new If (new Parens @guard).invert(), new StatementLiteral "continue"
            else
              body = Block.wrap [new If @guard, body] if @guard
          body = [].concat @makeCode("\n"), (body.compileToFragments o, LEVEL_TOP), @makeCode("\n#{@tab}")
        answer = [].concat @makeCode(set + @tab + "while ("), @condition.compileToFragments(o, LEVEL_PAREN),
          @makeCode(") {"), body, @makeCode("}")
        if @returns
          answer.push @makeCode "\n#{@tab}return #{rvar};"
        answer
  • ¶

    Op

  • ¶

    簡單的算術和邏輯運算。執行一些 CoffeeScript 運算轉換成 JavaScript 等價運算的動作。

    exports.Op = class Op extends Base
      constructor: (op, first, second, flip ) ->
        return new In first, second if op is 'in'
        if op is 'do'
          return @generateDo first
        if op is 'new'
          return first.newInstance() if first instanceof Call and not first.do and not first.isNew
          first = new Parens first   if first instanceof Code and first.bound or first.do
        @operator = CONVERSIONS[op] or op
        @first    = first
        @second   = second
        @flip     = !!flip
        return this
  • ¶

    CoffeeScript 轉換成 JavaScript 符號的對應表。

      CONVERSIONS =
        '==':        '==='
        '!=':        '!=='
        'of':        'in'
        'yieldfrom': 'yield*'
  • ¶

    可逆運算子的對應表。

      INVERSIONS =
        '!==': '==='
        '===': '!=='
    
      children: ['first', 'second']
    
      isNumber: ->
        @isUnary() and @operator in ['+', '-'] and
          @first instanceof Value and @first.isNumber()
    
      isYield: ->
        @operator in ['yield', 'yield*']
    
      isUnary: ->
        not @second
    
      isComplex: ->
        not @isNumber()
  • ¶

    我有能力進行 Python 式的比較鏈結 嗎?

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

    在鏈結中,不需要用括號包住單獨的 obj 文字,因為鏈結的表達式已經被包住了。

        @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 ['--', '++']
          message = isUnassignable @first.unwrapAll().value
          @first.error message if message
        return @compileYield     o if @isYield()
        return @compileUnary     o if @isUnary()
        return @compileChain     o if isChain
        switch @operator
          when '?'  then @compileExistence o
          when '**' then @compilePower o
          when '//' then @compileFloorDivision o
          when '%%' then @compileModulo o
          else
            lhs = @first.compileToFragments o, LEVEL_OP
            rhs = @second.compileToFragments o, LEVEL_OP
            answer = [].concat lhs, @makeCode(" #{@operator} "), rhs
            if o.level <= LEVEL_OP then answer else @wrapInBraces answer
  • ¶

    當連續使用多個比較運算子時,模擬 Python 的鏈結比較。例如

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

    保留左邊表達式的參照,除非這是存在性指派

      compileExistence: (o) ->
        if @first.isComplex()
          ref = new IdentifierLiteral 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
  • ¶

    編譯一元 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, ''
    
      compileYield: (o) ->
        parts = []
        op = @operator
        unless o.scope.parent?
          @error 'yield can only occur inside functions'
        if 'expression' in Object.keys(@first) and not (@first instanceof Throw)
          parts.push @first.expression.compileToFragments o, LEVEL_OP if @first.expression?
        else
          parts.push [@makeCode "("] if o.level >= LEVEL_PAREN
          parts.push [@makeCode op]
          parts.push [@makeCode " "] if @first.base?.value isnt ''
          parts.push @first.compileToFragments o, LEVEL_OP
          parts.push [@makeCode ")"] if o.level >= LEVEL_PAREN
        @joinFragmentArrays parts, ''
    
      compilePower: (o) ->
  • ¶

    呼叫 Math.pow

        pow = new Value new IdentifierLiteral('Math'), [new Access new PropertyName 'pow']
        new Call(pow, [@first, @second]).compileToFragments o
    
      compileFloorDivision: (o) ->
        floor = new Value new IdentifierLiteral('Math'), [new Access new PropertyName 'floor']
        second = if @second.isComplex() then new Parens @second else @second
        div = new Op '/', @first, second
        new Call(floor, [div]).compileToFragments o
    
      compileModulo: (o) ->
        mod = new Value new Literal utility 'modulo', o
        new Call(mod, [@first, @second]).compileToFragments o
    
      toString: (idt) ->
        super idt, @constructor.name + ' ' + @operator
  • ¶

    In

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

    只有當我們有一個沒有散佈的陣列文字時,才 compileOrTest

          return @compileOrTest o unless hasSplat
        @compileLoopTest o
    
      compileOrTest: (o) ->
        [sub, ref] = @object.cache o, LEVEL_OP
        [cmp, cnj] = if @negated then [' !== ', ' && '] else [' === ', ' || ']
        tests = []
        for item, i in @array.base.objects
          if i then tests.push @makeCode cnj
          tests = tests.concat (if i then ref else sub), @makeCode(cmp), item.compileToFragments(o, LEVEL_ACCESS)
        if o.level < LEVEL_OP then tests else @wrapInBraces tests
    
      compileLoopTest: (o) ->
        [sub, ref] = @object.cache o, LEVEL_LIST
        fragments = [].concat @makeCode(utility('indexOf', o) + ".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 ''
  • ¶

    Try

  • ¶

    一個經典的 try/catch/finally 區塊。

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

    編譯大致上是你所預期的 – finally 子句是可選的,catch 不是。

      compileNode: (o) ->
        o.indent  += TAB
        tryPart   = @attempt.compileToFragments o, LEVEL_TOP
    
        catchPart = if @recovery
          generatedErrorVariableName = o.scope.freeVariable 'error', reserve: no
          placeholder = new IdentifierLiteral generatedErrorVariableName
          if @errorVariable
            message = isUnassignable @errorVariable.unwrapAll().value
            @errorVariable.error message if message
            @recovery.unshift new Assign @errorVariable, placeholder
          [].concat @makeCode(" catch ("), placeholder.compileToFragments(o), @makeCode(") {\n"),
            @recovery.compileToFragments(o, LEVEL_TOP), @makeCode("\n#{@tab}}")
        else unless @ensure or @recovery
          generatedErrorVariableName = o.scope.freeVariable 'error', reserve: no
          [@makeCode(" catch (#{generatedErrorVariableName}) {}")]
        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

  • ¶

    一個用於擲回例外的簡單節點。

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

    Throw 已經是一種回傳,某種程度上來說…

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

    存在

  • ¶

    檢查變數是否存在 – 不是 null 也不是 undefined。這類似於 Ruby 中的 .nil?,並避免必須查閱 JavaScript 真值表。

    exports.Existence = class Existence extends Base
      constructor: (@expression) ->
    
      children: ['expression']
    
      invert: NEGATE
    
      compileNode: (o) ->
        @expression.front = @front
        code = @expression.compile o, LEVEL_OP
        if @expression.unwrap() instanceof IdentifierLiteral and not o.scope.check code
          [cmp, cnj] = if @negated then ['===', '||'] else ['!==', '&&']
          code = "typeof #{code} #{cmp} \"undefined\" #{cnj} #{code} #{cmp} null"
        else
  • ¶

    在此處不要使用嚴格相等;它會中斷現有程式碼

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

    括號

  • ¶

    一組額外的括號,在來源中明確指定。我們曾經試圖透過偵測和移除多餘的括號來清理結果,但不再這樣做 – 你可以放進任意多個括號。

    括號是強制任何陳述式變成表達式的良好方式。

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

    StringWithInterpolations

  • ¶

    帶有內插的字串實際上只是 Parens 的變體,內部有字串串接。

    exports.StringWithInterpolations = class StringWithInterpolations extends Parens
  • ¶

    在 CoffeeScript 2 中取消註解以下行,以允許使用 ES2015 語法輸出所有內插字串:unwrap: -> this

      compileNode: (o) ->
  • ¶

    此方法使用新的 ES2015 語法產生內插字串,這是透過使用標記範本字面值來選擇加入的。如果此 StringWithInterpolations 不在標記範本字面值中,則回復到 CoffeeScript 1.x 輸出。(在 CoffeeScript 2 中移除此檢查。)

        unless o.inTaggedTemplateCall
          return super
  • ¶

    假設:expr 是 Value>StringLiteral 或 Op

        expr = @body.unwrap()
    
        elements = []
        expr.traverseChildren no, (node) ->
          if node instanceof StringLiteral
            elements.push node
            return yes
          else if node instanceof Parens
            elements.push node
            return no
          return yes
    
        fragments = []
        fragments.push @makeCode '`'
        for element in elements
          if element instanceof StringLiteral
            value = element.value[1...-1]
  • ¶

    範本字面值中的反引號和 ${ 必須轉譯。

            value = value.replace /(\\*)(`|\$\{)/g, (match, backslashes, toBeEscaped) ->
              if backslashes.length % 2 is 0
                "#{backslashes}\\#{toBeEscaped}"
              else
                match
            fragments.push @makeCode value
          else
            fragments.push @makeCode '${'
            fragments.push element.compileToFragments(o, LEVEL_PAREN)...
            fragments.push @makeCode '}'
        fragments.push @makeCode '`'
    
        fragments
  • ¶

    For

  • ¶

    CoffeeScript 取代 for 迴圈的是我們的陣列和物件理解,它們在此編譯成 for 迴圈。它們也作為一個表達式,能夠傳回每個過濾反覆運算的結果。

    與 Python 陣列理解不同,它們可以是多行的,而且你可以將迴圈的目前索引作為第二個參數傳遞。與 Ruby 區塊不同,你可以在單次通過中對應和過濾。

    exports.For = class For extends While
      constructor: (body, source) ->
        {@source, @guard, @step, @name, @index} = source
        @body    = Block.wrap [body]
        @own     = !!source.own
        @object  = !!source.object
        @from    = !!source.from
        @index.error 'cannot use index with for-from' if @from and @index
        source.ownTag.error "cannot use own with for-#{if @from then 'from' else 'in'}" if @own and not @object
        [@name, @index] = [@index, @name] if @object
        @index.error 'index cannot be a pattern matching expression' if @index instanceof Value and not @index.isAssignable()
        @range   = @source instanceof Value and @source.base instanceof Range and not @source.properties.length and not @from
        @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
        @returns = false
    
      children: ['body', 'source', 'guard', 'step']
  • ¶

    歡迎使用 CoffeeScript 中最複雜的方法。處理陣列、物件和範圍理解的內部迴圈、過濾、遞增和結果儲存。部分產生的程式碼可以在共用中分享,而部分則不能。

      compileNode: (o) ->
        body        = Block.wrap [@body]
        [..., last] = body.expressions
        @returns    = no if last?.jumps() instanceof Return
        source      = if @range then @source.base else @source
        scope       = o.scope
        name        = @name  and (@name.compile o, LEVEL_LIST) if not @pattern
        index       = @index and (@index.compile o, LEVEL_LIST)
        scope.find(name)  if name and not @pattern
        scope.find(index) if index and @index not instanceof Value
        rvar        = scope.freeVariable 'results' if @returns
        if @from
          ivar = scope.freeVariable 'x', single: true if @pattern
        else
          ivar = (@object and index) or scope.freeVariable 'i', single: true
        kvar        = ((@range or @from) 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, isComplexOrAssignable
          stepNum   = Number stepVar if @step.isNumber()
        name        = ivar if @pattern
        varPart     = ''
        guardPart   = ''
        defPart     = ''
        idt1        = @tab + TAB
        if @range
          forPartFragments = source.compileToFragments merge o,
            {index: ivar, name, @step, isComplex: isComplexOrAssignable}
        else
          svar    = @source.compile o, LEVEL_LIST
          if (name or @own) and @source.unwrap() not instanceof IdentifierLiteral
            defPart    += "#{@tab}#{ref = scope.freeVariable 'ref'} = #{svar};\n"
            svar       = ref
          if name and not @pattern and not @from
            namePart   = "#{name} = #{svar}[#{kvar}]"
          if not @object and not @from
            defPart += "#{@tab}#{step};\n" if step isnt stepVar
            down = stepNum < 0
            lvar = scope.freeVariable 'len' unless @step and stepNum? and down
            declare = "#{kvarAssign}#{ivar} = 0, #{lvar} = #{svar}.length"
            declareDown = "#{kvarAssign}#{ivar} = #{svar}.length - 1"
            compare = "#{ivar} < #{lvar}"
            compareDown = "#{ivar} >= 0"
            if @step
              if stepNum?
                if down
                  compare = compareDown
                  declare = declareDown
              else
                compare = "#{stepVar} > 0 ? #{compare} : #{compareDown}"
                declare = "(#{stepVar} > 0 ? (#{declare}) : #{declareDown})"
              increment = "#{ivar} += #{stepVar}"
            else
              increment = "#{if kvar isnt ivar then "++#{ivar}" else "#{ivar}++"}"
            forPartFragments = [@makeCode("#{declare}; #{compare}; #{kvarAssign}#{increment}")]
        if @returns
          resultPart   = "#{@tab}#{rvar} = [];\n"
          returnResult = "\n#{@tab}return #{rvar};"
          body.makeReturn rvar
        if @guard
          if body.expressions.length > 1
            body.expressions.unshift new If (new Parens @guard).invert(), new StatementLiteral "continue"
          else
            body = Block.wrap [new If @guard, body] if @guard
        if @pattern
          body.expressions.unshift new Assign @name, if @from then new IdentifierLiteral kvar else 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', o}.call(#{svar}, #{kvar})) continue;" if @own
        else if @from
          forPartFragments = [@makeCode("#{kvar} of #{svar}")]
        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 IdentifierLiteral 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
  • ¶

    切換

  • ¶

    一個 JavaScript switch 陳述式。依需求轉換成可傳回的表達式。

    exports.Switch = class Switch extends Base
      constructor: (@subject, @cases, @otherwise) ->
    
      children: ['subject', 'cases', 'otherwise']
    
      isStatement: YES
    
      jumps: (o = {block: yes}) ->
        for [conds, block] in @cases
          return jumpNode if jumpNode = block.jumps o
        @otherwise?.jumps o
    
      makeReturn: (res) ->
        pair[1].makeReturn res for pair in @cases
        @otherwise or= new Block [new Literal 'void 0'] if res
        @otherwise?.makeReturn res
        this
    
      compileNode: (o) ->
        idt1 = o.indent + TAB
        idt2 = o.indent = idt1 + TAB
        fragments = [].concat @makeCode(@tab + "switch ("),
          (if @subject then @subject.compileToFragments(o, LEVEL_PAREN) else @makeCode "false"),
          @makeCode(") {\n")
        for [conditions, block], i in @cases
          for cond in flatten [conditions]
            cond  = cond.invert() unless @subject
            fragments = fragments.concat @makeCode(idt1 + "case "), cond.compileToFragments(o, LEVEL_PAREN), @makeCode(":\n")
          fragments = fragments.concat body, @makeCode('\n') if (body = block.compileToFragments o, LEVEL_TOP).length > 0
          break if i is @cases.length - 1 and not @otherwise
          expr = @lastNonComment block.expressions
          continue if expr instanceof Return or (expr instanceof Literal and expr.jumps() and expr.value isnt 'debugger')
          fragments.push cond.makeCode(idt2 + 'break;\n')
        if @otherwise and @otherwise.expressions.length
          fragments.push @makeCode(idt1 + "default:\n"), (@otherwise.compileToFragments o, LEVEL_TOP)..., @makeCode("\n")
        fragments.push @makeCode @tab + '}'
        fragments
  • ¶

    如果

  • ¶

    如果/否則 陳述式。透過將請求的傳回推送到每個子句的最後一行,作為一個表達式作用。

    單一表達式的 如果 會編譯成條件運算子(如果可能的話),因為三元運算子已經是適當的表達式,不需要轉換。

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

    改寫 如果 的鏈,將預設情況新增為最後的 否則。

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

    如果 僅在任一主體需要成為陳述式時編譯成陳述式。否則,條件運算子是安全的。

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

    將 如果 編譯成常規的 if-else 陳述式。扁平化鏈會強制內部 否則 主體進入陳述式形式。

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

    將 如果 編譯成條件運算子。

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

    常數

  • ¶
    UTILITIES =
  • ¶

    正確設定繼承的原型鏈,包括對超類別的參考以進行 super() 呼叫,以及任何靜態屬性的副本。

      extend: (o) -> "
        function(child, parent) {
          for (var key in parent) {
            if (#{utility 'hasProp', o}.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;
        }
      "
  • ¶

    建立一個繫結到「this」目前值的函式。

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

    找出陣列中是否有某個項目。

      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;
        }
      "
    
      modulo: -> """
        function(a, b) { return (+a % (b = +b) + b) % b; }
      """
  • ¶

    捷徑,用於加快原生函數的查詢時間。

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

    層級表示節點在 AST 中的位置。對於了解括號是否必要或多餘很有用。

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

    標籤在美化列印時為兩個空格。

    TAB = '  '
    
    SIMPLENUM = /^[+-]?\d+$/
  • ¶

    輔助函數

  • ¶
  • ¶

    輔助函數,用於確保實用函數分配在最上層。

    utility = (name, o) ->
      {root} = o.scope
      if name of root.utilities
        root.utilities[name]
      else
        ref = root.freeVariable name
        root.assign ref, UTILITIES[name] o
        root.utilities[name] = ref
    
    multident = (code, tab) ->
      code = code.replace /\n/g, '$&' + tab
      code.replace /\s+$/, ''
    
    isLiteralArguments = (node) ->
      node instanceof IdentifierLiteral and node.value is 'arguments'
    
    isLiteralThis = (node) ->
      node instanceof ThisLiteral or
        (node instanceof Code and node.bound) or
        node instanceof SuperCall
    
    isComplexOrAssignable = (node) -> node.isComplex() or node.isAssignable?()
  • ¶

    如果浸泡,展開節點的子節點,然後將節點塞到建立的 If 中

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