Java med ANTLR

1. Oversikt

I denne opplæringen vil vi gjøre en rask oversikt over ANTLR-parsergeneratoren og vise noen virkelige applikasjoner.

2. ANTLR

ANTLR (ANother Tool for Language Recognition) er et verktøy for behandling av strukturert tekst.

Det gjør dette ved å gi oss tilgang til språkbehandling primitiver som lexers, grammatikk og parsers, samt kjøretid for å behandle tekst mot dem.

Det brukes ofte til å bygge verktøy og rammer. For eksempel bruker Hibernate ANTLR til å analysere og behandle HQL-spørsmål, og Elasticsearch bruker det til smertefri.

Og Java er bare en binding. ANTLR tilbyr også bindinger for C #, Python, JavaScript, Go, C ++ og Swift.

3. Konfigurasjon

Først av alt, la oss starte med å legge til antlr-kjøretid til vår pom.xml:

 org.antlr antlr4-kjøretid 4.7.1 

Og også antlr-maven-plugin:

 org.antlr antlr4-maven-plugin 4.7.1 antlr4 

Det er pluginens jobb å generere kode fra grammatikkene vi spesifiserer.

4. Hvordan fungerer det?

I utgangspunktet, når vi vil lage parseren ved hjelp av ANTLR Maven-plugin, må vi følge tre enkle trinn:

  • utarbeide en grammatikkfil
  • generere kilder
  • lage lytteren

Så, la oss se disse trinnene i aksjon.

5. Bruke en eksisterende grammatikk

La oss først bruke ANTLR til å analysere kode for metoder med dårlig foringsrør:

offentlig klasse SampleClass {offentlig ugyldig DoSomethingElse () {// ...}}

Enkelt sagt, vi validerer at alle metodenavn i koden vår begynner med små bokstaver.

5.1. Forbered en grammatikkfil

Det som er hyggelig er at det allerede er flere grammatikkfiler der ute som kan passe til våre formål.

La oss bruke Java8.g4 grammatikkfilen som vi fant i ANTLRs Github grammatikk repo.

Vi kan lage src / main / antlr4 katalogen og last den ned der.

5.2. Generer kilder

ANTLR fungerer ved å generere Java-kode som tilsvarer grammatikkfilene vi gir den, og maven-pluginet gjør det enkelt:

mvn-pakke

Som standard genererer dette flere filer under mål / genererte kilder / antlr4 katalog:

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

Legg merke til at navnene på disse filene er basert på navnet på grammatikkfilen.

Vi trenger Java8Lexer og Java8Parser filer senere når vi tester. For nå trenger vi imidlertid Java8BaseListener for å skape vår MetodeUppercaseListener.

5.3. Å skape MetodeUppercaseListener

Basert på Java8-grammatikken vi brukte, Java8BaseListener har flere metoder som vi kan overstyre, hver og en tilsvarer en overskrift i grammatikkfilen.

For eksempel definerer grammatikken metodenavnet, parameterlisten og kaster-setningen slik:

methodDeclarator: Identifier '(' formalParameterList? ')' dims? ;

Og så Java8BaseListener har en metode enterMethodDeclarator som vil bli påkalt hver gang dette mønsteret oppstår.

Så la oss overstyre enterMethodDeclarator, trekk ut Identifikator, og utfør vår sjekk:

offentlig klasse UppercaseMethodListener utvider Java8BaseListener {private List-feil = ny ArrayList (); // ... getter for feil @ Override offentlig ugyldig enterMethodDeclarator (Java8Parser.MethodDeclaratorContext ctx) {TerminalNode node = ctx.Identifier (); String methodName = node.getText (); hvis (Character.isUpperCase (methodName.charAt (0))) {String error = String.format ("Method% s isercased!", methodName); error.add (feil); }}}

5.4. Testing

La oss nå teste. Først konstruerer vi lexeren:

String javaClassContent = "public class SampleClass {void DoSomething () {}}"; Java8Lexer java8Lexer = ny Java8Lexer (CharStreams.fromString (javaClassContent));

Deretter instanserer vi parseren:

CommonTokenStream tokens = nye CommonTokenStream (lexer); Java8Parser parser = ny Java8Parser (tokens); ParseTree-tre = parser.compilationUnit ();

Og så, rullatoren og lytteren:

ParseTreeWalker walker = ny ParseTreeWalker (); UppercaseMethodListener lytter = ny UppercaseMethodListener ();

Til slutt ber vi ANTLR om å gå gjennom prøveklassen vår:

walker.walk (lytter, tre); assertThat (listener.getErrors (). størrelse (), er (1)); assertThat (listener.getErrors (). get (0), is ("Method DoSomething is versal!"));

6. Bygg grammatikken vår

La oss nå prøve noe litt mer komplisert, som å analysere loggfiler:

2018-mai-05 14:20:18 INFO noen feil oppstod 2018-mai-05 14:20:19 INFO enda en feil 2018-mai-05 14:20:20 INFO noen metode startet 2018-mai-05 14:20 : 21 DEBUG en annen metode startet 2018-mai-05 14:20:21 DEBUG inn i fantastisk metode 2018-mai-05 14:20:24 FEIL Dårlig ting skjedde

Fordi vi har et tilpasset loggformat, må vi først lage vår egen grammatikk.

6.1. Forbered en grammatikkfil

La oss først se om vi kan lage et mentalt kart over hvordan hver logglinje ser ut i filen vår.

Eller hvis vi går et dyp til, kan vi si:

:= …

Og så videre. Det er viktig å vurdere dette slik at vi kan bestemme på hvilket granularitetsnivå vi vil analysere teksten.

En grammatikkfil er i utgangspunktet et sett med lexer- og parserregler. Enkelt sagt, lexer-regler beskriver syntaksen til grammatikken mens parser-reglene beskriver semantikken.

La oss starte med å definere fragmenter som er gjenbrukbare byggesteiner for lexer-regler.

fragment DIGIT: [0-9]; fragment TWODIGIT: DIGIT DIGIT; fragment BREV: [A-Za-z];

Deretter la oss definere restene lexer regler:

DATO: TWODIGIT TWODIGIT '-' BREVBREV '-' TWODIGIT; TID: TWODIGIT ':' TWODIGIT ':' TWODIGIT; TEKST: BREV +; CRLF: '\ r'? '\ n' | '\ r';

Med disse byggesteinene på plass kan vi bygge parserregler for den grunnleggende strukturen:

logg: oppføring +; oppføring: tidsstempel '' nivå '' melding CRLF;

Og så legger vi til detaljene for tidsstempel:

tidsstempel: DATE '' TID;

Til nivå:

nivå: 'FEIL' | 'INFO' | 'DEBUG';

Og for beskjed:

melding: (TEKST | '') +;

Og det er det! Grammatikken vår er klar til bruk. Vi vil legge det under src / main / antlr4 katalog som før.

6.2.Generer kilder

Husk at dette bare er en rask mvn-pakke, og at dette vil opprette flere filer som LogBaseListener, LogParser, og så videre, basert på navnet på grammatikken vår.

6.3. Lag vår logglytter

Nå er vi klare til å implementere lytteren vår, som vi til slutt bruker til å analysere en loggfil i Java-objekter.

Så la oss starte med en enkel modellklasse for loggoppføringen:

offentlig klasse LogEntry {privat LogLevel-nivå; privat strengmelding; privat LocalDateTime-tidsstempel; // getters og setters}

Nå må vi underklasse LogBaseListener som før:

offentlig klasse LogListener utvider LogBaseListener {private Listeoppføringer = ny ArrayList (); privat LogEntry gjeldende;

nåværende vil holde på gjeldende logglinje, som vi kan starte på nytt hver gang vi går inn i loggføring, igjen basert på grammatikken vår:

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

Deretter bruker vi enterTimestamp, enterLevel, og enterMessage for innstilling av passende Logg inn eiendommer:

 @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 ())); }

Og til slutt, la oss bruke exitEntry metode for å lage og legge til vår nye Logg inn:

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

Merk forresten at vår LogListener er ikke trådsikker!

6.4. Testing

Og nå kan vi teste igjen som forrige gang:

@Test offentlig ugyldig nårLogContainsOneErrorLogEntry_thenOneErrorIsReturned () kaster Unntak {String logLine; // instantiere lexer, parser og rullator LogListener lytter = ny LogListener (); walker.walk (lytter, logParser.log ()); LogEntry entry = listener.getEntries (). Get (0); assertThat (entry.getLevel (), er (LogLevel.ERROR)); assertThat (entry.getMessage (), er ("Dårlig ting skjedde")); assertThat (entry.getTimestamp (), er (LocalDateTime.of (2018,5,5,14,20,24))); }

7. Konklusjon

I denne artikkelen fokuserte vi på hvordan du oppretter en tilpasset parser for eget språk ved hjelp av ANTLR.

Vi så også hvordan du bruker eksisterende grammatikkfiler og bruker dem på veldig enkle oppgaver som kodeføring.

Som alltid kan all koden som brukes her, bli funnet på GitHub.


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