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

  • ¶

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

  • ¶

    生成されたトークンを作成します。これは、暗黙的な構文の使用によって存在するトークンです。

    generate = (tag, value, origin) ->
      tok = [tag, value]
      tok.generated = yes
      tok.origin = origin if origin
      tok
  • ¶

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

    exports.Rewriter = class Rewriter
  • ¶

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

      rewrite: (@tokens) ->
  • ¶

    デバッグのための便利なスニペット:console.log (@tokens内のtについて t[0] + ‘/‘ + t[1]).join ‘ ‘

        @removeLeadingNewlines()
        @closeOpenCalls()
        @closeOpenIndexes()
        @normalizeLines()
        @tagPostfixConditionals()
        @addImplicitBracesAndParens()
        @addLocationDataToGeneratedTokens()
        @fixOutdentLocationData()
        @tokens
  • ¶

    トークンストリームを書き換えます。1つ前のトークンと1つ後のトークンを見て処理します。ブロックの戻り値を使用して、ストリーム内で前進(または後退)するトークンの数を伝えることで、トークンが挿入および削除され、ストリームの長さが変化した場合でも、何も見逃さないようにします。

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

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

      removeLeadingNewlines: ->
        break for [tag], i in @tokens when tag isnt 'TERMINATOR'
        @tokens.splice 0, i if i
  • ¶

    lexerは、メソッド呼び出しの開き括弧にタグを付けました。対応する閉じ括弧と一致させます。同じ行で、アウトデントの直前に閉じられる呼び出しについて、ネストされていないアウトデントのケースもここに含まれています。

      closeOpenCalls: ->
        condition = (token, i) ->
          token[0] in [')', 'CALL_END'] or
          token[0] is 'OUTDENT' and @tag(i - 1) is ')'
    
        action = (token, i) ->
          @tokens[if token[0] is 'OUTDENT' then i - 1 else i][0] = 'CALL_END'
    
        @scanTokens (token, i) ->
          @detectEnd i + 1, condition, action if token[0] is 'CALL_START'
          1
  • ¶

    lexerは、インデックス付け操作呼び出しの開き括弧にタグを付けました。対応する閉じ括弧と一致させます。

      closeOpenIndexes: ->
        condition = (token, i) ->
          token[0] in [']', 'INDEX_END']
    
        action = (token, i) ->
          token[0] = 'INDEX_END'
    
        @scanTokens (token, i) ->
          @detectEnd i + 1, condition, action if token[0] is 'INDEX_START'
          1
  • ¶

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

      indexOfTag: (i, pattern...) ->
        fuzz = 0
        for j in [0 ... pattern.length]
          fuzz += 2 while @tag(i + j + fuzz) is 'HERECOMMENT'
          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
  • ¶

    「HERECOMMENT」をスキップして、@<x>:、<x>:、または<EXPRESSION_START><x>...<EXPRESSION_END>:のようなものの手前にある場合、「yes」を返します。

      looksObjectish: (j) ->
        return yes if @indexOfTag(j, '@', null, ':') > -1 or @indexOfTag(j, null, ':') > -1
        index = @indexOfTag(j, EXPRESSION_START)
        if index > -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] = 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 = (j) ->
            idx = j ? i
            stack.push ['(', idx, ours: yes]
            tokens.splice idx, 0, generate 'CALL_START', '(', ['', 'implicit function call', token[2]]
            i += 1 if not j?
    
          endImplicitCall = ->
            stack.pop()
            tokens.splice i, 0, generate 'CALL_END', ')', ['', 'end of input', token[2]]
            i += 1
    
          startImplicitObject = (j, startsLine = yes) ->
            idx = j ? i
            stack.push ['{', idx, sameLine: yes, startsLine: startsLine, ours: yes]
            val = new String '{'
            val.generated = yes
            tokens.splice idx, 0, generate '{', val, token
            i += 1 if not j?
    
          endImplicitObject = (j) ->
            j = j ? i
            stack.pop()
            tokens.splice j, 0, generate '}', '}', token
            i += 1
  • ¶

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

          if inImplicitCall() and tag in ['IF', 'TRY', 'FINALLY', 'CATCH',
            'CLASS', 'SWITCH']
            stack.push ['CONTROL', i, ours: yes]
            return forward(1)
    
          if tag is 'INDENT' and inImplicit()
  • ¶

    INDENTは、次の場合を除いて、暗黙的な呼び出しを閉じます。

    1. その行にCONTROL引数が見つかった場合。
    2. インデントの手前の最後のトークンが、以下のリストの一部である場合。
            if prevTag not in ['=>', '->', '[', '(', ',', '{', 'TRY', 'ELSE', '=']
              endImplicitCall() while inImplicitCall()
            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()
  • ¶

    f a、f() b、f? c、h[0] dなど、標準的な暗黙的な呼び出しを認識します。

          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 in IMPLICIT_UNSPACED_CALL and
              not tokens[i + 1]?.spaced and not tokens[i + 1]?.newLine)
            tag = token[0] = 'FUNC_EXIST' if tag is '?'
            startImplicitCall i + 1
            return forward(2)
  • ¶

    暗黙的にインデントされたオブジェクトを引数として取る暗黙的な呼び出し。

    f
      a: b
      c: d
    

    そして

    f
      1
      a: b
      b: c
    

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

    if f
       a: 1
    

    として

    if f(a: 1)
    

    これはおそらく常に意図しないものです。さらに、リテラル配列ではこれを許可しないでください。これは文法的な曖昧さを生み出すためです。

          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'])
            startImplicitCall i + 1
            stack.push ['INDENT', i + 2]
            return forward(3)
  • ¶

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

          if tag is ':'
  • ¶

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

            s = switch
              when @tag(i - 1) in EXPRESSION_END then start[1]
              when @tag(i - 2) is '@' then i - 2
              else i - 1
            s -= 2 while @tag(s - 2) is 'HERECOMMENT'
  • ¶

    値がforループであるかどうかをマークします。

            @insideForDeclaration = nextTag is 'FOR'
    
            startsLine = s is 0 or @tag(s - 1) in LINEBREAKS or tokens[s - 1].newLine
  • ¶

    既に宣言されたオブジェクトを継続しているだけですか?

            if stackTop()
              [stackTag, stackIdx] = stackTop()
              if (stackTag is '{' or stackTag is 'INDENT' and @tag(stackIdx - 1) is '{') and
                 (startsLine or @tag(s - 1) is ',' or @tag(s - 1) is '{')
                return forward(1)
    
            startImplicitObject(s, !!startsLine)
            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
    
          newLine = prevTag is 'OUTDENT' or prevToken.newLine
          if tag in IMPLICIT_END or tag in CALL_CLOSERS and newLine
            while inImplicit()
              [stackTag, stackIdx, {sameLine, startsLine}] = stackTop()
  • ¶

    引数リストの終わりに達したときに、暗黙的な呼び出しを閉じます。

              if inImplicitCall() and prevTag isnt ','
                endImplicitCall()
  • ¶

    次のような暗黙的なオブジェクトを閉じます: return a: 1, b: 2 unless true

              else if inImplicitObject() and not @insideForDeclaration and sameLine and
                      tag isnt 'TERMINATOR' and prevTag isnt ':'
                endImplicitObject()
  • ¶

    行の終わりに達し、行がコンマで終わっておらず、暗黙的なオブジェクトが行の先頭ではなく、次の行がオブジェクトの継続のように見えない場合、暗黙的なオブジェクトを閉じます。

              else if inImplicitObject() and tag is 'TERMINATOR' and prevTag isnt ',' and
                      not (startsLine and @looksObjectish(i + 1))
                return forward 1 if nextTag is 'HERECOMMENT'
                endImplicitObject()
              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 @insideForDeclaration 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)
  • ¶

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

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

    OUTDENTトークンは、常に前のトークンの最後の文字の位置に配置される必要があります。これにより、OUTDENTトークンで終わるASTノードは、ノードの下にある最後の「実際の」トークンに対応する位置を持つようになります。

      fixOutdentLocationData: ->
        @scanTokens (token, i, tokens) ->
          return 1 unless token[0] is 'OUTDENT' or
            (token.generated and token[0] is 'CALL_END') or
            (token.generated and token[0] is '}')
          prevLocationData = tokens[i - 1][2]
          token[2] =
            first_line:   prevLocationData.last_line
            first_column: prevLocationData.last_column
            last_line:    prevLocationData.last_line
            last_column:  prevLocationData.last_column
          return 1
  • ¶

    私たちの文法はLALR(1)であるため、終了デリミタがないいくつかの1行の式を処理できません。**Rewriter**は暗黙的なブロックを追加するため、処理する必要がありません。文法をクリーンで整然と保つために、式内の末尾の改行は削除され、空のブロックのインデントトークンが追加されます。

      normalizeLines: ->
        starter = indent = outdent = null
    
        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') 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) ->
          @tokens.splice (if @tag(i - 1) is ',' then i - 1 else i), 0, outdent
    
        @scanTokens (token, i, tokens) ->
          [tag] = token
          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
              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 SINGLE_LINERS and @tag(i + 1) isnt 'INDENT' and
             not (tag is 'ELSE' and @tag(i + 1) is 'IF')
            starter = tag
            [indent, outdent] = @indentation tokens[i]
            indent.fromThen   = true if starter is 'THEN'
            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
  • ¶

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

      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']
      ['REGEX_START', 'REGEX_END']
    ]
  • ¶

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

    exports.INVERSES = INVERSES = {}
  • ¶

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

    EXPRESSION_START = []
    EXPRESSION_END   = []
    
    for [left, rite] in BALANCED_PAIRS
      EXPRESSION_START.push INVERSES[rite] = left
      EXPRESSION_END  .push INVERSES[left] = rite
  • ¶

    式の句の閉じを指示するトークン。

    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', 'PROPERTY', 'NUMBER', 'INFINITY', 'NAN'
      'STRING', 'STRING_START', 'REGEX', 'REGEX_START', 'JS'
      'NEW', 'PARAM_START', 'CLASS', 'IF', 'TRY', 'SWITCH', 'THIS'
      'UNDEFINED', 'NULL', 'BOOL'
      'UNARY', 'YIELD', 'UNARY_MATH', 'SUPER', 'THROW'
      '@', '->', '=>', '[', '(', '{', '--', '++'
    ]
    
    IMPLICIT_UNSPACED_CALL = ['+', '-']
  • ¶

    1行の式の場合、常に暗黙的な呼び出しの終了を示すトークン。

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

    閉じられていない終了を持つブロック式の1行フレーバー。文法ではこれらを区別できないため、暗黙的なインデントを挿入します。

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

    行を終了するトークン。

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

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

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