Ruby parslet:解析多行

Dan*_*yel 5 ruby lines parslet

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

rule(:line) { (match('$').absent? >> any).repeat >> match('$') }
rule(:lines) { line.repeat }
Run Code Online (Sandbox Code Playgroud)

但是,lines总是会以无限循环结束,因为match('$')它将无休止地重复以匹配字符串的结尾.

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

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

a
multiline

string^D
Run Code Online (Sandbox Code Playgroud)

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

此致,
Danyel.

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

显然,如果你想用解析器做的比用String :: split("\n")实现的更多,你将用line_body有用的东西替换:)


我快速回答了这个问题并将其搞砸了.我只是想解释我犯的错误,并告诉你如何避免这种错误.

这是我的第一个答案.

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

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

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

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

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`!
Run Code Online (Sandbox Code Playgroud)

这里看看为什么我们得到一个无限循环.当输入被消耗时,你最终得到的是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) }
Run Code Online (Sandbox Code Playgroud)

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


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

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

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