Java ANTLR-rel
1. Áttekintés
Ebben az oktatóanyagban rövid áttekintést készítünk az ANTLR elemzőgenerátorról, és bemutatunk néhány valós alkalmazást.
2. ANTLR
Az ANTLR (ANother Tool for Language Recognition) a strukturált szöveg feldolgozásának eszköze.
Ezt úgy teszi meg, hogy hozzáférést biztosít számunkra a nyelvfeldolgozó primitívekhez, például a lexerekhez, a nyelvtanokhoz és a parserekhez, valamint a futásidőhöz, hogy szöveget dolgozzunk fel velük szemben.
Gyakran használják eszközök és keretek felépítésére. Például a Hibernate ANTLR-t használ a HQL-lekérdezések elemzéséhez és feldolgozásához, az Elasticsearch pedig a Painless-hez.
A Java pedig csak egy kötés. Az ANTLR emellett C #, Python, JavaScript, Go, C ++ és Swift kötéseket is kínál.
3. Konfiguráció
Először is kezdjük azzal, hogy hozzáadjuk az antlr-futásidőt a sajátunkhoz pom.xml:
org.antlr antlr4-futásidejű 4.7.1
És az antlr-maven-plugin is:
org.antlr antlr4-maven-plugin 4.7.1 antlr4
A plugin feladata kódot generálni az általunk megadott nyelvtanokból.
4. Hogyan működik?
Alapvetően, amikor az elemzőt az ANTLR Maven beépülő modul használatával szeretnénk létrehozni, három egyszerű lépést kell végrehajtanunk:
- készítsen nyelvtani fájlt
- forrásokat generál
- hozza létre a hallgatót
Tehát nézzük meg ezeket a lépéseket.
5. Meglévő nyelvtan használata
Először használjuk az ANTLR-t a rossz betűs metódusok kódjának elemzéséhez:
public class SampleClass {public void DoSomethingElse () {// ...}}
Egyszerűen fogalmazva, ellenőrizni fogjuk, hogy a kódunkban szereplő összes módszer neve kisbetűvel kezdődik-e.
5.1. Készítsen elő egy nyelvtani fájlt
Az a jó, hogy már létezik több nyelvtani fájl, amely megfelelhet céljainknak.
Használjuk a Java8.g4 nyelvtani fájlt, amelyet az ANTLR Github nyelvtani repójában találtunk.
Hozhatjuk létre a src / main / antlr4 könyvtárba, és töltse le oda.
5.2. Források létrehozása
Az ANTLR az általunk adott nyelvtani fájloknak megfelelő Java-kód létrehozásával működik, és a maven plugin megkönnyíti:
mvn csomag
Alapértelmezés szerint ez több fájlt generál a target / generált források / antlr4 Könyvtár:
- Java8.interp
- Java8Listener.java
- Java8BaseListener.java
- Java8Lexer.java
- Java8Lexer.interp
- Java8Parser.java
- Java8.tokens
- Java8Lexer.tokens
Vegye figyelembe, hogy ezeknek a fájloknak a neve a nyelvtani fájl nevén alapulnak.
Szükségünk lesz a Java8Lexer és a Java8Parser fájlokat később, amikor teszteljük. Most azonban szükségünk van a Java8BaseListener amiért létrehoztuk MethodUppercaseListener.
5.3. Teremtés MethodUppercaseListener
Az általunk használt Java8 nyelvtan alapján Java8BaseListener többféle módszerrel rendelkezik, amelyeket felülírhatunk, amelyek mindegyike megfelel a nyelvtani fájl fejlécének.
Például a nyelvtan meghatározza a metódus nevét, a paraméterlistát és a dob záradékot így:
methodDeclarator: Azonosító '(' formalParameterList? ')' tompul? ;
És aztán Java8BaseListener van egy módszere enterMethodDeclarator amelyek minden alkalommal meghívásra kerülnek, amikor erre a mintára kerül sor.
Tehát, írjuk felül enterMethodDeclarator, húzza ki a Azonosító, és végezze el az ellenőrzésünket:
public class UppercaseMethodListener kiterjeszti a Java8BaseListener {private List hibák = új ArrayList (); // ... getter for errors @Orride public void enterMethodDeclarator (Java8Parser.MethodDeclaratorContext ctx) {TerminalNode node = ctx.Identifier (); String methodName = csomópont.getText (); if (Character.isUpperCase (methodName.charAt (0))) {String error = String.format ("A (z)% s metódus felsőbetűs!", methodName); hibák.add (hiba); }}}
5.4. Tesztelés
Most végezzünk néhány tesztet. Először elkészítjük a lexert:
String javaClassContent = "public class SampleClass {void DoSomething () {}}"; Java8Lexer java8Lexer = új Java8Lexer (CharStreams.fromString (javaClassContent));
Ezután példázzuk az elemzőt:
CommonTokenStream tokenek = új CommonTokenStream (lexer); Java8Parser parser = új Java8Parser (tokenek); ParseTree tree = parser.compilationUnit ();
És akkor a sétáló és a hallgató:
ParseTreeWalker walker = új ParseTreeWalker (); UppercaseMethodListener listener = new UppercaseMethodListener ();
Végül azt mondjuk az ANTLR-nek, hogy járja végig a minta osztályunkat:
walker.walk (hallgató, fa); assertThat (listener.getErrors (). size (), is (1)); assertThat (listener.getErrors (). get (0), is ("A metódus DoSomething nagybetűs!"));
6. Nyelvtanunk építése
Most próbáljunk meg egy kicsit összetettebbet, például a naplófájlok elemzését:
2018-május-05 14:20:18 INFO valami hiba történt 2018-május-05 14:20:19 INFO még egy hiba 2018-május-05 14:20:20 INFO valamilyen módszer elindult 2018-május-05 14:20 : 21 DEBUG egy másik módszer indult 2018-május-05 14:20:21 DEBUG-félelmetes módszerbe lépés-2018-május-05 14:20:24 HIBA Rossz dolog történt
Mivel egyéni naplóformátummal rendelkezünk, először saját nyelvtanunkat kell létrehoznunk.
6.1. Készítsen elő egy nyelvtani fájlt
Először nézzük meg, hogy tudunk-e létrehozni egy mentális térképet arról, hogyan néznek ki az egyes naplósorok a fájlunkban.
Vagy ha még egy szinttel elmegyünk, akkor azt mondhatjuk:
:= …
Stb. Fontos ezt figyelembe venni, hogy eldönthessük, hogy a részletesség mely szintjén akarjuk elemezni a szöveget.
A nyelvtani fájl alapvetően lexer és értelmező szabályok halmaza. Egyszerűen fogalmazva, a lexer szabályok leírják a nyelvtan szintaxisát, míg az értelmező szabályok a szemantikát.
Kezdjük azzal, hogy definiáljuk a töredékeket újrafelhasználható építőelemek a lexer szabályokhoz.
töredék DIGIT: [0-9]; töredék TWODIGIT: DIGIT DIGIT; töredék LETTER: [A-Za-z];
Ezután definiáljuk a maradék lexer szabályokat:
Dátum: TWODIGIT TWODIGIT ”-„ LEVEL LEVEL LETTER ”-„ TWODIGIT; IDŐ: TWODIGIT ”:„ TWODIGIT ”:„ TWODIGIT; SZÖVEG: LETTER +; CRLF: '\ r'? '\ n' | '\ r';
Ezekkel az építőelemekkel a helyén felépíthetünk elemző szabályokat az alapstruktúrához:
napló: bejegyzés +; bejegyzés: időbélyegző '' level '' üzenet CRLF;
És akkor hozzáadjuk a részleteket időbélyeg:
időbélyeg: DATE '' TIME;
Mert szint:
szint: „HIBA” „INFO” „DEBUG”;
És azért üzenet:
üzenet: (TEXT | '') +;
És ez az! Nyelvtanunk használatra kész. Az alá fogjuk tenni src / main / antlr4 könyvtárat, mint korábban.
6.2.Források létrehozása
Emlékezzünk vissza, hogy ez csak egy gyors mvn csomag, és hogy ez több fájlt hoz létre, mint például LogBaseListener, LogParser, és így tovább, a nyelvtanunk neve alapján.
6.3. Hozzon létre naplófigyelőt
Most készen állunk a hallgatónk megvalósítására, amelyet végül egy naplófájl Java objektumokba történő elemzésére fogunk használni.
Tehát kezdjük a naplóbejegyzés egyszerű modellosztályával:
nyilvános osztály LogEntry {privát LogLevel szint; privát karakterlánc üzenet; privát LocalDateTime időbélyeg; // szerelők és beállítók}
Most alosztályt kell adnunk LogBaseListener mint azelőtt:
public class A LogListener kiterjeszti a LogBaseListener {privát lista bejegyzések = új ArrayList (); privát LogEntry aktuális;
jelenlegi megtartja az aktuális naplóvonalat, amelyet minden egyes alkalommal újra inicializálhatunk, amikor belépünk a logEntry, ismét a nyelvtanunk alapján:
@Orride public void enterEntry (LogParser.EntryContext ctx) {this.current = new LogEntry (); }
Ezután felhasználjuk enterTimestamp, enterLevel, és enterMessage a megfelelő beállításához LogEntry tulajdonságok:
@Orride public void enterTimestamp (LogParser.TimestampContext ctx) {this.current.setTimestamp (LocalDateTime.parse (ctx.getText (), DEFAULT_DATETIME_FORMATTER)); } @Orride public void enterMessage (LogParser.MessageContext ctx) {this.current.setMessage (ctx.getText ()); } @Orride public void enterLevel (LogParser.LevelContext ctx) {this.current.setLevel (LogLevel.valueOf (ctx.getText ())); }
És végül használjuk a exitEntry metódus létrehozása és hozzáadása az új LogEntry:
@Orride public void exitLogEntry (LogParser.EntryContext ctx) {this.entries.add (this.current); }
Egyébként vegye figyelembe, hogy a mi LogListener nem szálkás!
6.4. Tesztelés
És most újra tesztelhetünk, mint legutóbb:
@Test public void whenLogContainsOneErrorLogEntry_thenOneErrorIsReturned () dobja a Kivételt {String logLine; // példázza a lexert, az elemzőt és a sétálót LogListener listener = new LogListener (); walker.walk (figyelő, logParser.log ()); LogEntry bejegyzés = listener.getEntries (). Get (0); assertThat (entry.getLevel (), van (LogLevel.ERROR)); assertThat (entry.getMessage (), is ("Rossz dolog történt")); assertThat (entry.getTimestamp (), (LocalDateTime.of (2018,5,5,14,20,24))); }
7. Következtetés
Ebben a cikkben arra összpontosítottunk, hogy miként lehet létrehozni az egyéni értelmezőt a saját nyelvéhez az ANTLR segítségével.
Láttuk azt is, hogyan kell használni a meglévő nyelvtani fájlokat, és hogyan lehet ezeket nagyon egyszerű feladatokra alkalmazni, például a kód szöszölésére.
Mint mindig, az itt használt összes kód megtalálható a GitHubon.