在Ruby中使用Parslet的缩进敏感解析器?

Rya*_*wis 10 ruby parsing indentation parslet

我试图使用Ruby中的Parslet库解析一个简单的缩进敏感语法.

以下是我尝试解析的语法示例:

level0child0
level0child1
  level1child0
  level1child1
    level2child0
  level1child2
Run Code Online (Sandbox Code Playgroud)

生成的树看起来像这样:

[
  {
    :identifier => "level0child0",
    :children => []
  },
  {
    :identifier => "level0child1",
    :children => [
      {
        :identifier => "level1child0",
        :children => []
      },
      {
        :identifier => "level1child1",
        :children => [
          {
            :identifier => "level2child0",
            :children => []
          }
        ]
      },
      {
        :identifier => "level1child2",
        :children => []
      },
    ]
  }
]
Run Code Online (Sandbox Code Playgroud)

我现在的解析器可以解析嵌套级别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
Run Code Online (Sandbox Code Playgroud)

很明显,我需要一个动态计数器,它需要3个缩进节点匹配嵌套级别3上的标识符.

如何以这种方式使用Parslet实现缩进敏感的语法分析器?可能吗?

Nig*_*rne 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
Run Code Online (Sandbox Code Playgroud)

这是我最喜欢的解决方案.

  • 你,先生,已经让我大吃一惊.我已经尝试了一段时间了.我的问题是我没有清楚地理解"动态",因为文档并没有像我想的那样深入研究这个主题.非常感谢你的回答!这个解析器可能会为我未来的许多其他人提供基础:p (2认同)