Jestem kompletnym nowicjuszem ANTLR4, więc proszę wybaczyć moją niewiedzę. Wpadłem na this presentation, gdzie zdefiniowano bardzo prostą gramatykę wyrażenia arytmetycznego. Wygląda to tak:Wzór gościa ANTLR4 na prostym przykładzie arytmetycznym
grammar Expressions;
start : expr ;
expr : left=expr op=('*'|'/') right=expr #opExpr
| left=expr op=('+'|'-') right=expr #opExpr
| atom=INT #atomExpr
;
INT : ('0'..'9')+ ;
WS : [ \t\r\n]+ -> skip ;
Który jest wielki, ponieważ będzie generować bardzo prosty binarne drzewo, które można przejść za pomocą odwiedzający jak wyjaśniono w slajdach, na przykład tutaj jest funkcja, która odwiedza expr
:
public Integer visitOpExpr(OpExprContext ctx) {
int left = visit(ctx.left);
int right = visit(ctx.right);
String op = ctx.op.getText();
switch (op.charAt(0)) {
case '*': return left * right;
case '/': return left/right;
case '+': return left + right;
case '-': return left - right;
default: throw new IllegalArgumentException("Unkown opeator " + op);
}
}
Następną rzeczą, którą chciałbym dodać, jest obsługa nawiasów. Więc zmodyfikował expr
następująco:
expr : '(' expr ')' #opExpr
| left=expr op=('*'|'/') right=expr #opExpr
| left=expr op=('+'|'-') right=expr #opExpr
| atom=INT #atomExpr
;
Niestety, powyższy kod nie działa, ponieważ kiedy napotykają nawiasach trzy atrybuty op
, left
i right
są nieważne (nie z NPE).
Myślę, że mógłbym obejść to poprzez zdefiniowanie nowego atrybutu, np. parenthesized='(' expr ')'
, a następnie poradzić sobie z tym w kodzie odwiedzającym. Jednak wydaje mi się przesadne, że mam cały dodatkowy typ węzła, który reprezentuje wyrażenie w nawiasach. Prostszy ale brzydsze rozwiązaniem jest dodanie następującej linii kodu na początku metody visitOpExpr
:
if (ctx.op == null) return visit(ctx.getChild(1)); // 0 and 2 are the parentheses!
nie lubię powyżej w ogóle, ponieważ jest bardzo kruchy i bardzo zależy od struktury gramatyczne.
Zastanawiam się, czy istnieje sposób, aby powiedzieć ANTLR, aby po prostu "zjeść" nawiasy i traktować wyrażenie jak dziecko. Jest tu? Czy jest lepszy sposób to zrobić?
Uwaga: Mój koniec celem jest rozszerzenie przykład zawierać wyrażeń logicznych, które same mogą zawierać wyrażeń arytmetycznych, np (2+4*3)/10 >= 11
, czyli relację (<,>, ==, ~ =, itp.) pomiędzy wyrażeniami arytmetycznymi można zdefiniować wyrażenie binarne atomowe. Jest to prosta i mam już gramatyka zarysowane, ale mam ten sam problem z nawiasu, czyli muszę być w stanie napisać takie rzeczy (Ja też dodać wsparcie dla zmiennych):
((2+4*x)/10 >= 11) | (x>1 & x<3)
EDIT: Naprawiono pierwszeństwo wyrażenia parentetyzowanego, nawias zawsze ma wyższy priorytet.
Rozumiem. Sądzę więc, że nie ma sposobu, aby po prostu ANTLR pozbyć się węzła pośredniego? Co do zasady istnieje również kwestia efektywności kosmicznej? –
Nie jestem do końca pewny, czy wiem, co masz na myśli, ale nie ma możliwości obejścia parentetyzowanej alternatywy: ANTLR jest właśnie tutaj do analizy, wszystkie inne logiki/oceny muszą być obsługiwane bezpośrednio przez ciebie [w klasie odwiedzający/słuchacza]. –
Mam na myśli, jeśli napiszesz to: '(((((((((((((2 + 3))))))))))', zamiast '2 + 3', składnia jest oczywiście poprawna, ale przestrzeń zajmowana przez drzewo jest znacznie większa ze względu na wszystkie nawiasy. Jestem tylko zaskoczony, że nie ma sposobu, aby zdefiniować zwarcie tak, aby poprzednie wyrażenie zostało zamienione na drugie. –