[英]how to write a custom error reporter in go target of antlr
我正在尝试将 antlr 项目从 c++ 迁移到 go。 语法和代码生成大部分已完成(基于65038949中提供的解决方案),但有一个待定项目是在 go 中编写自定义错误报告器。
我正在为这些目的寻找自定义错误报告器:
我想打印我的自定义消息,可能带有额外的信息(例如,默认错误打印机不打印的文件名)。
在每个错误上,错误报告器都会更新一个全局计数器,并且在主程序中,如果这个 error_count>0 则跳过进一步的处理。
下面是 c++ 项目中所做的:
在此 function 中定义了一条自定义消息:
string MyErrorMessage(unsigned int l, unsigned int p, string m) { stringstream s; s << "ERR: line " << l << "::" << p << " " << m; global->errors++; return s.str(); }
并且 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; }
最后,主程序将跳过进一步的处理,如下所示:
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 编写的)。
您可以根据需要实现任意数量的 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,您必须在错误侦听器中引发恐慌。 老实说,我没有得到这个设计,但是词法分析/解析代码包含在恐慌中恢复检查恐慌是否属于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
}
这个恐慌错误被捕获并传递给实现ErrorStrategy
的ErrorHandler
。 我们关心的重要的 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.