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

R71*_*R71 2 go antlr4

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

我正在寻找一个用于以下目的的自定义错误报告器:

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

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

这是 c++ 项目中所做的事情:

  1. 在此函数中定义了自定义消息:

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

    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;
    }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 最后,主程序将跳过进一步的处理,如下所示:

    parser.top_rule();
    if(global->errors > 0) {
        exit(0);
    }
    
    Run Code Online (Sandbox Code Playgroud)

如何针对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
    
    Run Code Online (Sandbox Code Playgroud)
  • 在上面的注释中,我在 antlr 代码中引入了一个新变量,并在用户代码中读取它 - 糟糕的编码风格,但有效。但我还需要做相反的事情 - antlr 错误报告器(error_listener.go:SyntaxError())需要读取具有文件名的用户代码变量并打印它。我可以通过在 antlr 中添加一个新函数来传递此参数并将此文件名注册到新变量中来实现此目的,但是有更好的方法吗?

Ste*_*ley 6

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,
    })
}
Run Code Online (Sandbox Code Playgroud)

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

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

parserErrors := &CustomErrorListener{}
parser := NewMyParser(stream)
p.removeErrorListeners()
p.AddErrorListener(parserErrors)
Run Code Online (Sandbox Code Playgroud)

当词法/解析完成时,两个数据结构都将具有在词法/解析阶段发现的语法错误。您可以使用 中给出的字段进行操作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())
        }
    }
Run Code Online (Sandbox Code Playgroud)

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
}
Run Code Online (Sandbox Code Playgroud)

这个紧急错误被捕获并传递给ErrorHandlerwhich Implements ErrorStrategy。我们关心的重要函数是Recover(). 恢复尝试从错误中恢复,消耗令牌流,直到找到预期的模式/令牌。既然我们希望这件事中止,我们可以从 中获得灵感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
}
Run Code Online (Sandbox Code Playgroud)

然后添加到解析器中

parser.SetErrorHandler(NewBetterBailErrorStrategy())
Run Code Online (Sandbox Code Playgroud)

话虽这么说,我建议只收集听众的错误,而不是费心尝试提前中止。似乎BailErrorStrategy并没有那么好用,而且在 GoLang 中使用恐慌来恢复感觉很笨重,很容易搞砸。