10

Ruby 内のParsletライブラリを使用して、単純なインデントに依存する構文を解析しようとしています。

以下は、解析しようとしている構文の例です。

level0child0
level0child1
  level1child0
  level1child1
    level2child0
  level1child2

結果のツリーは次のようになります。

[
  {
    :identifier => "level0child0",
    :children => []
  },
  {
    :identifier => "level0child1",
    :children => [
      {
        :identifier => "level1child0",
        :children => []
      },
      {
        :identifier => "level1child1",
        :children => [
          {
            :identifier => "level2child0",
            :children => []
          }
        ]
      },
      {
        :identifier => "level1child2",
        :children => []
      },
    ]
  }
]

現在使用しているパーサーは、ネスト レベル 0 および 1 のノードを解析できますが、それ以降は解析できません。

require 'parslet'

class IndentationSensitiveParser < Parslet::Parser

  rule(:indent) { str('  ') }
  rule(:newline) { str("\n") }
  rule(:identifier) { match['A-Za-z0-9'].repeat.as(:identifier) }

  rule(:node) { identifier >> newline >> (indent >> identifier >> newline.maybe).repeat.as(:children) }

  rule(:document) { node.repeat }

  root :document

end

require 'ap'
require 'pp'

begin
  input = DATA.read

  puts '', '----- input ----------------------------------------------------------------------', ''
  ap input

  tree = IndentationSensitiveParser.new.parse(input)

  puts '', '----- tree -----------------------------------------------------------------------', ''
  ap tree

rescue IndentationSensitiveParser::ParseFailed => failure
  puts '', '----- error ----------------------------------------------------------------------', ''
  puts failure.cause.ascii_tree
end

__END__
user
  name
  age
recipe
  name
foo
bar

3 つのインデント ノードがネスト レベル 3 の識別子と一致することを期待する動的カウンターが必要であることは明らかです。

このようにParsletを使用して、インデントに敏感な構文パーサーを実装するにはどうすればよいですか? 出来ますか?

4

2 に答える 2

14

いくつかのアプローチがあります。

  1. 各行をインデントと識別子のコレクションとして認識してドキュメントを解析し、その後変換を適用して、インデントの数に基づいて階層を再構築します。

  2. キャプチャを使用して現在のインデントを保存し、次のノードにそのインデントに加えて、子として一致するその他のものが含まれることを期待します (次の方法が思い浮かんだので、このアプローチについては詳しく説明しませんでした)。

  3. ルールは単なる方法です。したがって、'node' をメソッドとして定義できます。つまり、パラメーターを渡すことができます。(次のように)

これによりnode(depth)、 の観点から定義できますnode(depth+1)。ただし、このアプローチの問題は、nodeメソッドが文字列に一致せず、パーサーを生成することです。したがって、再帰呼び出しは決して終了しません。

dynamicこれが存在する理由です。一致を試みる時点まで解決されないパーサーを返すため、問題なく再帰できるようになりました。

次のコードを参照してください。

require 'parslet'

class IndentationSensitiveParser < Parslet::Parser

  def indent(depth)
    str('  '*depth)
  end

  rule(:newline) { str("\n") }

  rule(:identifier) { match['A-Za-z0-9'].repeat(1).as(:identifier) }

  def node(depth) 
    indent(depth) >> 
    identifier >> 
    newline.maybe >> 
    (dynamic{|s,c| node(depth+1).repeat(0)}).as(:children)
  end 

  rule(:document) { node(0).repeat }

  root :document
end

これは私のお気に入りのソリューションです。

于 2013-05-15T10:54:14.167 に答える