• 跳转到 … +
    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 的节点。否则返回 undefined。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
  • ¶

    抛出与此节点位置相关的 SyntaxError。

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

    Block

  • ¶

    块是构成缩进代码块主体的一系列表达式——函数的实现、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
  • ¶

    如果此 Block 仅包含一个节点,则通过将其拉回出来将其解开。

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

    Block 节点不会返回其整个主体,而是确保返回最后一个表达式。

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

    Block 是唯一可以作为根节点的节点。

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

    编译 Block 主体内的所有表达式。如果我们需要返回结果,并且它是一个表达式,则只需返回它。如果它是一个语句,则要求语句执行此操作。

      compileNode: (o) ->
        @tab  = o.indent
        top   = o.level is LEVEL_TOP
        compiledNodes = []
    
        for node, index in @expressions
    
          node = node.unwrapAll()
          node = (node.unfoldSoak(o) or node)
          if node instanceof Block
  • ¶

    这是一个嵌套块。我们在这里不做任何特殊的事情,比如将其封闭在一个新的作用域中;我们只是编译此块中的语句以及我们自己的语句

            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

  • ¶

    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

  • ¶

    return 是一个纯语句——将其包装在闭包中将毫无意义。

    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 = []
  • ¶

    TODO:如果我们在这里两次调用 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
  • ¶

    Value

  • ¶

    一个值,变量或文字或带括号的,索引或带点的,或普通的。

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

    Comment

  • ¶

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

    Call

  • ¶

    函数调用的节点。

    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() 调用,以允许传递一个参数数组。如果它是一个构造函数,那么事情会变得非常棘手。我们必须注入一个内部构造函数,以便能够传递 varargs。

    splatArgs 是一个要放入“apply”中的 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 库 的 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
  • ¶

    Index

  • ¶

    对数组或对象进行 [ ... ] 索引访问。

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

    Range

  • ¶

    范围文字。范围可用于提取数组的部分(切片),指定推导的范围,或作为值,在运行时扩展为相应的整数数组。

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

    Slice

  • ¶

    数组切片文字。与 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']
  • ¶

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

    类

  • ¶

    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 对象来处理参数列表中的 splat 参数。如果函数使用 => 箭头绑定,则生成一个包装器,该包装器通过闭包保存 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 参数之外,这些参数还可以附加到函数的上下文,以及成为 splat,将一组参数收集到数组中。

    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-params @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-params {@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 仅当我们有一个没有 splat 的数组字面量时

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

    Existence

  • ¶

    检查变量是否存在 - 不是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})")]
  • ¶

    Parens

  • ¶

    一组额外的括号,在源代码中明确指定。我们曾经尝试通过检测和删除冗余括号来清理结果,但现在不再这样做 - 您可以随意添加任意数量的括号。

    括号是强制任何语句成为表达式的良好方法。

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

    Switch

  • ¶

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

    If

  • ¶

    If/else 语句。通过将请求的返回值推送到每个子句的最后一行来充当表达式。

    单表达式**Ifs** 如果可能,将编译成条件运算符,因为三元运算符已经是适当的表达式,不需要转换。

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

    重写**Ifs** 链以添加一个默认情况作为最终的else。

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

    **If** 仅在它的任何一个主体都需要成为语句时才编译成语句。否则,条件运算符是安全的。

      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 编译为常规的if-else 语句。扁平化的链将内部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
  • ¶

    将 If 编译为条件运算符。

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

    Constants

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