ANTLR4 - VisitChildren returns null,即使 child returns 一些 object
ANTLR4 - VisitChildren returns null, even when child returns some object
我一直在尝试实现访问者模式以将一些特定的 SQL 语句解析为由 TableDefinition 和 ColumnDefinition object 组成的内部 object 结构。
这是语法中的一小部分(精简):
column_definition
: column_name datatype? column_constraint*
;
column_constraint
: ( K_CONSTRAINT name )?
( K_PRIMARY K_KEY ( K_CLUSTERED | K_NONCLUSTERED )? ( K_ASC | K_DESC )? K_AUTOINCREMENT?
| K_AUTOINCREMENT
| K_NOT? K_NULL
)
;
datatype
: K_CHAR ( '(' unsigned_integer ')' )? #char
| K_DATE #date
;
这是派生的 BaseVisitors 之一,意在 return ColumnDefinitions:
namespace SqlParser.Visitor
{
public class DataTypeVisitor: SqlAnywhereParserBaseVisitor<ColumnDefinition>
{
public override ColumnDefinition VisitColumn_definition([NotNull] SqlAnywhereParser.Column_definitionContext context)
{
var res = VisitChildren(context);
var constraint = (SqlAnywhereParser.Column_constraintContext[])context.column_constraint();
if (res != null) // Add NULL attributes
{
if (constraint.Any(c => c.K_NULL() != null && c.K_NOT() == null))
res.IsNullable = true;
if (constraint.Any(c => c.K_NULL() != null && c.K_NOT() != null))
res.IsNullable = false;
}
return res;
}
public override ColumnDefinition VisitChar([NotNull] SqlAnywhereParser.CharContext context)
{
return new ColumnDefinition()
{
DataType = DbType.StringFixedLength,
Length = int.Parse(context.unsigned_integer()?.GetText() ?? "1")
};
}
}
}
当我调试进程时,我可以观察到对 VisitChildren 的调用如何进入 VisitChar,其中 return 是一个 ColumnDefinition object。当 VisitChar 完成并且光标跳回以在 VisitColumn_definition 中继续时,变量 res 为空。
我是不是漏掉了一些重要的东西,还是我误解了访问者模式?
在尝试 VisitChildren 之前,我曾经使用 base.VisitColumn_definition(context) 调用,它基本上只调用 VisitChildren。
有没有人提示我犯了哪些错误?为什么我在 VisitChar 叶中创建的 ColumnDefinition 结果没有冒泡?
下面是我的测试输入:
CREATE TABLE "DBA"."pbcattbl" (
"pbt_tnam" char(129) NOT NULL
,"pbt_tid" char(5) NULL
);
您必须覆盖所有 Visit...(... context)
调用(至少是您的解析树中包含的所有调用)。假设你有这个语法:
grammar T;
parse
: expr EOF
;
expr
: expr ( '*' | '/' ) expr #multExpr
| expr ( '+' | '-' ) expr #addExpr
| NUMBER #numberExpr
;
NUMBER
: [0-9]+ ( '.' [0-9]+ )?
;
SPACES
: [ \t\r\n]+ -> skip
;
并且您正在解析表达式 "42"
。那么仅覆盖方法 VisitNumberExpr(TParser.NumberExprContext context)
:
是不够的
using System;
using Antlr4.Runtime;
namespace AntlrTest
{
class Program
{
static void Main(string[] args)
{
var lexer = new TLexer(CharStreams.fromstring("42"));
var parser = new TParser(new CommonTokenStream(lexer));
var root = parser.parse();
var evaluated = new CustomVisitor().Visit(root);
Console.WriteLine($"evaluated: {evaluated}");
}
}
class CustomVisitor : TBaseVisitor<decimal>
{
public override decimal VisitNumberExpr(TParser.NumberExprContext context)
{
return decimal.Parse(context.GetText());
}
}
}
它将 return 默认 0
。在这种情况下,您还应该覆盖 VisitParse(TParser.ParseContext context)
:
class CustomVisitor : TBaseVisitor<decimal>
{
public override decimal VisitParse(TParser.ParseContext context)
{
return Visit(context.expr());
}
public override decimal VisitNumberExpr(TParser.NumberExprContext context)
{
return decimal.Parse(context.GetText());
}
}
现在 returns 42
.
如果你不想override/implement太多规则,你可以改用监听器:
using System;
using Antlr4.Runtime;
using Antlr4.Runtime.Tree;
namespace AntlrTest
{
class Program
{
static void Main(string[] args)
{
var lexer = new TLexer(CharStreams.fromstring("42"));
var parser = new TParser(new CommonTokenStream(lexer));
var root = parser.parse();
var listener = new CustomListener();
ParseTreeWalker.Default.Walk(listener, root);
Console.WriteLine($"Result: {listener.Result}");
}
}
class CustomListener : TBaseListener
{
public decimal Result { get; private set; }
public override void EnterNumberExpr(TParser.NumberExprContext context)
{
Result = decimal.Parse(context.GetText());
}
}
}
我找到了解决方案:
protected override List<ColumnDefinition> AggregateResult(List<ColumnDefinition> aggregate, List<ColumnDefinition> nextResult)
{
if (aggregate != null && nextResult != null) aggregate.AddRange(nextResult);
return aggregate ?? nextResult;
}
我将结果转换为 List 并向 AggregateResult 添加了适当的覆盖。
感谢@kaby76 用你的评论为我指明了正确的方向。
也感谢所有其他人的反馈和快速回复!
我一直在尝试实现访问者模式以将一些特定的 SQL 语句解析为由 TableDefinition 和 ColumnDefinition object 组成的内部 object 结构。
这是语法中的一小部分(精简):
column_definition
: column_name datatype? column_constraint*
;
column_constraint
: ( K_CONSTRAINT name )?
( K_PRIMARY K_KEY ( K_CLUSTERED | K_NONCLUSTERED )? ( K_ASC | K_DESC )? K_AUTOINCREMENT?
| K_AUTOINCREMENT
| K_NOT? K_NULL
)
;
datatype
: K_CHAR ( '(' unsigned_integer ')' )? #char
| K_DATE #date
;
这是派生的 BaseVisitors 之一,意在 return ColumnDefinitions:
namespace SqlParser.Visitor
{
public class DataTypeVisitor: SqlAnywhereParserBaseVisitor<ColumnDefinition>
{
public override ColumnDefinition VisitColumn_definition([NotNull] SqlAnywhereParser.Column_definitionContext context)
{
var res = VisitChildren(context);
var constraint = (SqlAnywhereParser.Column_constraintContext[])context.column_constraint();
if (res != null) // Add NULL attributes
{
if (constraint.Any(c => c.K_NULL() != null && c.K_NOT() == null))
res.IsNullable = true;
if (constraint.Any(c => c.K_NULL() != null && c.K_NOT() != null))
res.IsNullable = false;
}
return res;
}
public override ColumnDefinition VisitChar([NotNull] SqlAnywhereParser.CharContext context)
{
return new ColumnDefinition()
{
DataType = DbType.StringFixedLength,
Length = int.Parse(context.unsigned_integer()?.GetText() ?? "1")
};
}
}
}
当我调试进程时,我可以观察到对 VisitChildren 的调用如何进入 VisitChar,其中 return 是一个 ColumnDefinition object。当 VisitChar 完成并且光标跳回以在 VisitColumn_definition 中继续时,变量 res 为空。
我是不是漏掉了一些重要的东西,还是我误解了访问者模式? 在尝试 VisitChildren 之前,我曾经使用 base.VisitColumn_definition(context) 调用,它基本上只调用 VisitChildren。
有没有人提示我犯了哪些错误?为什么我在 VisitChar 叶中创建的 ColumnDefinition 结果没有冒泡?
下面是我的测试输入:
CREATE TABLE "DBA"."pbcattbl" (
"pbt_tnam" char(129) NOT NULL
,"pbt_tid" char(5) NULL
);
您必须覆盖所有 Visit...(... context)
调用(至少是您的解析树中包含的所有调用)。假设你有这个语法:
grammar T;
parse
: expr EOF
;
expr
: expr ( '*' | '/' ) expr #multExpr
| expr ( '+' | '-' ) expr #addExpr
| NUMBER #numberExpr
;
NUMBER
: [0-9]+ ( '.' [0-9]+ )?
;
SPACES
: [ \t\r\n]+ -> skip
;
并且您正在解析表达式 "42"
。那么仅覆盖方法 VisitNumberExpr(TParser.NumberExprContext context)
:
using System;
using Antlr4.Runtime;
namespace AntlrTest
{
class Program
{
static void Main(string[] args)
{
var lexer = new TLexer(CharStreams.fromstring("42"));
var parser = new TParser(new CommonTokenStream(lexer));
var root = parser.parse();
var evaluated = new CustomVisitor().Visit(root);
Console.WriteLine($"evaluated: {evaluated}");
}
}
class CustomVisitor : TBaseVisitor<decimal>
{
public override decimal VisitNumberExpr(TParser.NumberExprContext context)
{
return decimal.Parse(context.GetText());
}
}
}
它将 return 默认 0
。在这种情况下,您还应该覆盖 VisitParse(TParser.ParseContext context)
:
class CustomVisitor : TBaseVisitor<decimal>
{
public override decimal VisitParse(TParser.ParseContext context)
{
return Visit(context.expr());
}
public override decimal VisitNumberExpr(TParser.NumberExprContext context)
{
return decimal.Parse(context.GetText());
}
}
现在 returns 42
.
如果你不想override/implement太多规则,你可以改用监听器:
using System;
using Antlr4.Runtime;
using Antlr4.Runtime.Tree;
namespace AntlrTest
{
class Program
{
static void Main(string[] args)
{
var lexer = new TLexer(CharStreams.fromstring("42"));
var parser = new TParser(new CommonTokenStream(lexer));
var root = parser.parse();
var listener = new CustomListener();
ParseTreeWalker.Default.Walk(listener, root);
Console.WriteLine($"Result: {listener.Result}");
}
}
class CustomListener : TBaseListener
{
public decimal Result { get; private set; }
public override void EnterNumberExpr(TParser.NumberExprContext context)
{
Result = decimal.Parse(context.GetText());
}
}
}
我找到了解决方案:
protected override List<ColumnDefinition> AggregateResult(List<ColumnDefinition> aggregate, List<ColumnDefinition> nextResult)
{
if (aggregate != null && nextResult != null) aggregate.AddRange(nextResult);
return aggregate ?? nextResult;
}
我将结果转换为 List
感谢@kaby76 用你的评论为我指明了正确的方向。 也感谢所有其他人的反馈和快速回复!