2014-06-12 13 views
5

Próbuję napisać tłumacza kodu w Javie przy pomocy Antlr4 i miałem ogromny sukces z częścią gramatyczną do tej pory. Jednak teraz walę głową o ścianę, owijając mój umysł wokół struktury danych drzewa parsowania, którą muszę popracować po tym, jak moje dane wejściowe zostały przeanalizowane.Zrozumienie struktury danych kontekstowych w Antlr4

Próbuję użyć szablonu gościa, aby przejść przez moje drzewo parse. Pokażę ci przykład ilustrujący punkty mojego zamieszania.

Moja gramatyka:

grammar pqlc; 

// Lexer 

//Schlüsselwörter 
EXISTS: 'exists'; 
REDUCE: 'reduce'; 
QUERY: 'query'; 
INT: 'int'; 
DOUBLE: 'double'; 
CONST: 'const'; 
STDVECTOR: 'std::vector'; 
STDMAP: 'std::map'; 
STDSET: 'std::set'; 
C_EXPR: 'c_expr'; 

INTEGER_LITERAL : (DIGIT)+ ; 
fragment DIGIT: '0'..'9'; 
DOUBLE_LITERAL : DIGIT '.' DIGIT+; 

LPAREN   : '('; 
RPAREN   : ')'; 
LBRACK   : '['; 
RBRACK   : ']'; 
DOT    : '.'; 
EQUAL   : '=='; 
LE    : '<='; 
GE    : '>='; 
GT    : '>'; 
LT    : '<'; 
ADD    : '+'; 
MUL    : '*'; 
AND    : '&&'; 
COLON   : ':'; 

IDENTIFIER : JavaLetter JavaLetterOrDigit*; 
fragment JavaLetter : [a-zA-Z$_]; // these are the "java letters" below 0xFF 
fragment JavaLetterOrDigit : [a-zA-Z0-9$_]; // these are the "java letters or digits" below 0xFF 
WS 
    : [ \t\r\n\u000C]+ -> skip 
    ; 
COMMENT 
    : '/*' .*? '*/' -> skip 
    ; 

LINE_COMMENT 
    : '//' ~[\r\n]* -> skip 
    ; 


// Parser 

//start_rule: query; 

query : 
     quant_expr 
     | qexpr+ 
     | IDENTIFIER // order IDENTIFIER and qexpr+? 
     | numeral 
     | c_expr //TODO 

     ; 

c_type : INT | DOUBLE | CONST; 
bin_op: AND | ADD | MUL | EQUAL | LT | GT | LE| GE; 


qexpr: 
     LPAREN query RPAREN bin_op_query? 
     // query bin_op query 
     | IDENTIFIER bin_op_query? // copied from query to resolve left recursion problem 
     | numeral bin_op_query? //^
     | quant_expr bin_op_query? //^
      |c_expr bin_op_query? 
      // query.find(query) 
     | IDENTIFIER find_query? // copied from query to resolve left recursion problem 
     | numeral find_query? //^
     | quant_expr find_query? 
      |c_expr find_query? 
      // query[query] 
      | IDENTIFIER array_query? // copied from query to resolve left recursion problem 
     | numeral array_query? //^
     | quant_expr array_query? 
      |c_expr array_query? 

    // | qexpr bin_op_query // bad, resolved by quexpr+ in query 
    ; 

bin_op_query: bin_op query bin_op_query?; // resolve left recursion of query bin_op query 

find_query: '.''find' LPAREN query RPAREN; 
array_query: LBRACK query RBRACK; 

quant_expr: 
    quant id ':' query 
      | QUERY LPAREN match RPAREN ':' query 
      | REDUCE LPAREN IDENTIFIER RPAREN id ':' query 
    ; 

match: 
     STDVECTOR LBRACK id RBRACK EQUAL cm 
    | STDMAP '.''find' LPAREN cm RPAREN EQUAL cm 
    | STDSET '.''find' LPAREN cm RPAREN 
    ; 

cm: 
    IDENTIFIER 
    | numeral 
    | c_expr //TODO 
    ; 

quant : 
      EXISTS; 

id : 
    c_type IDENTIFIER 
    | IDENTIFIER // Nach Seite 2 aber nicht der Übersicht. Laut übersicht id -> aber dann wäre Regel 1 ohne + 
    ; 

numeral : 
      INTEGER_LITERAL 
     | DOUBLE_LITERAL 
     ; 
c_expr: 
      C_EXPR 
     ; 

Teraz analizować następujący ciąg:

double x: x >= c_expr 

Wizualnie Wezmę to drzewo: tree

Powiedzmy mój gość jest w visitQexpr(@NotNull pqlcParser.QexprContext ctx) rutynowe, gdy trafi do gałęzi Qexpr (x bin_op_query).

Moje pytanie brzmi: jak mogę powiedzieć, że lewe dzieci ("x" w drzewie) to węzeł końcowy, a dokładniej "IDENTYFIKATOR"? Nie ma reguł dotyczących odwiedzin dla węzłów terminalu, ponieważ nie są one regułami. ctx.getChild(0) nie ma elementu RuleIndex. Sądzę, że mógłbym użyć tego do sprawdzenia, czy jestem w terminalu czy nie, ale to nadal nie powiedziałoby mi, czy byłem w IDENTIFIER czy innym tokenie terminala. Muszę jakoś odróżnić różnicę.

Miałem więcej pytań, ale w czasie zajęło mi napisać wyjaśnienie, że je zapomniałem: < Z góry dziękuję.

+0

Typowym rozwiązaniem jest odwiedzenie [AST (abstrakcyjne drzewo syntaktyczne)] (https://en.wikipedia.org/wiki/Abstract_syntax_tree) zamiast wizyty w drzewie analizy ... Jednak ta funkcja została usunięta od antlr4] (http://stackoverflow.com/questions/15823333/how-can-i-build-an-ast-using-antlr4).Może możesz użyć króla [tablicy symboli] (https://pl.wikipedia.org/wiki/Symbol_table), aby wiedzieć, czy 'x' jest identyfikatorem :) Powodzenia! – NiziL

+0

Po pobraniu funkcji payLoad() elementu podrzędnego znajduje się identyfikator tokena (między innymi). Tak więc informacje są zapisywane w strukturze danych, musi istnieć odpowiedni sposób na łatwy dostęp do nich. ctx ma funkcje dla moich różnych węzłów końcowych, takich jak ctx.IDENTIFIER(), ale nie mogę powiedzieć, co dokładnie robią. Wygląda na to, że wartość zwracana przez ctx.IDENTIFIER() ma wartość null, jeśli nie ma węzłów końcowych, a tekst węzłów w przeciwnym przypadku. Nadal nie powiedziałby mi/które/z dzieci, jeśli jest ich kilka, jest węzłem końcowym. Uważam, że cała struktura danych jest tak zagmatwana: \ – user2323596

+1

Kiedy chcę wiedzieć, czy odwiedzany jest podrzędny/końcowy węzeł w jakimś kontekście, zwykle sprawdzam tylko, czy ten terminal/terminal jest pusty (nie jest odwiedzany) czy nie jest zerowy (jest odwiedzany). Ale też uważam, że jest to dość hacky, niż miły i czysty sposób na uzyskanie tego, czego chcesz ... – schauk11erd

Odpowiedz

3

Możesz dodać etykiety do tokenów i dostęp do nich/sprawdzić, czy występują one w otaczającej kontekście:

id : 
    c_type labelA = IDENTIFIER 
    | labelB = IDENTIFIER 
    ; 

Można też to zrobić, aby tworzyć różne wizyt:

id : 
    c_type IDENTIFIER #idType1 //choose more appropriate names! 
    | IDENTIFIER   #idType2 
    ; 

To stworzy różnych gości dla dwóch alternatyw i przypuszczam (to znaczy nie zweryfikowałem), że odwiedzający dla id nie zostanie wywołany.

Wolę następujące podejście choć:

id : 
     typeDef 
    | otherId 
    ; 
typeDef: c_type IDENTIFIER; 
otherId : IDENTIFIER ; 

Jest to w większym stopniu wpisane systemu. Ale możesz bardzo konkretnie odwiedzać węzły. Niektóre reguły, których używam:

  1. Użyj | tylko wtedy, gdy wszystkie alternatywy są regułami parsera.
  2. Zawiń każdy Token w regułę analizatora składni (np. otherId), aby nadać im "większe znaczenie".
  3. Można łączyć reguły i żetony parsera, jeśli tokeny nie są tak naprawdę ważne (jak ;) i dlatego nie są potrzebne w drzewie analiz.