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.