在Antlr4输入侦听器中,访问器函数意外返回null

时间:2019-01-22 13:12:42

标签: antlr4

我有以下Antlr4语法:

grammar CategoryExpr;
@header {
package org.example.antlr;
}
moneyTerm
    : dollars moneyTermSuffixes*
    ;

moneyTermSuffixes
   : '*' DIGITS # MoneyMult
   | '/' DIGITS # MoneyDiv
   ;

dollars : DIGITS ('.' DIGITS)? ;

DIGITS : [0-9]+ ;

ERRORCHAR : . ;

以及以下Kotlin代码:

private class MyListener : CategoryExprBaseListener() {
    override fun enterMoneyTerm(ctx: MoneyTermContext) {
        System.out.println(ctx.dollars().text.toDouble()) // ctx.dollars() unexpectedly returns null here!
    }
    override fun exitMoneyMult(ctx: CategoryExprParser.MoneyMultContext) {}
    override fun exitMoneyDiv(ctx: CategoryExprParser.MoneyDivContext) {}
    override fun exitMoneyTerm(ctx: MoneyTermContext) {
        System.out.println(ctx.dollars().text.toDouble()) // ctx.dollars() returns non-null here.
    }
}

fun testMoneyTerm() {
    val input = CharStreams.fromString("1.5")
    val lexer = CategoryExprLexer(input)
    val tokens = CommonTokenStream(lexer)
    val listener = MyListener()
    CategoryExprParser(tokens).apply {
        errorHandler = BailErrorStrategy()
        buildParseTree = false
        addParseListener(listener)
        moneyTerm()
    }
}

我的计划是使用enterMoneyTerm侦听器回调函数将MyListener的成员变量(属性)初始化为与“美元”解析规则匹配的值,然后让exitMoneyMult和exitMoneyDiv的侦听器通过乘或除来修改该变量的值与每个规则相关的DIGITS值。

但是,这种方法似乎不起作用,因为在enterMoneyTerm函数中,如果我尝试评估'ctx.dollars()',它会意外返回null。 (我似乎无法在enterMoneyTerm的调用中检索与“美元”相关的值。)

请注意,如果我在exitMoneyTerm中调用ctx.dollars()。text,则会得到正确的返回值(“ 1.5”),但是到那时,这已经太晚了,因为我需要进行乘法和除法运算从左到右,到那时,我已经从moneyTermSuffixes中递归了。

我不明白为什么会发生这种“从ctx.dollars()返回null”行为,或者如何处理。 (在调试中,我观察到MoneyTermContext对象的'children'对象设置为null,这说明了dollars()返回null的原因,但其本身无法解释。)

我想我可以列出moneyTermSuffixes所隐含的乘法和除法列表,然后在exitMoneyTerm中将其应用于事实之后,但这似乎不太优雅,如果可能的话,我宁愿避免使用它。

有人可以向我解释为什么ctx.dollars()在enterMoneyTerm中返回null,或者我可以做些什么,以便在解析moneyTermSuffixes之前收集“美元”文本的值?

编辑:为了明确起见,请注意,在较大的应用程序中,对我而言,转换为使用基于树行者的方法而不是基于侦听器的方法并不是真正的选择。我希望有一种方法可以最小化我的语法,以确保解析器已调用侦听器回调以向我提供“美元”规则匹配的完整文本值,然后才开始为moneyTermSuffixes规则发布回调。 >

1 个答案:

答案 0 :(得分:1)

使用Parser.addParseListener时,将在解析器解析时调用侦听器。在解析子表达式之前,将调用enter方法,在解析子表达式之后,将调用exit方法。

因此,这说明了为什么子代null在上下文对象中的原因:尚未对其进行解析,因此未构造任何解析树。

为避免此问题,可以在整个解析树构建完成后使用ParseTreeWalker.DEFAULT.walk而不是addParseListener来应用侦听器。