ANTLR4 JavaScript实现:如何像ANTLR Lab输出语法分析树?
在ANTLR4 JavaScript实现中输出类ANTLR Lab的语法分析树
问题背景
已基于ANTLR4完成JavaScript解析器开发,代码可正常运行,但需要在控制台输出类似ANTLR Lab的结构化语法分析树,而非当前的嵌套数组或原始树对象。现有核心代码如下:
解析器初始化代码
if (selected == "Funktionen") { console.log("You selected functions") const chars = new antlr4.InputStream(data.stringToLex); const lexer = new FunktionLexer(chars); const tokens = new antlr4.CommonTokenStream(lexer); const parser = new FunktionParser(tokens); parser.buildParseTrees = true; const tree = parser.start(); tree.accept(new Visitor()); // 现有访问器仅返回嵌套数组,无格式化输出 }
现有访问器实现
class Visitor { visitChildren(ctx) { if (!ctx) { return; } if (ctx.children) { return ctx.children.map(child => { if (child.children && child.children.length != 0) { return child.accept(this); } else { return child.getText(); } }); } } }
解决方案
要实现类ANTLR Lab的树形输出,核心是区分非终端节点(用规则名标识)和终端节点(用文本标识),并通过缩进体现层级结构。以下提供两种实现方式:
方式1:自定义递归打印函数
直接编写递归函数遍历语法树,控制台打印带缩进的结构:
// 递归打印语法树的函数 function printParseTree(node, indentLevel = 0) { const indent = ' '.repeat(indentLevel); let nodeLabel; // 判断节点类型:非终端节点取规则名,终端节点取文本 if (node.ruleIndex !== undefined) { nodeLabel = FunktionParser.ruleNames[node.ruleIndex]; } else { nodeLabel = node.getText(); } console.log(`${indent}${nodeLabel}`); // 递归处理子节点 if (node.children) { node.children.forEach(child => { printParseTree(child, indentLevel + 1); }); } } // 替换原有访问器调用 if (selected == "Funktionen") { console.log("You selected functions") const chars = new antlr4.InputStream(data.stringToLex); const lexer = new FunktionLexer(chars); const tokens = new antlr4.CommonTokenStream(lexer); const parser = new FunktionParser(tokens); parser.buildParseTrees = true; const tree = parser.start(); printParseTree(tree); // 调用打印函数 }
方式2:修改访问器实现打印逻辑
调整现有Visitor,在访问节点时直接打印结构化内容:
class TreePrinterVisitor { constructor() { this.indentLevel = 0; } visitChildren(ctx) { if (!ctx || !ctx.children) return; // 打印当前非终端节点的规则名 const ruleName = FunktionParser.ruleNames[ctx.ruleIndex]; console.log(`${' '.repeat(this.indentLevel)}${ruleName}`); this.indentLevel += 1; ctx.children.forEach(child => { if (child.children && child.children.length > 0) { // 子节点为非终端节点,继续访问 child.accept(this); } else { // 子节点为终端节点,直接打印文本 console.log(`${' '.repeat(this.indentLevel)}${child.getText()}`); } }); this.indentLevel -= 1; } } // 使用修改后的访问器 if (selected == "Funktionen") { console.log("You selected functions") const chars = new antlr4.InputStream(data.stringToLex); const lexer = new FunktionLexer(chars); const tokens = new antlr4.CommonTokenStream(lexer); const parser = new FunktionParser(tokens); parser.buildParseTrees = true; const tree = parser.start(); tree.accept(new TreePrinterVisitor()); // 使用打印访问器 }
说明
两种方式都会在控制台输出层级分明的语法树,非终端节点显示对应的规则名称(如start、expression等),终端节点显示实际输入文本,缩进层级对应树的嵌套深度,效果与ANTLR Lab的树形展示一致。
内容的提问来源于stack exchange,提问作者Ruffyg
相关产品推荐
相关产品推荐

