问题描述
使用这个 https://github.com/antlr/grammars-v4/tree/master/cpp antlr 语法我正在尝试解析 C++ 代码。我想获得每个函数的代码,所以我决定访问 visitFunctionBody
,你可以看到下面的代码,
#include <iostream>
#include <antlr4-runtime.h>
#include "parser/CPP14Lexer.h"
#include "parser/CPP14BaseVisitor.h"
#include "parser/CPP14Parser.h"
#include "parser/CPP14Visitor.h"
class TREEVisitor : public CPP14BaseVisitor {
public:
virtual antlrcpp::Any TREEVisitor::visitFunctionBody(
CPP14Parser::FunctionBodyContext *ctx) override
{
std::cout << ctx->getText() << std::endl;
return visitChildren(ctx);
}
};
int main(int argc,char *argv[]) {
std::ifstream stream;
stream.open(argv[1]);
antlr4::ANTLRInputStream input(stream);
CPP14Lexer lexer(&input);
antlr4::CommonTokenStream tokens(&lexer);
CPP14Parser parser(&tokens);
antlr4::tree::ParseTree *tree = parser.translationunit();
// Visitor
auto *visitor = new TREEVisitor();
visitor->visit(tree);
return 0;
}
我试图解析这个非常基本的 C++ 代码,
void foo()
{
char buf[10];
int i = 10;
int b = i * 2;
return b * i;
}
我的 antlr 访问者函数的输出是示例函数的代码,但没有任何换行符和缩进,如下所示,
{charbuf[10];inti=10;intb=i*2;returnb*i;}
在我的用例中,我解析了一个大的 C++ 文件,我想将解析结果与实际的源代码相匹配。
谢谢
解决方法
这是一种方式,但还有其他方式。在 CPP14Lexer.g4 中,将“->跳过”更改为“->通道(隐藏)”。然后,在visitFunctionBody()中,将调用“getText()”更改为“myGetText(ctx)”,并定义例程myGetText() *像这样,但对于C++“。这段代码是用Java编写的。
public String myGetText(ParseTree node) {
if (node.getChildCount() == 0) {
Token t = ((TerminalNodeImpl)node).getSymbol();
List<Token> tokensBefore = tokens.getHiddenTokensToLeft(t.getTokenIndex(),Token.HIDDEN_CHANNEL);
String pre = "";
if (tokensBefore != null) {
StringBuilder builder2 = new StringBuilder();
for (Token token : tokensBefore) {
CharStream input = token.getInputStream();
String s = input.getText(Interval.of(token.getStartIndex(),token.getStopIndex()));
builder2.append(s);
}
pre = builder2.toString();
}
String s2 = node.getText();
String ss = pre + s2;
return ss;
}
StringBuilder builder = new StringBuilder();
for (int i = 0; i < node.getChildCount(); i++) {
String s = myGetText(node.getChild(i));
builder.append(s);
}
return builder.toString();
}
你也可以通过直接查询char流树中叶子节点之间涉及的字符,在不将“skip”改为“HIDDEN”的情况下重构文本。
static void Reconstruct(ParseTree node,Parser parser)
{
var ct = (ParserRuleContext)node;
Token ta = ct.getStart();
Token tb = ct.getStop();
var input_stream = ta.getInputStream();
var start = ta.getStartIndex();
var stop = tb.getStopIndex();
System.out.println(input_stream.getText(new Interval(start,stop)));
}
,
您只需要保留空白标记(将它们推送到 HIDDEN
频道)。
然后,您将需要在您的 Listener/Visitor 中访问您的 TokenStream。然后,您可以为您的上下文和 getText(interval)
创建间隔。这将包括 HIDDEN
频道上的令牌。
示例:
在您的词法分析器中,将 -> skip
更改为 -> channel(HIDDEN)
:
Whitespace: [ \t]+ -> channel(HIDDEN);
Newline: ('\r' '\n'? | '\n') -> channel(HIDDEN);
BlockComment: '/*' .*? '*/' -> channel(HIDDEN);
LineComment: '//' ~ [\r\n]* -> channel(HIDDEN);
解析您的输入后,将您的 TokenStream 传递给您的侦听器。
...
CommonTokenStream tokens = new CommonTokenStream(lexer);
...
ParseTree tree = parser.translationUnit();
...
CPPListener listener = new CPPListener(tokens);
ParseTreeWalker walker = new ParseTreeWalker();
walker.walk(listener,tree);
然后在您的听众中:
class CPPListener extends CPP14ParserBaseListener {
TokenStream tokenStream;
CPPListener(TokenStream tokenStream) {
this.tokenStream = tokenStream;
}
@Override
public void exitFunctionDefinition(CPP14Parser.FunctionDefinitionContext ctx) {
Interval interval = new Interval(
ctx.start.getTokenIndex(),ctx.stop.getTokenIndex()
);
String source = tokenStream.getText(interval);
System.out.println(source);
}
}
示例输出:
void foo()
{
char buf[10];
int i = 10;
int b = i * 2;
return b * i;
}