代码之家  ›  专栏  ›  技术社区  ›  mipadi

如何创建一个行结尾有意义的解析器组合器?

  •  16
  • mipadi  · 技术社区  · 14 年前

    我正在创建一个dsl,并使用scala的解析器组合库来解析dsl。dsl遵循一种简单的、类似ruby的语法。源文件可以包含一系列如下所示的块:

    create_model do
      at 0,0,0
    end
    

    行结束在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
    }
    

    因为行尾很重要,所以我重写了 whiteSpace 所以它将只将空格和制表符视为空白(而不是将新行视为空白,从而忽略它们)。

    这是有效的,除了 commandBlock . 由于我的源文件包含一个尾随的新行,解析器抱怨它只需要一个 end 但是在 结束 关键字。

    所以我改变了 命令块 的定义:

    def commandBlock: Parser[Any] = command~"do"~eol~statementList~"end"~opt(eol)
    

    (也就是说,我在“end”后面添加了一个可选的新行)。

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

    [4.1] failure: `end' expected but `' found
    

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

    有什么解决办法吗?我可能从scala的parser combinator库中扩展了错误的解析器,因此,关于如何创建具有重要新行字符的语言定义的任何建议也都是受欢迎的。

    2 回复  |  直到 11 年前
        1
  •  9
  •   Daniel C. Sobral    14 年前

    我在两个方面都有同样的错误,但我认为你误解了。它所说的是它期望 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
    }
    
        2
  •  0
  •   Randall Schulz    14 年前

    你也可以 override 这个 protected val whiteSpace (正则表达式)其默认值为 """\s+""".r 凌驾 这个 protected def handleWhiteSpace(...) 方法,如果需要的控制比使用正则表达式容易实现的多。这两个成员都在regexparser中组织,regexparers是javatokenparser的基类。