繁体   English   中英

如何在 antlr 的 go 目标中编写自定义错误报告器

[英]how to write a custom error reporter in go target of antlr

我正在尝试将 antlr 项目从 c++ 迁移到 go。 语法和代码生成大部分已完成(基于65038949中提供的解决方案),但有一个待定项目是在 go 中编写自定义错误报告器。

我正在为这些目的寻找自定义错误报告器:

  1. 我想打印我的自定义消息,可能带有额外的信息(例如,默认错误打印机不打印的文件名)。

  2. 在每个错误上,错误报告器都会更新一个全局计数器,并且在主程序中,如果这个 error_count>0 则跳过进一步的处理。

下面是 c++ 项目中所做的:

  1. 在此 function 中定义了一条自定义消息:

     string MyErrorMessage(unsigned int l, unsigned int p, string m) { stringstream s; s << "ERR: line " << l << "::" << p << " " << m; global->errors++; return s.str(); }
  2. 并且 antlr 运行时 ( ConsoleErrorListener.cpp ) 已更新为调用上述 function:

     void ConsoleErrorListener::syntaxError(IRecognizer *, Token *, size_t line, size_t charPositionInLine, const std::string &msg, std::exception_ptr) { std::cerr << MyErrorMessage(line, charPositionInLine, msg) << std::endl; }
  3. 最后,主程序将跳过进一步的处理,如下所示:

     parser.top_rule(); if(global->errors > 0) { exit(0); }

如何为antlr的go目标重写这些c++代码?

一些附加说明,在浏览了 antlr 运行时代码(来自 github.com/antlr/antlr4/runtime/Go/antlr):

  • parser.go 有一个变量“ _SyntaxErrors ”,每次错误都会增加,但似乎没有人使用它。 这个变量的用途是什么,解析后如何使用它来检查是否出现任何错误? 我做了以下,但显然没有奏效,(一种解决方法是在解析器中添加一个新变量 MyErrorCount,并在 _SyntaxErrors 也增加时增加它,但这看起来不是一个优雅的解决方案,因为我在这里编辑运行时代码!)

     tree:= parser.Top_rule() // this is ok fmt.Printf("errors=%d\n", parser._SyntaxErrors) // this gives a compiler error //fmt.Printf("errors=%d\n", parser.MyErrorCount) // this is ok
  • 在上面的注释中,我在 antlr 代码中引入了一个新变量,并在用户代码中读取它 - 糟糕的编码风格,但有效。 但我还需要做相反的事情——antlr 错误报告器( error_listener.go:SyntaxError() )需要读取具有文件名的用户代码变量并打印它。 我可以通过在 antlr 中添加一个新的 function 来传递这个参数并将这个文件名注册到一个新的变量中,但是有没有更好的方法来做到这一点?

Antlr 很棒,但是,需要注意的一点是它在错误处理方面不是惯用的 Go。 这使得整个错误过程对于 GoLang 工程师来说是不直观的。

为了在每个步骤(词法分析、解析、遍历)注入您自己的错误处理,您必须注入带有恐慌的错误侦听器/处理程序。 恐慌和恢复非常像 Java 异常,我认为这就是它设计成这种方式的原因(Antlr 是用 Java 编写的)。

Lex/Parse 错误收集(容易做到)

您可以根据需要实现任意数量的 ErrorListener。 默认使用的是ConsoleErrorListenerInstance 它所做的只是在SyntaxErrors上打印到 stderr,因此我们将其删除。 自定义错误报告的第一步是替换它。 我做了一个基本的,只是收集自定义类型的错误,我以后可以使用/报告。

type CustomSyntaxError struct {
    line, column int
    msg          string
}

type CustomErrorListener struct {
    *antlr.DefaultErrorListener // Embed default which ensures we fit the interface
    Errors []error
}

func (c *CustomErrorListener) SyntaxError(recognizer antlr.Recognizer, offendingSymbol interface{}, line, column int, msg string, e antlr.RecognitionException) {
    c.Errors = append(c.Errors, &CustomSyntaxError{
        line:   line,
        column: column,
        msg:    msg,
    })
}

您可以在解析器/词法分析器上注入错误侦听器(同时清除默认侦听器)。

lexerErrors := &CustomErrorListener{}
lexer := NewMyLexer(is)
lexer.RemoveErrorListeners()
lexer.AddErrorListener(lexerErrors)

parserErrors := &CustomErrorListener{}
parser := NewMyParser(stream)
p.removeErrorListeners()
p.AddErrorListener(parserErrors)

当 Lexing/Parsing 完成时,两种数据结构都会出现在 Lexing/Parsing 阶段发现的语法错误。 您可以使用SyntaxError中给出的字段。 您必须在别处寻找其他接口功能,例如ReportAmbuiguity

    if len(lexerErrors.Errors) > 0 {
        fmt.Printf("Lexer %d errors found\n", len(lexerErrors.Errors))
        for _, e := range lexerErrors.Errors {
            fmt.Println("\t", e.Error())
        }
    }

    if len(parserErrors.Errors) > 0 {
        fmt.Printf("Parser %d errors found\n", len(parserErrors.Errors))
        for _, e := range parserErrors.Errors {
            fmt.Println("\t", e.Error())
        }
    }

Lex/Parse 错误中止(不确定这有多可靠)

警告:这真的感觉很卡。 如果只需要错误收集,只需执行上面显示的操作!

要在中途中止 lex/parse,您必须在错误侦听器中引发恐慌。 老实说,我没有得到这个设计,但是词法分析/解析代码包含在恐慌中恢复检查恐慌是否属于RecognitionException类型。 此异常作为参数传递给您的ErrorListener ,因此请修改SyntaxError表达式

func (c *CustomErrorListener) SyntaxError(recognizer antlr.Recognizer, offendingSymbol interface{}, line, column int, msg string, e antlr.RecognitionException) {
  // ...
  panic(e) // Feel free to only panic on certain conditions. This stops parsing/lexing
}

这个恐慌错误被捕获并传递给实现ErrorStrategyErrorHandler 我们关心的重要的 function 是Recover() Recover 尝试从错误中恢复,使用令牌 stream 直到可以找到预期的模式/令牌。 由于我们希望中止它,我们可以从BailErrorStrategy中获取灵感。 这种策略仍然很糟糕,因为它使用恐慌来停止所有工作。 您可以简单地省略实现。

type BetterBailErrorStrategy struct {
    *antlr.DefaultErrorStrategy
}

var _ antlr.ErrorStrategy = &BetterBailErrorStrategy{}

func NewBetterBailErrorStrategy() *BetterBailErrorStrategy {

    b := new(BetterBailErrorStrategy)

    b.DefaultErrorStrategy = antlr.NewDefaultErrorStrategy()

    return b
}

func (b *BetterBailErrorStrategy) ReportError(recognizer antlr.Parser, e antlr.RecognitionException) {
    // pass, do nothing
}


func (b *BetterBailErrorStrategy) Recover(recognizer antlr.Parser, e antlr.RecognitionException) {
    // pass, do nothing
}

// Make sure we don't attempt to recover from problems in subrules.//
func (b *BetterBailErrorStrategy) Sync(recognizer antlr.Parser) {
    // pass, do nothing
}

然后添加到解析器

parser.SetErrorHandler(NewBetterBailErrorStrategy())

话虽如此,我建议只与听众一起收集错误,而不是试图提前中止。 BailErrorStrategy似乎并没有那么好用,而且在 GoLang 中使用恐慌来恢复感觉很笨拙,很容易搞砸。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM