Parametry
Reguła może przyjmować parametry a także zwracać wartość (lub wartości). Składnia przedstawiona jest poniżej:
nazwa [parametry_wejsciowe, ...] returns [parametry_wyjściowe, ...] : ... ;
Aby do tak zadeklaroweanych parametrów formalnych się odwołać używamy
notacji analogicznej jak dla etykiet - poprzedzając nazwę parametru
symbolem $
Poniżej praktyczny przykład:
grammar Par; r1 : r2 | lol=r3[1] { System.out.println("Zwrocilo sie " + $lol.wyn); } ; r2 : 'a' r3[2] { System.out.println("Zwrocilo sie " + $r3.wyn); } ; r3 [int par] returns [int wyn] : 'b' { if ($par==1) { System.out.println("b1"); $wyn=$par+2; } else { System.out.println("b2"); $wyn=$par+3; } } ; NL : '\r'? '\n' {skip();}; WS : (' ' | '\t' | '\n' | '\r') { skip(); };
Wyjątki
ANTLR v 3.1+ obsługuje wyjątki, ale tylko "wybiórczo":
- ignorowane jest słowo kluczowe "throws" i następujący po nim parametr (!!!)
- kompilator javy nie sprawdza wyjątków należących do klasy RuntimeException i jej pochodnych (target java)
- słowo kluczowe "catch" jest w pełni respektowane
Nic nie stoi zatem na przeszkodzie, by utworzyć własną klasę dziedziczącą po RuntimeException i zrobic z niej użytek :)
grammar Wyjatko; @members { class MyException extends RuntimeException { MyException() { System.out.println("Ajajaj!"); } } } r1 : r2 ; /* tu jest średnik należący do reguły r1! */ catch [MyException foo] { System.out.println("Catch!"); } /* to też odnosi się do reguły r1!*/ r2 : 'a' | 'b' { if (1==1) throw new MyException(); } ;
Odzyskiwanie kontroli po błędzie
Z błędami należy sobie radzić możliwie selektywnie. Gdy z szeregu wyrażeń jedno jest błędne (np. dzielenie przez zero), następujące po nim wyrażenia powinny zostać prawidłowo przetworzone. W tym celu czasem trzeba w sensowny sposób pozbyć się fragmentu wejścia. Możemy do tego użyć dwóch funkcji: consumeUntil(input, dokąd) oraz input.consume().
- consumeUntil(input, dokąd) "połyka" z wejścia wszystkie
leksemy do napotkania leksemu dokąd (jego nie połykając)
Parametr dokąd może być:- nazwą pojedyńczego tokenu (np. NL),
- zbiorem (ściślej - obiektem klasy BitSet) tokenów.
- input.consume() "konsumuje" jeden (następny) leksem z wejścia.
Przykładowe konstrukcje zamieszczone są poniżej.
// pozbywamy się całego wejścia do najbliższego leksemu NL oraz leksemu NL consumeUntil(input, NL); input.consume(); // pozbywamy się wejścia do najbliższego leksemu PLUS lub MINUS wraz z tym leksemem BitSet leksemy = new BitSet(); leksemy.add(PLUS); leksemy.add(MINUS); consumeUntil(input, leksemy); input.consume();
Zakresy
Poniżej przedstawiony jest (niepełny) przykład zastosowania bloków i zakresów widoczności.
grammar Skopki; @header { import java.util.HashMap; } @members { Integer getSymbol (String name) { for (int lv=$r2.size()-1; lv >=0; lv--) { if ($r2[lv]::hm.containsKey(name)) return $r2[lv]::hm.get(name); } /* Obsluga wyjatkow prezentowana byla w poprzednim odcinku throw new NotFoundException(name); */ return -1; } Integer putSymbol (String name, Integer value) { for (int lv=$r2.size()-1; lv >=0; lv--) { if ($r2[lv]::hm.containsKey(name)) { $r2[lv]::hm.put(name, value); return value; } } // throw new NotFoundException(name); return -1; } void declareSymbol (String name) { if(!$r2::hm.containsKey(name)) $r2::hm.put(name, -1); /* else throw new RedeclaredException(name); */ } } r1 : r2+; r2 scope { HashMap<String, Integer> hm; } @init { $r2::hm=new HashMap<String, Integer>(); } : '[' { System.out.println("Entering new scope..."); } r3+ ']' { System.out.println("Leaving scope..."); }; r3 : r2 | SLOWO { System.out.println(getSymbol($SLOWO.text)); } | DEC SLOWO { declareSymbol($SLOWO.text);} | PUT SLOWO {putSymbol($SLOWO.text, $r2::hm.size());}; DEC : 'Dec'; PUT : '>'; SLOWO : ('a'..'z')+; NL : '\r'? '\n' { $channel=HIDDEN; }; WS : (' ' | '\t' ) { $channel=HIDDEN; }; //WS : (' ' | '\t' ) { skip(); };