11

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

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

これは、使用可能な形式でインデントをキャプチャするトークナイザーを作成するための疑似コードのオフサイド ルールです。言語によって回答を制限したくありません。

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

このレイアウタは、一度に複数の NEWLINE を生成することはなく、インデントが発生しても NEWLINE を生成しません。したがって、解析ルールは非常に単純なままです。かなり良いと思いますが、それを達成するためのより良い方法があればお知らせください。

しばらくこれを使用しているうちに、DEDENT の後にとにかく改行を発行するのが良いことに気付きました。この方法では、INDENT DEDENT を式のトレーラーとして保持しながら、NEWLINE で式を区切ることができます。

4

3 に答える 3

8

私はここ数年、インデント中心のドメイン固有言語のトークナイザーとパーサーをいくつか書いてきました。私が間違っていなければ、あなたの方法は、たとえばPythonが行うことと非常に似ており、ある程度の重みがあるようです。

パーサーにヒットする前に NEWLINE NEWLINE INDENT を単に INDENT に変換することは、物事を行うための正しい方法のように思えます。パーサーで常に先を見ているのは苦痛 (IME) です。私は実際にそのステップを別のレイヤーとして実行し、最終的に 3 つのステップのプロセスになりました。最初のステップでは、レクサーとレイアウターが行うことを組み合わせて、すべての NEWLINE 先読み要素を差し引いたもの (これにより非常に単純になりました)、2 番目のステップ (これも非常に単純です) ) レイヤーが連続した NEWLINE を折り畳み、NEWLINE INDENT を単に 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 から先を見て、このスキームで INDENT が来るかどうかを確認する必要はありません。

それでも、トークナイザー/レイアウトであらゆる種類の狂ったように見える空白を許可して保持し、パーサーに何がリテラルで何がコードかを判断させることは、少し珍しい要件であることに同意すると思います! たとえば、Pythonコードを解析できるようにしたいだけなら、パーサーにそのインデントカウンターを搭載したくないことは確かです。あなたがやっているやり方は、ほぼ間違いなくあなたのアプリケーションや他の多くのアプリケーションにとって正しいアプローチです。他の誰かがこの種のことを行う最善の方法について考えているなら、私は明らかにそれらを聞きたい....

于 2008-11-03T08:59:09.043 に答える
3

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

私の解決策はそれを頭に向けることでした、そしてNEWLINESが行の終わりをマークする代わりに、私はLINEトークンを使って行の始まりをマークします。

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

line1
    line2
    line3
line4

トークンストリームを与える

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

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

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を出力することは避けてください。

于 2009-06-03T18:34:32.433 に答える
1

楽しみのための Ruby のトークナイザー:

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 個のインデントでのみ機能します。結果は のようなもの["Hello there from level 0\n", :INDENT, "This\nis level\ntwo\n", :DEDENT, "This is level0 again\n"]です。

于 2011-05-14T15:18:47.747 に答える