Java s ANTLR

1. Prehľad

V tomto výučbe urobíme rýchly prehľad o generátore analyzátora ANTLR a ukážeme si niektoré aplikácie v reálnom svete.

2. ANTLR

ANTLR (ANother Tool for Language Recognition) je nástroj na spracovanie štruktúrovaného textu.

Robí to tak, že nám poskytuje prístup k primitívam na spracovanie jazyka, ako sú lexeri, gramatiky a syntaktické analyzátory, a tiež čas potrebný na spracovanie textu.

Často sa používa na vytváranie nástrojov a rámcov. Napríklad Hibernate používa ANTLR na analýzu a spracovanie HQL dotazov a Elasticsearch ho používa pre Painless.

A Java je len jedna väzba. ANTLR tiež ponúka väzby pre C #, Python, JavaScript, Go, C ++ a Swift.

3. Konfigurácia

Najskôr začnime pridaním antlr-runtime do nášho pom.xml:

 org.antlr antlr4-runtime 4.7.1 

A tiež antlr-maven-plugin:

 org.antlr antlr4-maven-plugin 4.7.1 antlr4 

Úlohou pluginu je generovať kód z gramatík, ktoré špecifikujeme.

4. Ako to funguje?

V zásade, keď chceme vytvoriť syntaktický analyzátor pomocou doplnku ANTLR Maven, musíme postupovať podľa troch jednoduchých krokov:

  • pripraviť gramatický súbor
  • generovať zdroje
  • vytvoriť poslucháča

Pozrime sa teda na tieto kroky v akcii.

5. Používanie existujúcej gramatiky

Najprv použijeme ANTLR na analýzu kódu pre metódy so zlým písmenom:

verejná trieda SampleClass {public void DoSomethingElse () {// ...}}

Jednoducho povedané, overíme, že všetky názvy metód v našom kóde začínajú malým písmenom.

5.1. Pripravte si gramatický súbor

Je pekné, že už existuje niekoľko gramatických súborov, ktoré vyhovujú našim účelom.

Použime gramatický súbor Java8.g4, ktorý sme našli v repozitári gramatiky Github spoločnosti ANTLR.

Môžeme vytvoriť src / main / antlr4 adresár a stiahnite si ho tam.

5.2. Generovať zdroje

ANTLR funguje tak, že generuje kód Java zodpovedajúci gramatickým súborom, ktoré mu dávame, a plugin maven to uľahčuje:

balíček mvn

V predvolenom nastavení sa to vygeneruje niekoľko súborov pod cieľ / generované-zdroje / antlr4 adresár:

  • Java8.interp
  • Java8Listener.java
  • Java8BaseListener.java
  • Java8Lexer.java
  • Java8Lexer.interp
  • Java8Parser.java
  • Java8.tokens
  • Java8Lexer.tokens

Všimnite si, že názvy týchto súborov sú založené na názve gramatického súboru.

Budeme potrebovať Java8Lexer a Java8Parser súbory neskôr, keď otestujeme. Zatiaľ však potrebujeme Java8BaseListener za vytvorenie našej MethodUppercaseListener.

5.3. Tvorenie MethodUppercaseListener

Na základe gramatiky Java8, ktorú sme použili, Java8BaseListener má niekoľko metód, ktoré môžeme prepísať, pričom každá z nich zodpovedá nadpisu v súbore gramatiky.

Napríklad gramatika definuje názov metódy, zoznam parametrov a klauzulu hodí takto:

methodDeclarator: Identifikátor '(' formalParameterList? ')' sa stlmí? ;

A tak Java8BaseListener má metódu enterMethodDeclarator ktoré sa vyvolajú zakaždým, keď sa vyskytne tento vzor.

Poďme to teda prekonať enterMethodDeclarator, vytiahnite Identifikátora vykonáme našu kontrolu:

verejná trieda UppercaseMethodListener rozširuje Java8BaseListener {chyby súkromného zoznamu = nový ArrayList (); // ... hľadač chýb @Override public void enterMethodDeclarator (Java8Parser.MethodDeclaratorContext ctx) {TerminalNode node = ctx.Identifier (); Reťazec methodName = node.getText (); if (Character.isUpperCase (methodName.charAt (0))) {String error = String.format ("Metóda% s je veľká!", methodName); errors.add (chyba); }}}

5.4. Testovanie

Teraz urobme niekoľko testov. Najskôr zostrojíme lexer:

Reťazec javaClassContent = "verejná trieda SampleClass {void DoSomething () {}}"; Java8Lexer java8Lexer = nový Java8Lexer (CharStreams.fromString (javaClassContent));

Potom vykonáme inštanciu syntaktického analyzátora:

CommonTokenStream tokeny = nový CommonTokenStream (lexer); Analyzátor Java8Parser = nový Java8Parser (tokeny); Strom ParseTree = parser.compilationUnit ();

A potom chodec a poslucháč:

Walker ParseTreeWalker = nový ParseTreeWalker (); Poslucháč UppercaseMethodListener = nový UppercaseMethodListener ();

Nakoniec povieme ANTLR, aby prešlo našou triedou vzoriek:

walker.walk (poslucháč, strom); assertThat (listener.getErrors (). size (), je (1)); assertThat (listener.getErrors (). get (0), is ("Metóda DoSomething je veľká!"));

6. Budovanie našej gramatiky

Teraz skúsme niečo trochu zložitejšie, napríklad analýzu súborov protokolu:

2018-máj-05 14:20:18 INFO došlo k nejakej chybe 2018-máj-05 14:20:19 INFO ešte ďalšia chyba 2018-máj-05 14:20:20 INFO začala nejaká metóda 2018-máj-05 14:20 : 21 DEBUG začala iná metóda 2018-máj-05 14:20:21 DEBUG vstúpila do úžasnej metódy 2018-máj-05 14:20:24 CHYBA Stala sa zlá vec

Pretože máme vlastný formát protokolu, budeme si najskôr musieť vytvoriť vlastnú gramatiku.

6.1. Pripravte si gramatický súbor

Najskôr sa pozrime, či môžeme vytvoriť mentálnu mapu toho, ako každý riadok denníka vyzerá v našom súbore.

Alebo ak pôjdeme ešte o jednu úroveň hlbšie, môžeme povedať:

:= …

A tak ďalej. Je dôležité zvážiť to, aby sme sa mohli rozhodnúť, na akej úrovni podrobnosti chceme text analyzovať.

Gramatický súbor je v podstate sada pravidiel lexeru a syntaktického analyzátora. Jednoducho povedané, lexerove pravidlá popisujú syntax gramatiky, zatiaľ čo pravidlá syntaktického analyzátora popisujú sémantiku.

Začnime definovaním fragmentov, ktoré sú opakovane použiteľné stavebné bloky pre pravidlá lexeru.

fragment DIGIT: [0-9]; fragment TWODIGIT: DIGIT DIGIT; fragment PÍSMENO: [A-Za-z];

Ďalej definujeme zostávajúce pravidlá lexeru:

DÁTUM: TWODIGIT TWODIGIT '-' PÍSMENO LIST PÍSMENO '-' TWODIGIT; TIME: TWODIGIT ':' TWODIGIT ':' TWODIGIT; TEXT: LIST +; CRLF: „ '\ n' | ';

Po zavedení týchto stavebných blokov môžeme zostaviť pravidlá syntaktickej analýzy pre základnú štruktúru:

log: vstup +; záznam: časová pečiatka '' úroveň '' správa CRLF;

A potom pridáme podrobnosti pre časová značka:

časová pečiatka: DATE '' TIME;

Pre úrovni:

úroveň: 'CHYBA' | 'INFO' | „DEBUG“;

A pre správa:

správa: (TEXT | '') +;

A je to! Naša gramatika je pripravená na použitie. Dáme pod src / main / antlr4 adresár ako predtým.

6.2.Generovať zdroje

Pripomeňme, že je to len rýchle balíček mvna tým sa vytvorí niekoľko súborov ako LogBaseListener, LogParser, a tak ďalej, na základe názvu našej gramatiky.

6.3. Vytvorte si náš poslucháč protokolov

Teraz sme pripravení implementovať nášho poslucháča, ktorý nakoniec použijeme na analýzu súboru protokolu do objektov Java.

Začnime teda jednoduchou modelovou triedou pre položku protokolu:

verejná trieda LogEntry {súkromná úroveň LogLevel; súkromná reťazcová správa; súkromná časová značka LocalDateTime; // zakladatelia a zakladatelia}

Teraz musíme podtriedu LogBaseListener ako predtým:

verejná trieda LogListener rozširuje LogBaseListener {súkromné ​​položky zoznamu = nový ArrayList (); súkromný prúd LogEntry;

prúd bude držať aktuálny riadok denníka, ktorý môžeme znova inicializovať zakaždým, keď zadáme a logEntry, opäť na základe našej gramatiky:

 @Override public void enterEntry (LogParser.EntryContext ctx) {this.current = nový LogEntry (); }

Ďalej použijeme enterTimestamp, enterLevel, a enterMessage pre nastavenie príslušnej LogEntry vlastnosti:

 @Override public void enterTimestamp (LogParser.TimestampContext ctx) {this.current.setTimestamp (LocalDateTime.parse (ctx.getText (), DEFAULT_DATETIME_FORMATTER)); } @Override public void enterMessage (LogParser.MessageContext ctx) {this.current.setMessage (ctx.getText ()); } @Override public void enterLevel (LogParser.LevelContext ctx) {this.current.setLevel (LogLevel.valueOf (ctx.getText ())); }

A nakoniec použijeme exitEntry metóda na vytvorenie a pridanie našej novej LogEntry:

 @Override public void exitLogEntry (LogParser.EntryContext ctx) {this.entries.add (this.current); }

Všimnite si, mimochodom, že náš LogListener nie je bezpečný pre vlákna!

6.4. Testovanie

A teraz môžeme testovať znova, ako sme to robili naposledy:

@Test public void whenLogContainsOneErrorLogEntry_thenOneErrorIsReturned () vyvolá výnimku {String logLine; // inštancia lexeru, parseru a chodca LogListener listener = nový LogListener (); walker.walk (poslucháč, logParser.log ()); Záznam LogEntry = listener.getEntries (). Get (0); assertThat (entry.getLevel (), je (LogLevel.ERROR)); assertThat (entry.getMessage (), je ("Stala sa zlá vec")); assertThat (entry.getTimestamp (), je (LocalDateTime.of (2018,5,5,14,20,24)))); }

7. Záver

V tomto článku sme sa zamerali na to, ako vytvoriť vlastný syntaktický analyzátor pre vlastný jazyk pomocou ANTLR.

Tiež sme videli, ako využiť existujúce gramatické súbory a použiť ich na veľmi jednoduché úlohy, ako je preklad kódu.

Ako vždy, všetky tu použité kódy nájdete na GitHub.


$config[zx-auto] not found$config[zx-overlay] not found