我通常为 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?
. 所有repeat
s 都有消耗某些东西的身体。我们现在是金色的。