gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-03 10:09:07 25 4
gpt4 key购买 nike

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

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

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

  2. 对于每个错误,错误报告器都会更新一个全局计数器,并且在主程序中如果此错误计数>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();
    }
  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;
    }
  3. 最后,主程序会像这样跳过进一步的处理:

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

如何为antlr的go target重写这些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 中添加一个新函数来传递这个参数并将这个文件名注册到一个新变量中来做到这一点,但是有没有更好的方法来做到这一点?

最佳答案

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

为了在每个步骤(词法分析、解析、遍历)中注入(inject)您自己的错误处理,您必须注入(inject)带有 panic 的错误监听器/处理程序。 Panic & recovery 非常像 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,
})
}

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

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 中给出的字段。您必须在别处寻找其他接口(interface)函数,例如 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,您必须在错误监听器中引发 panic 。老实说,我不明白这种设计,但词法分析/解析代码包含在 panic recover 中,检查 panic 是否属于 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
}

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

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 似乎并不是那么好用,使用 panics 来恢复在 GoLang 中感觉很笨拙,很容易搞砸。

关于go - 如何在 antlr 的 go target 中编写自定义错误报告器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66067549/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com