質問

このトリックを実行するジェネレーターを既に作成しましたが、オフサイドルールを実装するための最良の方法を知りたいです。

短い:オフサイドルールは、このコンテキストではインデントが次のように認識されることを意味します。構文要素。

インデントを使用可能な形式でキャプチャするトークナイザーを作成するための擬似コードのオフサイドルールを次に示します。言語ごとに回答を制限したくありません:

token NEWLINE
    matches r"\n\ *"
    increase line count
    pick up and store the indentation level
    remember to also record the current level of parenthesis

procedure layout tokens
    level = stack of indentation levels
    push 0 to level
    last_newline = none
    per each token
        if it is NEWLINE put it to last_newline and get next token
        if last_newline contains something
            extract new_level and parenthesis_count from last_newline
            - if newline was inside parentheses, do nothing
            - if new_level > level.top
                push new_level to level
                emit last_newline as INDENT token and clear last_newline
            - if new_level == level.top
                emit last_newline and clear last_newline
            - otherwise
                while new_level < level.top
                    pop from level
                    if new_level > level.top
                        freak out, indentation is broken.
                    emit last_newline as DEDENT token
                clear last_newline
        emit token
    while level.top != 0
        emit token as DEDENT token
        pop from level

comments are ignored before they are getting into the layouter
layouter lies between a lexer and a parser

このレイアウト機能は、一度に複数の改行を生成することはなく、インデントが来るときに改行を生成することもありません。したがって、解析ルールは非常にシンプルなままです。かなり良いと思いますが、それを達成するより良い方法があればお知らせします。

これをしばらく使用している間、DEDENTの後に、とにかく改行を発行するのがいいかもしれないことに気づきました。この方法で、INDENT DEDENTを式のトレーラーとして保持しながら、NEWLINEで式を分離できます。

役に立ちましたか?

解決

過去数年間に、インデント中心のドメイン固有のいくつかの言語用のトークナイザーとパーサーを書いてきましたが、あなたが持っているものは、それが価値のあるものであるとしても、かなり合理的に見えます。誤解しない限り、あなたの方法は、例えばPythonが行うことと非常に似ており、ある程度の重みを持たせるべきだと思われます。

NEWLINE NEWLINE INDENTをパーサーにヒットする前に単にINDENTに変換することは、間違いなく正しい方法のように思えます-パーサーでそれを常に覗き込むのは苦痛です(IME)!私は実際にそのステップを別のレイヤーとして実行しましたが、最終的には3ステップのプロセスになりました:最初はレクサーとレイアウターが行うこととすべてのNEWLINE先読みのものを組み合わせたもの(非常にシンプルになりました)、2つ目(非常にシンプルです) )レイヤーは連続した改行を折り畳み、改行を単にINDENTに変換しました(または、実際にはCOLON NEWLINE INDENTからINDENT、この場合、すべてのインデントされたブロックは常にコロンに先行しているため)。その後、パーサーはその上の3番目のステージでした。しかし、特にレキサーとレイアウターを分離したい場合、おそらくコード生成ツールを使用している場合にしたい場合、あなたが説明した方法で物事を行うことは私にとって非常に理にかなっていますたとえば、一般的な慣習のようにレクサーを作成します。

インデントルールについてもう少し柔軟にする必要があるアプリケーションが1つあり、本質的にパーサーに必要なときにそれらを適用するようにしました。たとえば、以下は特定のコンテキストで有効である必要がありました。

this line introduces an indented block of literal text:
    this line of the block is indented four spaces
  but this line is only indented two spaces

これはINDENT / DEDENTトークンではひどく動作しません。インデントの各列に対して1つのINDENTを生成し、途中で同じ数のDEDENTを生成する必要があるためです。インデントレベルは最終的には存在することになりますが、これはトークナイザーにしたいとは思われません。その場合、いくつかの異なることを試して、次の論理行のインデントの変化(正または負)を与える各NEWLINEトークンにカウンターを格納するだけになりました。 (各トークンは、保存する必要がある場合に備えて、すべての末尾の空白も保存しました。NEWLINEの場合、保存された空白にはEOL自体、間にある空白行、および後続の論理行のインデントが含まれます。)個別のINDENTまたはDEDENTトークンはありません。パーサーにそれを処理させることは、単にINDENTとDEDENTをネストするよりも少し手間がかかり、派手なパーサージェネレーターを必要とする複雑な文法で地獄だったかもしれませんが、それは私が恐れていたほど悪くはありませんでした、どちらか。繰り返しますが、パーサーがこのスキームにインデントが来るかどうかを確認するためにNEWLINEを先読みする必要はありません。

それでも、トークナイザー/レイアウトでクレイジーに見えるホワイトスペースのすべての方法を許可および保持し、パーサーにリテラルとコードを決定させることは少し珍しい要件であることに同意すると思います!たとえば、Pythonコードを解析できるようにしたいだけなら、そのインデントカウンターでパーサーを操作したくないでしょう。あなたが物事をしている方法は、ほぼ間違いなくあなたのアプリケーションと他の多くの人にとって正しいアプローチです。この種のことをするのに最善の方法について他の誰かが考えているなら、私は明らかにそれらを聞きたいです...

他のヒント

私は最近これを実験してきましたが、少なくとも私のニーズのために、インデントされたブロックの最後のステートメントであるかどうかにかかわらず、各「ステートメント」の終わりをNEWLINESでマークしたかったという結論に達しましたつまり、DEDENTの前でも改行が必要です。

私の解決策は、その頭をオンにすることであり、改行で行の終わりを示す代わりに、LINEトークンを使用して行の始まりを示します。

空の行(コメントのみの行を含む)を折りたたみ、最後の行のインデントに関する情報を含む単一のLINEトークンを発行するレクサーがあります。次に、私の前処理関数はこのトークンストリームを取得し、「間に」INDENTまたはDEDENTを追加します。インデントが変更される行。だから

line1
    line2
    line3
line4

トークンストリームを提供します

LINE "line1" INDENT LINE "line2" LINE "line3" DEDENT LINE "line4" EOF

これにより、ステートメントがネストされたインデントされたサブブロックで終わる場合でも、ステートメントの終わりを検出することを心配せずに、ステートメントの明確な文法生成を書くことができます。 p>

O'Camlで記述されたプリプロセッサのコアは次のとおりです。

  match next_token () with
      LINE indentation ->
        if indentation > !current_indentation then
          (
            Stack.push !current_indentation indentation_stack;
            current_indentation := indentation;
            INDENT
          )
        else if indentation < !current_indentation then
          (
            let prev = Stack.pop indentation_stack in
              if indentation > prev then
                (
                  current_indentation := indentation;
                  BAD_DEDENT
                )
              else
                (
                  current_indentation := prev;
                  DEDENT
                )
          )
        else (* indentation = !current_indentation *)
          let  token = remove_next_token () in
            if next_token () = EOF then
              remove_next_token ()
            else
              token
    | _ ->
        remove_next_token ()

括弧のサポートはまだ追加していませんが、これは単純な拡張機能です。ただし、ファイルの最後に迷っているLINEが出力されることはありません。

楽しみのためにルビーのトークナイザー:

def tokenize(input)
  result, prev_indent, curr_indent, line = [""], 0, 0, ""
  line_started = false

  input.each_char do |char|

    case char
    when ' '
      if line_started
        # Content already started, add it.
        line << char
      else
        # No content yet, just count.
        curr_indent += 1
      end
    when "\n"
      result.last << line + "\n"
      curr_indent, line = 0, ""
      line_started = false
    else
      # Check if we are at the first non-space character.
      unless line_started
        # Insert indent and dedent tokens if indentation changed.
        if prev_indent > curr_indent
          # 2 spaces dedentation
          ((prev_indent - curr_indent) / 2).times do
            result << :DEDENT
          end
          result << ""
        elsif prev_indent < curr_indent
          result << :INDENT
          result << ""
        end

        prev_indent = curr_indent
      end

      # Mark line as started and add char to line.
      line_started = true; line << char
    end

  end

  result
end

2スペースのインデントに対してのみ機能します。結果は [&quot; Hello there from level 0 \ n&quot ;,:INDENT、&quot; This \ nis level \ ntwo \ n&quot ;,:DEDENT、&quot; This is again0n \ n&quot;] >。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top