• ジャンプ先 … +
    browser.coffee cake.coffee coffeescript.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
  • rewriter.coffee

  • §

    CoffeeScript言語には、多くの省略可能な構文、暗黙的な構文、および短縮構文があります。 これにより、文法が非常に複雑になり、結果として得られる解析テーブルが肥大化します。 パーサーにすべてを処理させる代わりに、トークンストリームに対して一連のパスを実行し、このRewriterを使用して短縮形を明確な長形式に変換し、暗黙的なインデントと括弧を追加し、一般的にクリーンアップを行います。

    {throwSyntaxError, extractAllCommentTokens} = require './helpers'
  • §

    添付されたコメントをあるトークンから別のトークンに移動します。

    moveComments = (fromToken, toToken) ->
      return unless fromToken.comments
      if toToken.comments and toToken.comments.length isnt 0
        unshiftedComments = []
        for comment in fromToken.comments
          if comment.unshift
            unshiftedComments.push comment
          else
            toToken.comments.push comment
        toToken.comments = unshiftedComments.concat toToken.comments
      else
        toToken.comments = fromToken.comments
      delete fromToken.comments
  • §

    生成されたトークンを作成します。これは、暗黙的な構文の使用によって存在するトークンです。 オプションで、この新しいトークンに別のトークンから添付されたコメントを取得させます。

    generate = (tag, value, origin, commentsToken) ->
      token = [tag, value]
      token.generated = yes
      token.origin = origin if origin
      moveComments commentsToken, token if commentsToken
      token
  • §

    Rewriterクラスは、Lexerによって、その内部トークン配列に対して直接使用されます。

    exports.Rewriter = class Rewriter
  • §

    トークンストリームを複数パスで書き換え、一度に1つの論理フィルターを適用します。 これは確かに、大きな効率的なスイッチを使用してストリームを1回パススルーするように変更できますが、このように作業する方がはるかに快適です。 これらのパスの順序は重要です。インデントは、暗黙の括弧をコードブロックで囲む前に修正する必要があります。

      rewrite: (@tokens) ->
  • §

    環境変数DEBUG_TOKEN_STREAMをtrueに設定して、トークンデバッグ情報を出力します。 また、DEBUG_REWRITTEN_TOKEN_STREAMをtrueに設定して、このファイルによって書き換えられた後のトークンストリームを出力します。

        if process?.env?.DEBUG_TOKEN_STREAM
          console.log 'Initial token stream:' if process.env.DEBUG_REWRITTEN_TOKEN_STREAM
          console.log (t[0] + '/' + t[1] + (if t.comments then '*' else '') for t in @tokens).join ' '
        @removeLeadingNewlines()
        @closeOpenCalls()
        @closeOpenIndexes()
        @normalizeLines()
        @tagPostfixConditionals()
        @addImplicitBracesAndParens()
        @rescueStowawayComments()
        @addLocationDataToGeneratedTokens()
        @enforceValidJSXAttributes()
        @fixIndentationLocationData()
        @exposeTokenDataToGrammar()
        if process?.env?.DEBUG_REWRITTEN_TOKEN_STREAM
          console.log 'Rewritten token stream:' if process.env.DEBUG_TOKEN_STREAM
          console.log (t[0] + '/' + t[1] + (if t.comments then '*' else '') for t in @tokens).join ' '
        @tokens
  • §

    トークンストリームを書き換え、前後1つのトークンを調べます。 ブロックの戻り値を使用して、ストリーム内で何個のトークンを前後に移動するかを指示し、トークンが挿入および削除され、ストリームの長さが変化する際に何も見逃さないようにします。

      scanTokens: (block) ->
        {tokens} = this
        i = 0
        i += block.call this, token, i, tokens while token = tokens[i]
        true
    
      detectEnd: (i, condition, action, opts = {}) ->
        {tokens} = this
        levels = 0
        while token = tokens[i]
          return action.call this, token, i if levels is 0 and condition.call this, token, i
          if token[0] in EXPRESSION_START
            levels += 1
          else if token[0] in EXPRESSION_END
            levels -= 1
          if levels < 0
            return if opts.returnOnNegativeLevel
            return action.call this, token, i
          i += 1
        i - 1
  • §

    先頭の改行は文法に曖昧さを導入するため、ここでそれらを処理します。

      removeLeadingNewlines: ->
  • §

    最初の非TERMINATORトークンのインデックスを見つけます。

        break for [tag], i in @tokens when tag isnt 'TERMINATOR'
        return if i is 0
  • §

    破棄しようとしているトークンにコメントが添付されている場合は、それらを新しい最初のトークンになるものに転送します。

        for leadingNewlineToken in @tokens[0...i]
          moveComments leadingNewlineToken, @tokens[i]
  • §

    先頭のすべての改行トークンを破棄します。

        @tokens.splice 0, i
  • §

    レクサーは、メソッド呼び出しの開始括弧にタグを付けました。 それと対になる閉じ括弧と一致させます。

      closeOpenCalls: ->
        condition = (token, i) ->
          token[0] in [')', 'CALL_END']
    
        action = (token, i) ->
          token[0] = 'CALL_END'
    
        @scanTokens (token, i) ->
          @detectEnd i + 1, condition, action if token[0] is 'CALL_START'
          1
  • §

    レクサーは、インデックス操作呼び出しの開始ブラケットにタグを付けました。 それと対になる閉じブラケットと一致させます。

      closeOpenIndexes: ->
        startToken = null
        condition = (token, i) ->
          token[0] in [']', 'INDEX_END']
    
        action = (token, i) ->
          if @tokens.length >= i and @tokens[i + 1][0] is ':'
            startToken[0] = '['
            token[0] = ']'
          else
            token[0] = 'INDEX_END'
    
        @scanTokens (token, i) ->
          if token[0] is 'INDEX_START'
            startToken = token
            @detectEnd i + 1, condition, action
          1
  • §

    iから始まるトークンストリーム内のタグをpatternと一致させます。 patternは、文字列(等価)、文字列の配列(いずれか)、またはnull(ワイルドカード)で構成されます。 一致するインデックスを返します。一致しない場合は-1を返します。

      indexOfTag: (i, pattern...) ->
        fuzz = 0
        for j in [0 ... pattern.length]
          continue if not pattern[j]?
          pattern[j] = [pattern[j]] if typeof pattern[j] is 'string'
          return -1 if @tag(i + j + fuzz) not in pattern[j]
        i + j + fuzz - 1
  • §

    @<x>:、<x>:、または<EXPRESSION_START><x>...<EXPRESSION_END>:のように見えるものの前にある場合、yesを返します。

      looksObjectish: (j) ->
        return yes if @indexOfTag(j, '@', null, ':') isnt -1 or @indexOfTag(j, null, ':') isnt -1
        index = @indexOfTag j, EXPRESSION_START
        if index isnt -1
          end = null
          @detectEnd index + 1, ((token) -> token[0] in EXPRESSION_END), ((token, i) -> end = i)
          return yes if @tag(end + 1) is ':'
        no
  • §

    現在のトークン行に、同じ式レベルのタグの要素が含まれている場合、yesを返します。 LINEBREAKSまたは包含するバランスの取れた式の明示的な開始で検索を停止します。

      findTagsBackwards: (i, tags) ->
        backStack = []
        while i >= 0 and (backStack.length or
              @tag(i) not in tags and
              (@tag(i) not in EXPRESSION_START or @tokens[i].generated) and
              @tag(i) not in LINEBREAKS)
          backStack.push @tag(i) if @tag(i) in EXPRESSION_END
          backStack.pop() if @tag(i) in EXPRESSION_START and backStack.length
          i -= 1
        @tag(i) in tags
  • §

    トークンストリーム内の暗黙の呼び出しとオブジェクトの兆候を探し、それらを追加します。

      addImplicitBracesAndParens: ->
  • §

    スタック上の現在のバランス深度(暗黙的および明示的の両方)を追跡します。

        stack = []
        start = null
    
        @scanTokens (token, i, tokens) ->
          [tag]     = token
          [prevTag] = prevToken = if i > 0 then tokens[i - 1] else []
          [nextTag] = nextToken = if i < tokens.length - 1 then tokens[i + 1] else []
          stackTop  = -> stack[stack.length - 1]
          startIdx  = i
  • §

    新しいトークンを取得するために戻るときに、消費およびスプライスされたトークンの数を追跡するために使用されるヘルパー関数。

          forward   = (n) -> i - startIdx + n
  • §

    ヘルパー関数

          isImplicit        = (stackItem) -> stackItem?[2]?.ours
          isImplicitObject  = (stackItem) -> isImplicit(stackItem) and stackItem?[0] is '{'
          isImplicitCall    = (stackItem) -> isImplicit(stackItem) and stackItem?[0] is '('
          inImplicit        = -> isImplicit stackTop()
          inImplicitCall    = -> isImplicitCall stackTop()
          inImplicitObject  = -> isImplicitObject stackTop()
  • §

    暗黙の括弧内(クラス宣言やif条件など)で閉じられていない制御ステートメント。

          inImplicitControl = -> inImplicit() and stackTop()?[0] is 'CONTROL'
    
          startImplicitCall = (idx) ->
            stack.push ['(', idx, ours: yes]
            tokens.splice idx, 0, generate 'CALL_START', '(', ['', 'implicit function call', token[2]], prevToken
    
          endImplicitCall = ->
            stack.pop()
            tokens.splice i, 0, generate 'CALL_END', ')', ['', 'end of input', token[2]], prevToken
            i += 1
    
          startImplicitObject = (idx, {startsLine = yes, continuationLineIndent} = {}) ->
            stack.push ['{', idx, sameLine: yes, startsLine: startsLine, ours: yes, continuationLineIndent: continuationLineIndent]
            val = new String '{'
            val.generated = yes
            tokens.splice idx, 0, generate '{', val, token, prevToken
    
          endImplicitObject = (j) ->
            j = j ? i
            stack.pop()
            tokens.splice j, 0, generate '}', '}', token, prevToken
            i += 1
    
          implicitObjectContinues = (j) =>
            nextTerminatorIdx = null
            @detectEnd j,
              (token) -> token[0] is 'TERMINATOR'
              (token, i) -> nextTerminatorIdx = i
              returnOnNegativeLevel: yes
            return no unless nextTerminatorIdx?
            @looksObjectish nextTerminatorIdx + 1
  • §

    これらのいずれかが引数/値にある場合、次のインデントで暗黙の呼び出し/オブジェクトを終了しないでください。

          if (
            (inImplicitCall() or inImplicitObject()) and tag in CONTROL_IN_IMPLICIT or
            inImplicitObject() and prevTag is ':' and tag is 'FOR'
          )
            stack.push ['CONTROL', i, ours: yes]
            return forward(1)
    
          if tag is 'INDENT' and inImplicit()
  • §

    INDENTは、以下の場合を除いて、暗黙の呼び出しを閉じます

    1. 行にCONTROL引数が表示されています。
    2. インデントの前の最後のトークンは、以下のリストの一部です。
            if prevTag not in ['=>', '->', '[', '(', ',', '{', 'ELSE', '=']
              while inImplicitCall() or inImplicitObject() and prevTag isnt ':'
                if inImplicitCall()
                  endImplicitCall()
                else
                  endImplicitObject()
            stack.pop() if inImplicitControl()
            stack.push [tag, i]
            return forward(1)
  • §

    明示的な式の簡単な開始。

          if tag in EXPRESSION_START
            stack.push [tag, i]
            return forward(1)
  • §

    明示的に閉じられた式内のすべての暗黙の式を閉じます。

          if tag in EXPRESSION_END
            while inImplicit()
              if inImplicitCall()
                endImplicitCall()
              else if inImplicitObject()
                endImplicitObject()
              else
                stack.pop()
            start = stack.pop()
    
          inControlFlow = =>
            seenFor = @findTagsBackwards(i, ['FOR']) and @findTagsBackwards(i, ['FORIN', 'FOROF', 'FORFROM'])
            controlFlow = seenFor or @findTagsBackwards i, ['WHILE', 'UNTIL', 'LOOP', 'LEADING_WHEN']
            return no unless controlFlow
            isFunc = no
            tagCurrentLine = token[2].first_line
            @detectEnd i,
              (token, i) -> token[0] in LINEBREAKS
              (token, i) ->
                [prevTag, ,{first_line}] = tokens[i - 1] || []
                isFunc = tagCurrentLine is first_line and prevTag in ['->', '=>']
              returnOnNegativeLevel: yes
            isFunc
  • §

    f a、f()b、f?c、h [0] dなどの標準の暗黙の呼び出しを認識します。 左側のスプレッドドットのサポートが追加されました:f…a

          if (tag in IMPLICIT_FUNC and token.spaced or
              tag is '?' and i > 0 and not tokens[i - 1].spaced) and
             (nextTag in IMPLICIT_CALL or
             (nextTag is '...' and @tag(i + 2) in IMPLICIT_CALL and not @findTagsBackwards(i, ['INDEX_START', '['])) or
              nextTag in IMPLICIT_UNSPACED_CALL and
              not nextToken.spaced and not nextToken.newLine) and
              not inControlFlow()
            tag = token[0] = 'FUNC_EXIST' if tag is '?'
            startImplicitCall i + 1
            return forward(2)
  • §

    最初の引数として暗黙のインデントオブジェクトを取る暗黙の呼び出し。

    f
      a: b
      c: d
    

    以下の制御構造と同じ行にある場合、このタイプの暗黙の呼び出しを受け入れないでください。これは、次のような構造を誤って解釈する可能性があるためです。

    if f
       a: 1
    

    として

    if f(a: 1)
    

    これはおそらく常に意図しないものです。 さらに、リテラル配列または明示的なオブジェクトの最初の行でこれを許可しないでください。文法的なあいまいさが生じるためです(#5368)。

          if tag in IMPLICIT_FUNC and
             @indexOfTag(i + 1, 'INDENT') > -1 and @looksObjectish(i + 2) and
             not @findTagsBackwards(i, ['CLASS', 'EXTENDS', 'IF', 'CATCH',
              'SWITCH', 'LEADING_WHEN', 'FOR', 'WHILE', 'UNTIL']) and
             not ((s = stackTop()?[0]) in ['{', '['] and
                  not isImplicit(stackTop()) and
                  @findTagsBackwards(i, s))
            startImplicitCall i + 1
            stack.push ['INDENT', i + 2]
            return forward(3)
  • §

    暗黙のオブジェクトはここから始まります。

          if tag is ':'
  • §

    オブジェクトの(暗黙の)開始位置に戻ります。

            s = switch
              when @tag(i - 1) in EXPRESSION_END
                [startTag, startIndex] = start
                if startTag is '[' and startIndex > 0 and @tag(startIndex - 1) is '@' and not tokens[startIndex - 1].spaced
                  startIndex - 1
                else
                  startIndex
              when @tag(i - 2) is '@' then i - 2
              else i - 1
    
            startsLine = s <= 0 or @tag(s - 1) in LINEBREAKS or tokens[s - 1].newLine
  • §

    すでに宣言されているオブジェクトを継続しているだけですか? 明示的な「{」の後の行にインデントする場合を含みます。

            if stackTop()
              [stackTag, stackIdx] = stackTop()
              stackNext = stack[stack.length - 2]
              if (stackTag is '{' or
                  stackTag is 'INDENT' and stackNext?[0] is '{' and
                  not isImplicit(stackNext) and
                  @findTagsBackwards(stackIdx-1, ['{'])) and
                 (startsLine or @tag(s - 1) is ',' or @tag(s - 1) is '{') and
                 @tag(s - 1) not in UNFINISHED
                return forward(1)
    
            preObjectToken = if i > 1 then tokens[i - 2] else []
            startImplicitObject(s, {startsLine: !!startsLine, continuationLineIndent: preObjectToken.continuationLineIndent})
            return forward(2)
  • §

    たとえば、次のようにメソッド呼び出しを連鎖する場合、暗黙の呼び出しを終了します。

    f ->
      a
    .g b, ->
      c
    .h a
    

    そしてまた

    f a
    .g b
    .h a
    
  • §

    すべての囲みオブジェクトをsameLineではないとマークします

          if tag in LINEBREAKS
            for stackItem in stack by -1
              break unless isImplicit stackItem
              stackItem[2].sameLine = no if isImplicitObject stackItem
  • §

    インデントが終了したら、インデントされた継続行の暗黙のオブジェクトを終了します。

          if tag is 'TERMINATOR' and token.endsContinuationLineIndentation
            {preContinuationLineIndent} = token.endsContinuationLineIndentation
            while inImplicitObject() and (implicitObjectIndent = stackTop()[2].continuationLineIndent)? and implicitObjectIndent > preContinuationLineIndent
              endImplicitObject()
    
          newLine = prevTag is 'OUTDENT' or prevToken.newLine
          if tag in IMPLICIT_END or
              (tag in CALL_CLOSERS and newLine) or
              (tag in ['..', '...'] and @findTagsBackwards(i, ["INDEX_START"]))
            while inImplicit()
              [stackTag, stackIdx, {sameLine, startsLine}] = stackTop()
  • §

    引数リストの最後に達したら、暗黙の呼び出しを閉じます

              if inImplicitCall() and prevTag isnt ',' or
                  (prevTag is ',' and tag is 'TERMINATOR' and not nextTag?)
                endImplicitCall()
  • §

    次のような暗黙のオブジェクトを閉じます。trueでない限り、a:1、b:2を返します

              else if inImplicitObject() and sameLine and
                      tag isnt 'TERMINATOR' and prevTag isnt ':' and
                      not (tag in ['POST_IF', 'FOR', 'WHILE', 'UNTIL'] and startsLine and implicitObjectContinues(i + 1))
                endImplicitObject()
  • §

    行の終わりに達し、行がコンマで終わらず、暗黙のオブジェクトが行を開始せず、次の行がオブジェクトの継続のように見えない場合、暗黙のオブジェクトを閉じます。

              else if inImplicitObject() and tag is 'TERMINATOR' and prevTag isnt ',' and
                      not (startsLine and @looksObjectish(i + 1))
                endImplicitObject()
              else if inImplicitControl() and tokens[stackTop()[1]][0] is 'CLASS' and tag is 'TERMINATOR'
                stack.pop()
              else
                break
  • §

    コンマが最後の文字であり、後続の文字が属していないように見える場合、暗黙のオブジェクトを閉じます。 これは、次のような末尾のコンマと呼び出しに使用されます。

    x =
        a: b,
        c: d,
    e = 2
    

    そして

    f a, b: c, d: e, f, g: h: i, j
    
          if tag is ',' and not @looksObjectish(i + 1) and inImplicitObject() and not (@tag(i + 2) in ['FOROF', 'FORIN']) and
             (nextTag isnt 'TERMINATOR' or not @looksObjectish(i + 2))
  • §

    nextTagがOUTDENTの場合、コンマは重要ではなく、無視する必要があるため、暗黙のオブジェクトに埋め込みます。

    そうでない場合は、コンマはスタックの上位にある呼び出しまたは配列で役割を果たすため、チャンスを与えます。

            offset = if nextTag is 'OUTDENT' then 1 else 0
            while inImplicitObject()
              endImplicitObject i + offset
          return forward(1)
  • §

    JSX属性では文字列とラップされた式のみが使用されるようにします。

      enforceValidJSXAttributes: ->
        @scanTokens (token, i, tokens) ->
          if token.jsxColon
            next = tokens[i + 1]
            if next[0] not in ['STRING_START', 'STRING', '(']
              throwSyntaxError 'expected wrapped or quoted JSX attribute', next[2]
          return 1
  • §

    すべてのトークンがパーサーによる処理を生き残るわけではありません。 コメントが失われないように、運命づけられたトークンに添付されたコメントを見つけ、反対側に到達するトークンに移動します。

      rescueStowawayComments: ->
        insertPlaceholder = (token, j, tokens, method) ->
          tokens[method] generate 'TERMINATOR', '\n', tokens[j] unless tokens[j][0] is 'TERMINATOR'
          tokens[method] generate 'JS', '', tokens[j], token
    
        dontShiftForward = (i, tokens) ->
          j = i + 1
          while j isnt tokens.length and tokens[j][0] in DISCARDED
            return yes if tokens[j][0] is 'INTERPOLATION_END'
            j++
          no
    
        shiftCommentsForward = (token, i, tokens) ->
  • §

    次に生き残るトークンを見つけ、このトークンのコメントをそれに添付します。そのトークン自体のコンパイルの*前*にそのようなコメントを出力することを知っているフラグを付けます。 (そうでない場合、コメントは添付されているトークンの後に続きます。)

          j = i
          j++ while j isnt tokens.length and tokens[j][0] in DISCARDED
          unless j is tokens.length or tokens[j][0] in DISCARDED
            comment.unshift = yes for comment in token.comments
            moveComments token, tokens[j]
            return 1
          else # All following tokens are doomed!
            j = tokens.length - 1
            insertPlaceholder token, j, tokens, 'push'
  • §

    生成されたトークンはインラインではなく最後に追加されたため、スキップしません。

            return 1
    
        shiftCommentsBackward = (token, i, tokens) ->
  • §

    最後に生き残ったトークンを見つけ、このトークンのコメントをそれに添付します。

          j = i
          j-- while j isnt -1 and tokens[j][0] in DISCARDED
          unless j is -1 or tokens[j][0] in DISCARDED
            moveComments token, tokens[j]
            return 1
          else # All previous tokens are doomed!
            insertPlaceholder token, 0, tokens, 'unshift'
  • §

    2つのトークンを追加したため、挿入を考慮して前にシフトします。

            return 3
    
        @scanTokens (token, i, tokens) ->
          return 1 unless token.comments
          ret = 1
          if token[0] in DISCARDED
  • §

    このトークンはパーサーを通過しても生き残れないため、添付されたトークンを救出して近くのトークンに再配布する必要があります。 新しい行を開始しないコメントは、最後の安全なトークンに後方にシフトできますが、他のトークンは前方にシフトする必要があります。

            dummyToken = comments: []
            j = token.comments.length - 1
            until j is -1
              if token.comments[j].newLine is no and token.comments[j].here is no
                dummyToken.comments.unshift token.comments[j]
                token.comments.splice j, 1
              j--
            if dummyToken.comments.length isnt 0
              ret = shiftCommentsBackward dummyToken, i - 1, tokens
            if token.comments.length isnt 0
              shiftCommentsForward token, i, tokens
          else unless dontShiftForward i, tokens
  • §

    このトークンのコメントのいずれかが行を開始する場合(前の改行とコメントの開始の間に空白のみが存在する場合)、これは特別なJSトークンの1つではないため、このコメントを前にシフトして次の有効なトークン。 Block.compileCommentsにも、コメントが添付されているトークンに関連して「新しい行を開始する」コメントが最も近い改行の前後に続くようにするロジックがありますが、その改行は}または)またはこのコメントを実際に出力したい他の生成されたトークンの中にあります。 したがって、ここでコメントをシフトし、そのような生成および破棄されたトークンを回避する必要があります。

            dummyToken = comments: []
            j = token.comments.length - 1
            until j is -1
              if token.comments[j].newLine and not token.comments[j].unshift and
                 not (token[0] is 'JS' and token.generated)
                dummyToken.comments.unshift token.comments[j]
                token.comments.splice j, 1
              j--
            if dummyToken.comments.length isnt 0
              ret = shiftCommentsForward dummyToken, i + 1, tokens
          delete token.comments if token.comments?.length is 0
          ret
  • §

    リライターによって生成されたすべてのトークンに位置データを追加します。

      addLocationDataToGeneratedTokens: ->
        @scanTokens (token, i, tokens) ->
          return 1 if     token[2]
          return 1 unless token.generated or token.explicit
          if token.fromThen and token[0] is 'INDENT'
            token[2] = token.origin[2]
            return 1
          if token[0] is '{' and nextLocation=tokens[i + 1]?[2]
            {first_line: line, first_column: column, range: [rangeIndex]} = nextLocation
          else if prevLocation = tokens[i - 1]?[2]
            {last_line: line, last_column: column, range: [, rangeIndex]} = prevLocation
            column += 1
          else
            line = column = 0
            rangeIndex = 0
          token[2] = {
            first_line:            line
            first_column:          column
            last_line:             line
            last_column:           column
            last_line_exclusive:   line
            last_column_exclusive: column
            range: [rangeIndex, rangeIndex]
          }
          return 1
  • §

    OUTDENTトークンは常に前のトークンの最後の文字に配置する必要があります。そのため、OUTDENTトークンで終わるASTノードは、ノードの下の最後の「実際の」トークンに対応する場所に配置されます。

      fixIndentationLocationData: ->
        @allComments ?= extractAllCommentTokens @tokens
        findPrecedingComment = (token, {afterPosition, indentSize, first, indented}) =>
          tokenStart = token[2].range[0]
          matches = (comment) ->
            if comment.outdented
              return no unless indentSize? and comment.indentSize > indentSize
            return no if indented and not comment.indented
            return no unless comment.locationData.range[0] < tokenStart
            return no unless comment.locationData.range[0] > afterPosition
            yes
          if first
            lastMatching = null
            for comment in @allComments by -1
              if matches comment
                lastMatching = comment
              else if lastMatching
                return lastMatching
            return lastMatching
          for comment in @allComments when matches comment by -1
            return comment
          null
    
        @scanTokens (token, i, tokens) ->
          return 1 unless token[0] in ['INDENT', 'OUTDENT'] or
            (token.generated and token[0] is 'CALL_END' and not token.data?.closingTagNameToken) or
            (token.generated and token[0] is '}')
          isIndent = token[0] is 'INDENT'
          prevToken = token.prevToken ? tokens[i - 1]
          prevLocationData = prevToken[2]
  • §

    addLocationDataToGeneratedTokens()は、outdentの位置データを前のトークンの位置データに設定しましたが、空の「ブロック」内のコメントを検出するために、次のトークンの前にあるコメントを探したいと考えています。

          useNextToken = token.explicit or token.generated
          if useNextToken
            nextToken = token
            nextTokenIndex = i
            nextToken = tokens[nextTokenIndex++] while (nextToken.explicit or nextToken.generated) and nextTokenIndex isnt tokens.length - 1
          precedingComment = findPrecedingComment(
            if useNextToken
              nextToken
            else
              token
            afterPosition: prevLocationData.range[0]
            indentSize: token.indentSize
            first: isIndent
            indented: useNextToken
          )
          if isIndent
            return 1 unless precedingComment?.newLine
  • §

    たとえば、if条件の最後にある暗黙の呼び出しに、後続のインデントされたコメントを含めたくありません。

          return 1 if token.generated and token[0] is 'CALL_END' and precedingComment?.indented
          prevLocationData = precedingComment.locationData if precedingComment?
          token[2] =
            first_line:
              if precedingComment?
                prevLocationData.first_line
              else
                prevLocationData.last_line
            first_column:
              if precedingComment?
                if isIndent
                  0
                else
                  prevLocationData.first_column
              else
                prevLocationData.last_column
            last_line:              prevLocationData.last_line
            last_column:            prevLocationData.last_column
            last_line_exclusive:    prevLocationData.last_line_exclusive
            last_column_exclusive:  prevLocationData.last_column_exclusive
            range:
              if isIndent and precedingComment?
                [
                  prevLocationData.range[0] - precedingComment.indentSize
                  prevLocationData.range[1]
                ]
              else
                prevLocationData.range
          return 1
  • §

    私たちの文法はLALR(1)であるため、終了区切り文字のない単一行式を処理できません。 Rewriterは暗黙のブロックを追加するため、必要ありません。 文法をきれいに整頓するために、式内の末尾の改行が削除され、空のブロックのインデントトークンが追加されます。

      normalizeLines: ->
        starter = indent = outdent = null
        leading_switch_when = null
        leading_if_then = null
  • §

    THENタグを数えます

        ifThens = []
    
        condition = (token, i) ->
          token[1] isnt ';' and token[0] in SINGLE_CLOSERS and
          not (token[0] is 'TERMINATOR' and @tag(i + 1) in EXPRESSION_CLOSE) and
          not (token[0] is 'ELSE' and
               (starter isnt 'THEN' or (leading_if_then or leading_switch_when))) and
          not (token[0] in ['CATCH', 'FINALLY'] and starter in ['->', '=>']) or
          token[0] in CALL_CLOSERS and
          (@tokens[i - 1].newLine or @tokens[i - 1][0] is 'OUTDENT')
    
        action = (token, i) ->
          ifThens.pop() if token[0] is 'ELSE' and starter is 'THEN'
          @tokens.splice (if @tag(i - 1) is ',' then i - 1 else i), 0, outdent
    
        closeElseTag = (tokens, i) =>
          tlen = ifThens.length
          return i unless tlen > 0
          lastThen = ifThens.pop()
          [, outdentElse] = @indentation tokens[lastThen]
  • §

    内部IFを閉じるためにOUTDENTを挿入します。

          outdentElse[1] = tlen*2
          tokens.splice(i, 0, outdentElse)
  • §

    外部IFを閉じるためにOUTDENTを挿入します。

          outdentElse[1] = 2
          tokens.splice(i + 1, 0, outdentElse)
  • §

    末尾からoutdentを削除します。

          @detectEnd i + 2,
            (token, i) -> token[0] in ['OUTDENT', 'TERMINATOR']
            (token, i) ->
                if @tag(i) is 'OUTDENT' and @tag(i + 1) is 'OUTDENT'
                  tokens.splice i, 2
          i + 2
    
        @scanTokens (token, i, tokens) ->
          [tag] = token
          conditionTag = tag in ['->', '=>'] and
            @findTagsBackwards(i, ['IF', 'WHILE', 'FOR', 'UNTIL', 'SWITCH', 'WHEN', 'LEADING_WHEN', '[', 'INDEX_START']) and
            not (@findTagsBackwards i, ['THEN', '..', '...'])
    
          if tag is 'TERMINATOR'
            if @tag(i + 1) is 'ELSE' and @tag(i - 1) isnt 'OUTDENT'
              tokens.splice i, 1, @indentation()...
              return 1
            if @tag(i + 1) in EXPRESSION_CLOSE
              if token[1] is ';' and @tag(i + 1) is 'OUTDENT'
                tokens[i + 1].prevToken = token
                moveComments token, tokens[i + 1]
              tokens.splice i, 1
              return 0
          if tag is 'CATCH'
            for j in [1..2] when @tag(i + j) in ['OUTDENT', 'TERMINATOR', 'FINALLY']
              tokens.splice i + j, 0, @indentation()...
              return 2 + j
          if tag in ['->', '=>'] and (@tag(i + 1) in [',', ']'] or @tag(i + 1) is '.' and token.newLine)
            [indent, outdent] = @indentation tokens[i]
            tokens.splice i + 1, 0, indent, outdent
            return 1
          if tag in SINGLE_LINERS and @tag(i + 1) isnt 'INDENT' and
             not (tag is 'ELSE' and @tag(i + 1) is 'IF') and
             not conditionTag
            starter = tag
            [indent, outdent] = @indentation tokens[i]
            indent.fromThen   = true if starter is 'THEN'
            if tag is 'THEN'
              leading_switch_when = @findTagsBackwards(i, ['LEADING_WHEN']) and @tag(i + 1) is 'IF'
              leading_if_then = @findTagsBackwards(i, ['IF']) and @tag(i + 1) is 'IF'
            ifThens.push i if tag is 'THEN' and @findTagsBackwards(i, ['IF'])
  • §

    ELSEタグは閉じられていません。

            if tag is 'ELSE' and @tag(i - 1) isnt 'OUTDENT'
              i = closeElseTag tokens, i
            tokens.splice i + 1, 0, indent
            @detectEnd i + 2, condition, action
            tokens.splice i, 1 if tag is 'THEN'
            return 1
          return 1
  • §

    後置条件をそのようにタグ付けして、異なる優先順位で解析できるようにします。

      tagPostfixConditionals: ->
        original = null
    
        condition = (token, i) ->
          [tag] = token
          [prevTag] = @tokens[i - 1]
          tag is 'TERMINATOR' or (tag is 'INDENT' and prevTag not in SINGLE_LINERS)
    
        action = (token, i) ->
          if token[0] isnt 'INDENT' or (token.generated and not token.fromThen)
            original[0] = 'POST_' + original[0]
    
        @scanTokens (token, i) ->
          return 1 unless token[0] is 'IF'
          original = token
          @detectEnd i + 1, condition, action
          return 1
  • §

    追加データを含むトークンの場合、トークン値をString()オブジェクトとしてラップし、そのオブジェクトのプロパティとしてデータを設定することにより、そのデータを文法に表示できるようにします。 次に、文法は、ノードコンストラクターのためにこれをクリーンアップする責任があります。トークン値をプリミティブ文字列にラップ解除し、予期されるトークンデータプロパティを個別に渡します

      exposeTokenDataToGrammar: ->
        @scanTokens (token, i) ->
          if token.generated or (token.data and Object.keys(token.data).length isnt 0)
            token[1] = new String token[1]
            token[1][key] = val for own key, val of (token.data ? {})
            token[1].generated = yes if token.generated
          1
  • §

    同じ行にある別のトークンに基づいて、インデントトークンを生成します。

      indentation: (origin) ->
        indent  = ['INDENT', 2]
        outdent = ['OUTDENT', 2]
        if origin
          indent.generated = outdent.generated = yes
          indent.origin = outdent.origin = origin
        else
          indent.explicit = outdent.explicit = yes
        [indent, outdent]
    
      generate: generate
  • §

    トークンインデックスでタグを検索します。

      tag: (i) -> @tokens[i]?[0]
  • §

    定数

  • §
  • §

    バランスを取る必要があるトークンペアのリスト。

    BALANCED_PAIRS = [
      ['(', ')']
      ['[', ']']
      ['{', '}']
      ['INDENT', 'OUTDENT'],
      ['CALL_START', 'CALL_END']
      ['PARAM_START', 'PARAM_END']
      ['INDEX_START', 'INDEX_END']
      ['STRING_START', 'STRING_END']
      ['INTERPOLATION_START', 'INTERPOLATION_END']
      ['REGEX_START', 'REGEX_END']
    ]
  • §

    修正しようとしているBALANCED_PAIRSの逆マッピング。そのため、どちらの端からでも検索できます。

    exports.INVERSES = INVERSES = {}
  • §

    バランスの取れたペアの開始/終了を示すトークン。

    EXPRESSION_START = []
    EXPRESSION_END   = []
    
    for [left, right] in BALANCED_PAIRS
      EXPRESSION_START.push INVERSES[right] = left
      EXPRESSION_END  .push INVERSES[left] = right
  • §

    式の句の終わりを示すトークン。

    EXPRESSION_CLOSE = ['CATCH', 'THEN', 'ELSE', 'FINALLY'].concat EXPRESSION_END
  • §

    IMPLICIT_CALLが続く場合、関数呼び出しを示すトークン。

    IMPLICIT_FUNC    = ['IDENTIFIER', 'PROPERTY', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@', 'THIS']
  • §

    IMPLICIT_FUNCが前に付いている場合は、関数呼び出しを示します。

    IMPLICIT_CALL    = [
      'IDENTIFIER', 'JSX_TAG', 'PROPERTY', 'NUMBER', 'INFINITY', 'NAN'
      'STRING', 'STRING_START', 'REGEX', 'REGEX_START', 'JS'
      'NEW', 'PARAM_START', 'CLASS', 'IF', 'TRY', 'SWITCH', 'THIS'
      'DYNAMIC_IMPORT', 'IMPORT_META', 'NEW_TARGET'
      'UNDEFINED', 'NULL', 'BOOL'
      'UNARY', 'DO', 'DO_IIFE', 'YIELD', 'AWAIT', 'UNARY_MATH', 'SUPER', 'THROW'
      '@', '->', '=>', '[', '(', '{', '--', '++'
    ]
    
    IMPLICIT_UNSPACED_CALL = ['+', '-']
  • §

    単一行の暗黙の呼び出しの終わりを常に示すトークン。

    IMPLICIT_END     = ['POST_IF', 'FOR', 'WHILE', 'UNTIL', 'WHEN', 'BY',
      'LOOP', 'TERMINATOR']
  • §

    終了が閉じられていないブロック式の単一行フレーバー。 文法ではそれらを明確にすることができないため、暗黙のインデントを挿入します。

    SINGLE_LINERS    = ['ELSE', '->', '=>', 'TRY', 'FINALLY', 'THEN']
    SINGLE_CLOSERS   = ['TERMINATOR', 'CATCH', 'FINALLY', 'ELSE', 'OUTDENT', 'LEADING_WHEN']
  • §

    行を終了するトークン。

    LINEBREAKS       = ['TERMINATOR', 'INDENT', 'OUTDENT']
  • §

    改行の後に開いている呼び出しを閉じるトークン。

    CALL_CLOSERS     = ['.', '?.', '::', '?::']
  • §

    後続のインデントが暗黙の呼び出し/オブジェクトを終了するのを防ぐトークン

    CONTROL_IN_IMPLICIT = ['IF', 'TRY', 'FINALLY', 'CATCH', 'CLASS', 'SWITCH']
  • §

    パーサーによって飲み込まれ、コード生成につながらないトークン。 grammar.coffeeでこれらを見つけることができます。なぜなら、o関数の2番目の引数には、これらのトークンのnew呼び出しが含まれていないからです。 STRING_STARTのlocationDataはStringWithInterpolationsになるノードのlocationDataと一致するため、addDataToNodeはSTRING_STARTのトークンをそのノードに添付するため、STRING_STARTはこのリストにありません。

    DISCARDED = ['(', ')', '[', ']', '{', '}', ':', '.', '..', '...', ',', '=', '++', '--', '?',
      'AS', 'AWAIT', 'CALL_START', 'CALL_END', 'DEFAULT', 'DO', 'DO_IIFE', 'ELSE',
      'EXTENDS', 'EXPORT', 'FORIN', 'FOROF', 'FORFROM', 'IMPORT', 'INDENT', 'INDEX_SOAK',
      'INTERPOLATION_START', 'INTERPOLATION_END', 'LEADING_WHEN', 'OUTDENT', 'PARAM_END',
      'REGEX_START', 'REGEX_END', 'RETURN', 'STRING_END', 'THROW', 'UNARY', 'YIELD'
    ].concat IMPLICIT_UNSPACED_CALL.concat IMPLICIT_END.concat CALL_CLOSERS.concat CONTROL_IN_IMPLICIT
  • §

    行の終わりに表示される場合、後続のTERMINATOR / INDENTトークンを抑制するトークン

    exports.UNFINISHED = UNFINISHED = ['\\', '.', '?.', '?::', 'UNARY', 'DO', 'DO_IIFE', 'MATH', 'UNARY_MATH', '+', '-',
               '**', 'SHIFT', 'RELATION', 'COMPARE', '&', '^', '|', '&&', '||',
               'BIN?', 'EXTENDS']