#antlr #antlr4
#antlr #antlr4
Вопрос:
#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(amp;input);
antlr4::CommonTokenStream tokens(amp;lexer);
CPP14Parser parser(amp;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 и хочу сопоставить результат моего синтаксического анализа с фактическим исходным кодом.
Спасибо
Ответ №1:
Это один из способов, но есть и другие. В 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();
}
Вы также можете восстановить текст, не меняя «пропустить» на «СКРЫТЫЙ», напрямую запрашивая в потоке символов символы, задействованные между конечными узлами дерева.
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)));
}
Комментарии:
1. В
Reconstruct()
функцииParseTree node
аргумент может быть любого типа? например, могу ли я передатьCPP14Parser::DeclarationContext
? или измените прототип на,static void Reconstruct(CPP14Parser::DeclarationContext, Parser parser){...}
2. Да, проверки проверки отсутствуют.
Ответ №2:
Вам просто нужно сохранить маркеры пробелов (отправить их в HIDDEN
канал).
Затем вам потребуется доступ к вашему потоку токенов в вашем слушателе / посетителе. Затем вы можете создать интервал для вашего контекста и getText(interval)
. Это будет включать токены на HIDDEN
канале.
Пример:
В вашем лексере измените -> skip
на -> channel(HIDDEN)
:
Whitespace: [ t] -> channel(HIDDEN);
Newline: ('r' 'n'? | 'n') -> channel(HIDDEN);
BlockComment: '/*' .*? '*/' -> channel(HIDDEN);
LineComment: '//' ~ [rn]* -> channel(HIDDEN);
После анализа ваших входных данных передайте свой токенстрим вашему слушателю.
...
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;
}
Комментарии:
1. Привет, Майк, ссылка была полезной, но я не уверен, что смогу реализовать ее, просто прочитав этот документ, я был бы признателен, если бы вы могли поделиться некоторым кодом. Большое вам спасибо
2. Хотя я немного «помешан на языках», я лишь ОЧЕНЬ недолго занимался C около 20-30 лет назад. Я мог бы собрать что-нибудь вместе на Java. Если я найду несколько минут, я опубликую это сообщение. Основная идея заключалась бы в создании TokenStremRewriter из вашего потока токенов. Затем, когда вы посещаете дерево и получаете ParserRuleContext для интересующего вас узла, вы создаете объект Interval, используя токены start и stop этого контекста. Затем вы можете вызвать rewriter.getText(интервал), чтобы вернуть исходный код. Такова теория. Я обновлю сообщение, если у меня будет возможность закодировать его на Java.
3. Хм… у меня возникла дополнительная мысль, и я проверил ее. Вам не нужен рерайтер… обновит ответ.