如何创建一个解析器组合器,其中行结尾很重要?

mip*_*adi 16 parsing scala parser-combinators

我正在创建一个DSL,并使用Scala的解析器组合库来解析DSL.DSL遵循简单的类似Ruby的语法.源文件可以包含一系列看起来像这样的块:

create_model do
  at 0,0,0
end
Run Code Online (Sandbox Code Playgroud)

线路结尾在DSL中很重要,因为它们被有效地用作语句终止符.

我写了一个Scala解析器,看起来像这样:

class ML3D extends JavaTokenParsers {
  override val whiteSpace = """[ \t]+""".r

  def model: Parser[Any] = commandList
  def commandList: Parser[Any] = rep(commandBlock)
  def commandBlock: Parser[Any] = command~"do"~eol~statementList~"end"
  def eol: Parser[Any] = """(\r?\n)+""".r
  def command: Parser[Any] = commandName~opt(commandLabel)
  def commandName: Parser[Any] = ident
  def commandLabel: Parser[Any] = stringLiteral
  def statementList: Parser[Any] = rep(statement)
  def statement: Parser[Any] = functionName~argumentList~eol
  def functionName: Parser[Any] = ident
  def argumentList: Parser[Any] = repsep(argument, ",")
  def argument: Parser[Any] = stringLiteral | constant
  def constant: Parser[Any] = wholeNumber | floatingPointNumber
}
Run Code Online (Sandbox Code Playgroud)

由于行结尾很重要,我覆盖whiteSpace它以便它只将空格和制表符视为空格(而不是将新行视为空格,从而忽略它们).

这是有效的,除了"结束"声明commandBlock.由于我的源文件包含一个尾随的新行,因此解析器会抱怨它只是一个end但在end关键字后面有一个新行.

所以我把commandBlock定义改为:

def commandBlock: Parser[Any] = command~"do"~eol~statementList~"end"~opt(eol)
Run Code Online (Sandbox Code Playgroud)

(也就是说,我在"结束"之后添加了一个可选的新行).

但是现在,在解析源文件时,我收到以下错误:

[4.1] failure: `end' expected but `' found
Run Code Online (Sandbox Code Playgroud)

认为这是因为,在它吸收尾随的新行之后,解析器遇到一个它认为无效的空字符串,但我不确定它为什么这样做.

有关如何解决此问题的任何提示?我可能会从Scala的解析器组合库中扩展错误的解析器,因此任何关于如何使用重要的新行字符创建语言定义的建议也是受欢迎的.

Dan*_*ral 9

我在两个方面都得到了同样的错误,但我认为你误解了它.它的意思是它期待一个end,但它已经到了输入的末尾.

正在发生的原因是end作为一种陈述被阅读.现在,我确信有一个很好的解决方法,但我对Scala解析器的经验不足.似乎要走的路是将令牌解析器与扫描部分一起使用,但我无法想办法让标准令牌解析器不将换行视为空格.

所以,这是另一种选择:

import scala.util.parsing.combinator.JavaTokenParsers

class ML3D extends JavaTokenParsers {
  override val whiteSpace = """[ \t]+""".r
  def keywords: Parser[Any] = "do" | "end"
  def identifier: Parser[Any] = not(keywords)~ident

  def model: Parser[Any] = commandList
  def commandList: Parser[Any] = rep(commandBlock)
  def commandBlock: Parser[Any] = command~"do"~eol~statementList~"end"~opt(eol)
  def eol: Parser[Any] = """(\r?\n)+""".r
  def command: Parser[Any] = commandName~opt(commandLabel)
  def commandName: Parser[Any] = identifier
  def commandLabel: Parser[Any] = stringLiteral
  def statementList: Parser[Any] = rep(statement)
  def statement: Parser[Any] = functionName~argumentList~eol
  def functionName: Parser[Any] = identifier
  def argumentList: Parser[Any] = repsep(argument, ",")
  def argument: Parser[Any] = stringLiteral | constant
  def constant: Parser[Any] = wholeNumber | floatingPointNumber
}
Run Code Online (Sandbox Code Playgroud)

  • 您可以在`log(...)`中包含对出现在另一个生产的右侧的生产的任何引用,并且每当解析尝试匹配该非终端时,您将获得跟踪输出.例如,要记录匹配`model`的特定尝试,请将规则中的非终端引用替换为`log(model)`. (4认同)