5

我正在寻找一种匹配多行 Parslet 的方法。代码如下所示:

rule(:line) { (match('$').absent? >> any).repeat >> match('$') }
rule(:lines) { line.repeat }

但是,lines将始终以无限循环结束,这是因为match('$')将无休止地重复以匹配字符串的结尾。

是否可以匹配多个可以为空的行?

irb(main)> lines.parse($stdin.read)
This
is

a
multiline

string^D

应该匹配成功。我错过了什么吗?我也尝试过(match('$').absent? >> any.maybe).repeat(1) >> match('$'),但这与空行不匹配。

问候,
丹尼尔。

4

2 回答 2

6

我通常为 end_of_line 定义一个规则。这是基于http://kschiess.github.io/parslet/tricks.html中匹配 end_of_file 的技巧。

class MyParser < Parslet::Parser
  rule(:cr)         { str("\n") }
  rule(:eol?)       { any.absent? | cr }
  rule(:line_body)  { (eol?.absent? >> any).repeat(1) }
  rule(:line)       { cr | line_body >> eol? }
  rule(:lines?)     { line.repeat (0)}
  root(:lines?)
end

puts MyParser.new.parse(""" this is a line
so is this

that was too
This ends""").inspect

显然,如果你想用解析器做的比用 String::split("\n") 做的更多,你会line_body用一些有用的东西代替 :)


我快速回答了这个问题并将其搞砸了。我只是想解释一下我所犯的错误,并向您展示如何避免此类错误。

这是我的第一个答案。

rule(:eol)   { str('\n') | any.absent?  }
rule(:line)  { (eol.absent? >> any).repeat >> eol }
rule(:lines) { line.as(:line).repeat }

我没有遵循我通常的规则:

  • 始终明确重复计数
  • 任何可以匹配零长度字符串的规则的名称都应该以“?”结尾

所以让我们应用这些......

rule(:eol?)   { str('\n') | any.absent?  } 
# as the second option consumes nothing

rule(:line?)  { (eol.absent? >> any).repeat(0) >> eol? } 
# repeat(0) can consume nothing

rule(:lines?) { line.as(:line?).repeat(0) }
# We have a problem! We have a rule that can consume nothing inside a `repeat`!

在这里看看为什么我们会得到一个无限循环。随着输入的消耗,您最终只得到end of file匹配的eol?, 因此line?(因为行体可以为空)。在lines'内部repeat,它会一直匹配而不消耗任何东西并永远循环。

我们需要改变线路规则,让它总是消耗一些东西。

rule(:cr)         { str('\n') }
rule(:eol?)       { cr | any.absent?  }
rule(:line_body)  { (eol.absent? >> any).repeat(1) }
rule(:line)       { cr | line_body >> eol? }
rule(:lines?)     { line.as(:line).repeat(0) }

现在line必须匹配一些东西,要么是cr(对于空行),要么是至少一个字符,后跟可选的eol?. 所有repeats 都有消耗某些东西的身体。我们现在是金色的。

于 2013-07-24T00:11:35.227 回答
3

我认为您的匹配有两个相关的问题:

  • 伪字符匹配$不消耗任何真实字符。您仍然需要以某种方式使用换行符。

  • Parslet 以某种方式修改输入,$在您可能没想到的地方进行匹配。我可以使用的最佳结果$最终匹配每个单独的字符。

\n用作行尾字符更安全。我得到了以下工作(我自己有点像 Parslet 的初学者,如果它更清楚,请道歉):

require 'parslet'

class Lines < Parslet::Parser
    rule(:text) { match("[^\n]") }
    rule(:line) { ( text.repeat(0) >> match("\n") ) | text.repeat(1) }
    rule(:lines) { line.as(:line).repeat }
    root :lines
end

s = "This
is

a
multiline
string"

p Lines.new.parse( s )

该行的规则很复杂,因为需要匹配空行和没有\n.

您不必使用.as(:line)语法 - 我只是添加它以清楚地表明该:line规则单独匹配每一行,而不是简单地消耗整个输入。

于 2013-07-18T20:19:43.777 回答