如何在Java、Python、JS中打印Antlr的ctx详情并获取Token信息?
ANTLR中Context对象(ctx)的Token相关属性/方法及可读导出方案(Java、Python、JS)
背景与已知使用示例
我了解ctx是ANTLR中的核心对象,以下是Java和Python中的基础使用示例:
Java示例
@Override public Value visitNumberAtom(MuParser.NumberAtomContext ctx) { return new Value(Double.valueOf(ctx.getText())); }
Python示例
def visitNumberAtom(self, ctx): value = ctx.getText() return float(value)
问题
我想知道ctx还有哪些与Token相关的属性或方法;理想情况下能以可读格式打印(导出)ctx。同时想了解这在Java、Python和JS中是否可行,或者能实现的最佳效果是什么?
补充示例说明
为了更清晰说明问题,我补充了具体示例:
Expr.g4 语法文件
grammar Expr; prog: expr EOF; expr: expr ('+' | '-') expr | INT; NEWLINE: [\r\n]+ -> skip; INT: [0-9]+;
生成Java文件的命令
antlr4 Expr.g4 -visitor -no-listener
EvalVisitor.java 重写类
public class EvalVisitor extends ExprBaseVisitor<Object> { @Override public Object visitExpr(ExprParser.ExprContext ctx) { System.out.println("visitExpr ctx.getText(): " + ctx.getText()); System.out.println("visitExpr ctx.toString(): " + ctx.toString()); return visitChildren(ctx); } }
Main.java 调用代码
import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.tree.ParseTree; public class Main { public static void main(String[] args) throws Exception { ExprLexer lexer = new ExprLexer(CharStreams.fromString("5+2-1")); ExprParser parser = new ExprParser(new CommonTokenStream(lexer)); ParseTree tree = parser.prog(); System.out.println("tree: " + tree.toStringTree(parser)); EvalVisitor visitor = new EvalVisitor(); visitor.visit(tree); } }
编译运行输出
$ java Main tree: (prog (expr (expr (expr 5) + (expr 2)) - (expr 1)) <EOF>) visitExpr ctx.getText(): 5+2-1 visitExpr ctx.toString(): [4] visitExpr ctx.getText(): 5+2 visitExpr ctx.toString(): [2 4] visitExpr ctx.getText(): 5 visitExpr ctx.toString(): [2 2 4] visitExpr ctx.getText(): 2 visitExpr ctx.toString(): [20 2 4] visitExpr ctx.getText(): 1 visitExpr ctx.toString(): [20 4]
可以看到toString()和toStringTree()的输出对普通用户友好,但对开发者不够直观——我想明确知道ctx的哪些属性对应具体Token,比如如何获取INT Token的内容?
解答
一、ctx与Token相关的核心属性/方法
所有ANTLR生成的Context类都继承自ParserRuleContext,核心Token相关方法/属性如下:
getText(): 返回当前ctx覆盖的所有文本(如示例中的5+2-1)getStart(): 返回当前ctx对应的第一个Token对象getStop(): 返回当前ctx对应的最后一个Token对象getToken(int tokenType, int index): 根据Token类型和索引获取指定Token(比如获取INT类型的第0个Token)getChildCount(): 返回当前ctx的子节点数量(包括子Context和Token)getChild(int index): 获取指定索引的子节点,可通过类型判断是否为Token
针对你的示例,要获取INT Token的内容,可在visitExpr中做如下处理:
Java实现
@Override public Object visitExpr(ExprParser.ExprContext ctx) { // 直接匹配INT类型的子Token if (ctx.INT() != null) { System.out.println("INT Token内容: " + ctx.INT().getText()); } // 遍历所有子节点查找Token for (int i = 0; i < ctx.getChildCount(); i++) { Object child = ctx.getChild(i); if (child instanceof Token) { Token token = (Token) child; System.out.printf("Token类型: %s, 内容: %s, 位置: %d%n", ExprLexer.VOCABULARY.getSymbolicName(token.getType()), token.getText(), token.getStartIndex()); } } return visitChildren(ctx); }
Python实现
def visitExpr(self, ctx): # 直接访问生成的INT属性 if ctx.INT(): print(f"INT Token内容: {ctx.INT().getText()}") # 遍历子节点 for child in ctx.getChildren(): if hasattr(child, 'getSymbol'): token = child.getSymbol() print(f"Token类型: {self.parser.symbolicNames[token.type]}, 内容: {token.text}, 位置: {token.start}") return self.visitChildren(ctx)
JS实现
visitExpr(ctx) { // 直接访问INT节点 if (ctx.INT()) { console.log(`INT Token内容: ${ctx.INT().getText()}`); } // 遍历子节点 for (let i = 0; i < ctx.getChildCount(); i++) { const child = ctx.getChild(i); if (child.getSymbol) { const token = child.getSymbol(); console.log(`Token类型: ${this.parser.vocabulary.getSymbolicName(token.type)}, 内容: ${token.text}, 位置: ${token.startIndex}`); } } return this.visitChildren(ctx); }
二、可读格式打印/导出ctx的最佳方案
1. 自定义打印方法
针对开发者需求,可编写工具方法遍历ctx的所有属性和Token信息,输出结构化内容(比如JSON格式):
Java示例(导出为JSON)
import com.google.gson.Gson; import java.util.ArrayList; import java.util.List; public class CtxPrinter { public static String printCtx(ParserRuleContext ctx) { List<TokenInfo> tokens = new ArrayList<>(); collectTokens(ctx, tokens); return new Gson().toJson(tokens); } private static void collectTokens(ParserRuleContext ctx, List<TokenInfo> tokens) { for (int i = 0; i < ctx.getChildCount(); i++) { Object child = ctx.getChild(i); if (child instanceof Token) { Token token = (Token) child; tokens.add(new TokenInfo( ctx.getClass().getSimpleName(), ExprLexer.VOCABULARY.getSymbolicName(token.getType()), token.getText(), token.getStartIndex(), token.getStopIndex() )); } else if (child instanceof ParserRuleContext) { collectTokens((ParserRuleContext) child, tokens); } } } private static class TokenInfo { public String contextType; public String tokenType; public String text; public int startIndex; public int stopIndex; public TokenInfo(String contextType, String tokenType, String text, int startIndex, int stopIndex) { this.contextType = contextType; this.tokenType = tokenType; this.text = text; this.startIndex = startIndex; this.stopIndex = stopIndex; } } }
调用方式:System.out.println(CtxPrinter.printCtx(ctx));
2. 利用ANTLR内置工具
toStringTree(parser): 输出结构化的语法树(适合快速查看树结构)getRuleContext(): 获取当前ctx对应的规则上下文信息- Java环境下可通过反射遍历ctx的自动生成属性(如
INT()、PLUS()等方法)
三、各语言的实现差异
| 语言 | Token获取方式 | 导出便利性 |
|---|---|---|
| Java | 自动生成对应Token的getter方法(如ctx.INT()),支持强类型判断 | 可通过反射、JSON库快速结构化导出 |
| Python | 自动生成属性(如ctx.INT),需通过getSymbol()获取Token对象 | 原生支持字典转换,可快速转为JSON |
| JS | 自动生成方法(如ctx.INT()),Token对象通过getSymbol()获取 | 原生支持JSON序列化,实现成本低 |
内容的提问来源于stack exchange,提问作者oldpride
相关产品推荐
相关产品推荐

