使用Kotlin和better-parse创建的简单算术解析器失败

bis0qfac  于 2023-02-09  发布在  Kotlin
关注(0)|答案(1)|浏览(131)

bounty将在3小时后过期。回答此问题可获得+500声望奖励。typeduke希望引起更多人对此问题的关注。

请看下面这个我在Kotlin中使用解析器组合子库better-parse实现的解析器,它解析(应该解析,而不是解析)简单的算术表达式:

object CalcGrammar: Grammar<Int>() {
    val num by regexToken("""\d+""")
    val pls by literalToken("+")
    val min by literalToken("-")
    val mul by literalToken("*")
    val div by literalToken("/")
    val lpr by literalToken("(")
    val rpr by literalToken(")")
    val wsp by regexToken("\\s+", ignore = true)

    // expr ::= term + expr | term - expr | term
    // term ::= fact * term | fact / term | fact
    // fact ::= (expr) | -fact | int

    val fact: Parser<Int> by
        skip(lpr) and parser(::expr) and skip(rpr) or
        (skip(min) and parser(::fact) map { -it }) or
        (num map { it.text.toInt() })
    val term by
        leftAssociative(fact, mul) { a, _, b -> a * b } or
        leftAssociative(fact, div) { a, _, b -> a / b } or
        fact
    val expr by
        leftAssociative(term, pls) { a, _, b -> a + b } or
        leftAssociative(term, min) { a, _, b -> a - b } or
        term

    override val rootParser by expr
}

但是,当我解析-2 + 4 - 5 + 6时,我得到了这个ParseException

Could not parse input: UnparsedRemainder(startsWith=min@8 for "-" at 7 (1:8))

起初,我认为问题在于我没有将自递归编入expr结果式中,也就是说,代码没有准确地表示语法:

// Reference to `expr` is missing on the right-hand side...
val expr = ... leftAssociative(term, min) ...

// ...although the corresponding production defines it.
// expr ::= ... term - expr ...

但是后来我注意到作为库文档的一部分提供的official example for an arithmetic parser结果几乎是相同的afaict也省略了这一点。
如果不是这个我又做错了什么?我怎么才能让它成功呢?

uyhoqukh

uyhoqukh1#

我并不完全熟悉这个库,但是看起来您从语法到代码的翻译对于库的语法来说太字面了,而且库实际上隐式地处理了您显式编写的大部分内容,而且作为这种“易用性”的一部分,它似乎打破了看似正确的代码。
对于初学者,您会发现无论是否将or term链接到expr的末尾,将or fact链接到term的末尾,代码的行为都完全相同。
基于此,我可以得出结论,当将leftAssociative链接在一起时,这些or不能按预期工作,事实上,如果您尝试解析除法,也会遇到同样的问题。我相信正是由于这个原因,您提供的示例链接结合了加法和减法(就像乘法和除法一样)转换成一个更动态的leftAssociative调用。如果将相同的工作复制到自己的代码中,它将完美地运行:

object CalcGrammar: Grammar<Int>() {
    val num by regexToken("""\d+""")
    val pls by literalToken("+")
    val min by literalToken("-")
    val mul by literalToken("*")
    val div by literalToken("/")
    val lpr by literalToken("(")
    val rpr by literalToken(")")
    val wsp by regexToken("\\s+", ignore = true)

    // expr ::= term + expr | term - expr | term
    // term ::= fact * term | fact / term | fact
    // fact ::= (expr) | -fact | int

    val fact: Parser<Int> by
      skip(lpr) and parser(::expr) and skip(rpr) or
          (skip(min) and parser(::fact) map { -it }) or
          (num map { it.text.toInt() })

    private val term by
      leftAssociative(fact, div or mul use { type }) { a, op, b ->
          if (op == div) a / b else a * b
      }

    private val expr by
      leftAssociative(term, pls or min use { type }) { a, op, b ->
          if (op == pls) a + b else a - b
      }

    override val rootParser by expr
}

相关问题