Сопоставление правил синтаксического анализа ANTLR с пользовательскими классами Java AST для генерации кода
Я, кажется, борюсь с AST - > StringTemplate стороной вещей, вероятно, потому что я пришел от написания парсеров вручную - > LLVM.
Я ищу способ автоматического сопоставления правила синтаксического анализа с классом AST, который может его представлять и содержит метод для генерации выходных данных целевого языка. (возможно, в этом случае используется StringTemplate.)
В псевдокоде, приведенном в этом примере грамматики:
numberExpression
: DIGIT+
;
Я хочу, чтобы он был сопоставлен с этим АСТ класс:
class NumberExpressionAST extends BaseAST {
private double value;
public NumberExpressionAST(node) {
this.value = node.value;
}
public String generateCode() {
// However we want to generate the output.
// Maybe use a template, maybe string literals, maybe cure cancer...whatever.
}
}
Чтобы соединить их, возможно, будет какой-то клей, как показано ниже: (или вы можете сойти с ума с Class.forName
вещами)
switch (child.ruleName) {
case 'numberExpression':
return new NumberExpressionAST(child);
break;
}
Я рыскал по сети и нашел правила синтаксического разбора в грамматике с ->
, но я не могу понять, как сохранить всю эту логику из грамматики. Особенно код для настройки и генерации целевого вывода из шаблона. Я не против ходить по дереву несколько раз.
Я подумал, что, возможно, я мог бы использовать этот вариант output=AST
а потом, может быть, предоставить свои собственные классы AST, распространяющиеся от CommonTree? Я признаю, что мои познания в АНТЛР очень примитивны, так что простите мое невежество. Каждый урок, который я читаю, показывает, что все это делается в соответствии с грамматикой, которая для меня совершенно безумна и трудна для поддержания.
цель: исключить логику AST/codegen/template из грамматики.
Править ---------------------------------------------
Я прибегал к трассировке через фактический исходный код ANTLR (так как они используют себя), и я вижу похожие вещи, такие как BlockAST
, RuleAST
, и т.д. Все наследующие от CommonTree
. Я еще не совсем разобрался в самом главном...как они их используют..
Оглядевшись, я заметил, что вы можете в основном вводить маркеры подсказок:
identifier
: IDENTIFIER<AnyJavaClassIWantAST>
;
Вы не можете сделать то же самое для правил синтаксического анализа...но если вы создадите какой-то маркер для представления правило разбора в целом, вы можете использовать правила перезаписи следующим образом:
declaration
: type identifier -> SOME_PARSE_RULE<AnyJavaClassIWantAST>
;
Все это ближе к тому, что я хочу, но в идеале я не должен мусорить grammar...is есть ли способ положить их куда-нибудь еще?
1 ответ:
Не могли бы вы добавить это в качестве ответа?..Вот надуманный пример, который использует несколько функций ANTLR4, которые имеют большое значение для отделения грамматики от языка вывода, главным образомальтернативные метки и сгенерированный слушатель. Этот пример грамматики может представлять несколько тривиальных битов кода, но он делает это без ссылок на язык-даже без вызова
skip()
для пробелов в лексере. Тестовый класс преобразует входные данные в некоторые выходные данные Java использование сгенерированного слушателя.Я избегал использовать что-либо, что я не мог получить, чтобы работать на первой паре попыток, так что не считайте это исчерпывающим примером ни в коем случае.
Симпланг.g
grammar Simplang; compilationUnit : statements EOF; statements : statement+; statement : block #BlockStatement | call #CallStatement | decl #DeclStatement ; block : LCUR statements RCUR; call : methodName LPAR args=arglist? RPAR SEMI; methodName : ID; arglist : arg (COMMA arg)*; arg : expr; decl : VAR variableName EQ expr SEMI; variableName : ID; expr : add_expr; add_expr : lhs=primary_expr (add_op rhs=primary_expr)*; add_op : PLUS | MINUS; primary_expr : string=STRING | id=ID | integer=INT ; VAR: 'var'; ID: ('a'..'z'|'A'..'Z')+; INT: ('0'..'9')+; STRING: '\'' ~('\r'|'\n'|'\'')* '\''; SEMI: ';'; LPAR: '('; RPAR: ')'; LCUR: '{'; RCUR: '}'; PLUS: '+'; MINUS: '-'; COMMA: ','; EQ: '='; WS: (' '|'\t'|'\f'|'\r'|'\n') -> skip;
Наряду с лексером и синтаксическим анализатором, ANTLR4 генерирует интерфейс прослушивателя и пустой класс реализации по умолчанию. Вот интерфейс, созданный для приведенной выше грамматики.
SimplangListener.java
public interface SimplangListener extends ParseTreeListener { void enterArglist(SimplangParser.ArglistContext ctx); void exitArglist(SimplangParser.ArglistContext ctx); void enterCall(SimplangParser.CallContext ctx); void exitCall(SimplangParser.CallContext ctx); void enterCompilationUnit(SimplangParser.CompilationUnitContext ctx); void exitCompilationUnit(SimplangParser.CompilationUnitContext ctx); void enterVariableName(SimplangParser.VariableNameContext ctx); void exitVariableName(SimplangParser.VariableNameContext ctx); void enterBlock(SimplangParser.BlockContext ctx); void exitBlock(SimplangParser.BlockContext ctx); void enterExpr(SimplangParser.ExprContext ctx); void exitExpr(SimplangParser.ExprContext ctx); void enterPrimary_expr(SimplangParser.Primary_exprContext ctx); void exitPrimary_expr(SimplangParser.Primary_exprContext ctx); void enterAdd_expr(SimplangParser.Add_exprContext ctx); void exitAdd_expr(SimplangParser.Add_exprContext ctx); void enterArg(SimplangParser.ArgContext ctx); void exitArg(SimplangParser.ArgContext ctx); void enterAdd_op(SimplangParser.Add_opContext ctx); void exitAdd_op(SimplangParser.Add_opContext ctx); void enterStatements(SimplangParser.StatementsContext ctx); void exitStatements(SimplangParser.StatementsContext ctx); void enterBlockStatement(SimplangParser.BlockStatementContext ctx); void exitBlockStatement(SimplangParser.BlockStatementContext ctx); void enterCallStatement(SimplangParser.CallStatementContext ctx); void exitCallStatement(SimplangParser.CallStatementContext ctx); void enterMethodName(SimplangParser.MethodNameContext ctx); void exitMethodName(SimplangParser.MethodNameContext ctx); void enterDeclStatement(SimplangParser.DeclStatementContext ctx); void exitDeclStatement(SimplangParser.DeclStatementContext ctx); void enterDecl(SimplangParser.DeclContext ctx); void exitDecl(SimplangParser.DeclContext ctx); }
Вот тестовый класс, который переопределяет несколько методов в пустой слушатель и вызывает парсер.
SimplangTest.java
public class SimplangTest { public static void main(String[] args) { ANTLRInputStream input = new ANTLRInputStream( "var x = 4;\nfoo(x, 10);\nbar(y + 10 - 1, 'x' + 'y' + 'z');"); SimplangLexer lexer = new SimplangLexer(input); SimplangParser parser = new SimplangParser(new CommonTokenStream(lexer)); parser.addParseListener(new SimplangBaseListener() { public void exitArg(SimplangParser.ArgContext ctx) { System.out.print(", "); } public void exitCall(SimplangParser.CallContext call) { System.out.print("})"); } public void exitMethodName(SimplangParser.MethodNameContext ctx) { System.out.printf("call(\"%s\", new Object[]{", ctx.ID() .getText()); } public void exitCallStatement(SimplangParser.CallStatementContext ctx) { System.out.println(";"); } public void enterDecl(SimplangParser.DeclContext ctx) { System.out.print("define("); } public void exitVariableName(SimplangParser.VariableNameContext ctx) { System.out.printf("\"%s\", ", ctx.ID().getText()); } public void exitDeclStatement(SimplangParser.DeclStatementContext ctx) { System.out.println(");"); } public void exitAdd_op(SimplangParser.Add_opContext ctx) { if (ctx.MINUS() != null) { System.out.print(" - "); } else { System.out.print(" + "); } } public void exitPrimary_expr(SimplangParser.Primary_exprContext ctx) { if (ctx.string != null) { String value = ctx.string.getText(); System.out.printf("\"%s\"", value.subSequence(1, value.length() - 1)); } else if (ctx.altNum == 2){ //cheating and using the alt# for "INT" System.out.printf("read(\"%s\")", ctx.id.getText()); } else { System.out.print(ctx.INT().getText()); } } }); parser.compilationUnit(); } }
Вот тестовый вход, жестко закодированный в тестовом классе:
var x = 4; foo(x, 10); bar(y + 10 - 1, 'x' + 'y' + 'z');
Вот полученный результат:
Это глупый пример, но он показывает несколько функций, которые могут быть полезны вам при создании пользовательского AST.define("x", 4); call("foo", new Object[]{read("x"), 10, }); call("bar", new Object[]{read("y") + 10 - 1, "x" + "y" + "z", });