ANTLR: Is er een eenvoudig voorbeeld?

Ik zou graag aan de slag willen met ANTLR, maar na een paar uur de voorbeelden door te hebben genomen op antlr.orgsite, kan ik nog steeds geen duidelijk begrip krijgen van de grammatica van het Java-proces.

Is er een eenvoudig voorbeeld, zoiets als een rekenmachine met vier bewerkingen die is geïmplementeerd met ANTLR die de parserdefinitie doorloopt en helemaal naar de Java-broncode gaat?


Antwoord 1, autoriteit 100%

Opmerking: dit antwoord is voor ANTLR3! Als je op zoek bent naar een ANTLR4voorbeeld, dan is deze V&Alaat zien hoe u een eenvoudige expressie-parser en evaluator maakt met behulp van ANTLR4.


Je maakt eerst een grammatica. Hieronder vindt u een kleine grammatica die u kunt gebruiken om uitdrukkingen te evalueren die zijn gebouwd met behulp van de 4 elementaire wiskundige operatoren: +, -, * en /. U kunt uitdrukkingen ook groeperen met haakjes.

Merk op dat deze grammatica slechts een zeer eenvoudige grammatica is: hij verwerkt geen unaire operatoren (de min in: -1+9) of decimalen zoals .99 (zonder voorloopnummer), om maar twee tekortkomingen te noemen. Dit is slechts een voorbeeld waar u zelf aan kunt werken.

Dit is de inhoud van het grammaticabestand Exp.g:

grammar Exp;
/* This will be the entry point of our parser. */
eval
    :    additionExp
    ;
/* Addition and subtraction have the lowest precedence. */
additionExp
    :    multiplyExp 
         ( '+' multiplyExp 
         | '-' multiplyExp
         )* 
    ;
/* Multiplication and division have a higher precedence. */
multiplyExp
    :    atomExp
         ( '*' atomExp 
         | '/' atomExp
         )* 
    ;
/* An expression atom is the smallest part of an expression: a number. Or 
   when we encounter parenthesis, we're making a recursive call back to the
   rule 'additionExp'. As you can see, an 'atomExp' has the highest precedence. */
atomExp
    :    Number
    |    '(' additionExp ')'
    ;
/* A number: can be an integer value, or a decimal value */
Number
    :    ('0'..'9')+ ('.' ('0'..'9')+)?
    ;
/* We're going to ignore all white space characters */
WS  
    :   (' ' | '\t' | '\r'| '\n') {$channel=HIDDEN;}
    ;

(Parserregels beginnen met een kleine letter en lexerregels beginnen met een hoofdletter)

Nadat je de grammatica hebt gemaakt, wil je er een parser en lexer van maken. Download de ANTLR jaren sla deze op in dezelfde map als uw grammaticabestand.

Voer het volgende commando uit op je shell/opdrachtprompt:

java -cp antlr-3.2.jar org.antlr.Tool Exp.g

Het zou geen foutmelding moeten geven en de bestanden ExpLexer.java, ExpParser.javaen Exp.tokenszouden nu moeten worden gegenereerd .

Maak deze testklasse om te zien of alles goed werkt:

import org.antlr.runtime.*;
public class ANTLRDemo {
    public static void main(String[] args) throws Exception {
        ANTLRStringStream in = new ANTLRStringStream("12*(5-6)");
        ExpLexer lexer = new ExpLexer(in);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        ExpParser parser = new ExpParser(tokens);
        parser.eval();
    }
}

en compileren:

// *nix/MacOS
javac -cp .:antlr-3.2.jar ANTLRDemo.java
// Windows
javac -cp .;antlr-3.2.jar ANTLRDemo.java

en voer het dan uit:

// *nix/MacOS
java -cp .:antlr-3.2.jar ANTLRDemo
// Windows
java -cp .;antlr-3.2.jar ANTLRDemo

Als alles goed gaat, wordt er niets naar de console afgedrukt. Dit betekent dat de parser geen fout heeft gevonden. Wanneer u "12*(5-6)"verandert in "12*(5-6"en vervolgens opnieuw compileert en uitvoert, moet het volgende worden afgedrukt:

line 0:-1 mismatched input '<EOF>' expecting ')'

Ok, nu willen we een stukje Java-code aan de grammatica toevoegen, zodat de parser daadwerkelijk iets nuttigs doet. U kunt code toevoegen door {en }in uw grammatica te plaatsen met wat gewone Java-code erin.

Maar eerst: alle parserregels in het grammaticabestand moeten een primitieve dubbele waarde retourneren. U kunt dat doen door returns [double value]toe te voegen na elke regel:

grammar Exp;
eval returns [double value]
    :    additionExp
    ;
additionExp returns [double value]
    :    multiplyExp 
         ( '+' multiplyExp 
         | '-' multiplyExp
         )* 
    ;
// ...

wat weinig uitleg behoeft: van elke regel wordt verwacht dat deze een dubbele waarde retourneert. Om nu te “interactie” met de geretourneerde waarde double value(die NIET in een gewoon Java-codeblok {...}zit) vanuit een codeblok, zul je moet een dollarteken worden toegevoegd vóór value:

grammar Exp;
/* This will be the entry point of our parser. */
eval returns [double value]                                                  
    :    additionExp { /* plain code block! */ System.out.println("value equals: "+$value); }
    ;
// ...

Hier is de grammatica, maar nu met de Java-code toegevoegd:

grammar Exp;
eval returns [double value]
    :    exp=additionExp {$value = $exp.value;}
    ;
additionExp returns [double value]
    :    m1=multiplyExp       {$value =  $m1.value;} 
         ( '+' m2=multiplyExp {$value += $m2.value;} 
         | '-' m2=multiplyExp {$value -= $m2.value;}
         )* 
    ;
multiplyExp returns [double value]
    :    a1=atomExp       {$value =  $a1.value;}
         ( '*' a2=atomExp {$value *= $a2.value;} 
         | '/' a2=atomExp {$value /= $a2.value;}
         )* 
    ;
atomExp returns [double value]
    :    n=Number                {$value = Double.parseDouble($n.text);}
    |    '(' exp=additionExp ')' {$value = $exp.value;}
    ;
Number
    :    ('0'..'9')+ ('.' ('0'..'9')+)?
    ;
WS  
    :   (' ' | '\t' | '\r'| '\n') {$channel=HIDDEN;}
    ;

en aangezien onze evalregel nu een dubbele retourneert, verander je ANTLRDemo.java in dit:

import org.antlr.runtime.*;
public class ANTLRDemo {
    public static void main(String[] args) throws Exception {
        ANTLRStringStream in = new ANTLRStringStream("12*(5-6)");
        ExpLexer lexer = new ExpLexer(in);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        ExpParser parser = new ExpParser(tokens);
        System.out.println(parser.eval()); // print the value
    }
}

Genereer (opnieuw) een nieuwe lexer en parser uit uw grammatica (1), compileer alle klassen (2) en voer ANTLRDemo (3) uit:

// *nix/MacOS
java -cp antlr-3.2.jar org.antlr.Tool Exp.g   // 1
javac -cp .:antlr-3.2.jar ANTLRDemo.java      // 2
java -cp .:antlr-3.2.jar ANTLRDemo            // 3
// Windows
java -cp antlr-3.2.jar org.antlr.Tool Exp.g   // 1
javac -cp .;antlr-3.2.jar ANTLRDemo.java      // 2
java -cp .;antlr-3.2.jar ANTLRDemo            // 3

en je ziet nu het resultaat van de uitdrukking 12*(5-6)afgedrukt op je console!

Nogmaals: dit is een heel korte uitleg. Ik moedig u aan om door de ANTLR-wikite bladeren en enkele tutorials en/of speel een beetje met wat ik zojuist heb gepost.

Veel succes!

BEWERKEN:

Dit berichtlaat zien hoe je het bovenstaande voorbeeld kunt uitbreiden zodat een Map<String, Double>kan worden opgegeven die variabelen bevat in de opgegeven uitdrukking.

Om deze code te laten werken met een huidige versie van Antlr (juni 2014) moest ik een paar wijzigingen aanbrengen. ANTLRStringStreammoest ANTLRInputStreamworden, de geretourneerde waarde die nodig was om te veranderen van parser.eval()in parser.eval().value, en ik moest de WS-clausule aan het einde verwijderen, omdat attribuutwaarden zoals $channelniet langer mogen verschijnen in lexer-acties.


Antwoord 2, autoriteit 4%

ANTLR mega-tutorialdoor Gabriele Tomassetti is erg nuttig

Het heeft grammaticavoorbeelden, voorbeelden van bezoekers in verschillende talen (Java, JavaScript, C# en Python) en vele andere dingen. Sterk aanbevolen.

EDIT: andere nuttige artikelen van Gabriele Tomassetti op ANTLR


Antwoord 3

Voor Antlr 4 staat het proces voor het genereren van Java-code hieronder:-

java -cp antlr-4.5.3-complete.jar org.antlr.v4.Tool Exp.g

Werk uw jar-naam in classpath dienovereenkomstig bij.


Antwoord 4

versie 4.7.1 was iets anders:
voor import:

import org.antlr.v4.runtime.*;

voor het hoofdsegment – let op de CharStreams:

CharStream in = CharStreams.fromString("12*(5-6)");
ExpLexer lexer = new ExpLexer(in);
CommonTokenStream tokens = new CommonTokenStream(lexer);
ExpParser parser = new ExpParser(tokens);

Antwoord 5

Op https://github.com/BITPlan/com.bitplan.antlrvindt u een ANTLR java-bibliotheek met enkele nuttige helperklassen en een paar volledige voorbeelden. Het is klaar voor gebruik met maven en als je van eclipse en maven houdt.

https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/main/antlr4/com/bitplan/exp/Exp.g4

is een eenvoudige expressietaal die bewerkingen kan vermenigvuldigen en optellen.
https: //github.com/BITPlan/com.bitplan.antlr/blob/master/src/test/java/com/bitplan/antlr/TestExpParser.javaheeft de bijbehorende unit-tests ervoor.

https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/main/antlr4/com/bitplan/iri/IRIParser.g4is een IRI-parser die is opgesplitst in de drie delen:

  1. grammatica parseren
  2. lexer-grammatica
  3. geïmporteerde LexBasic-grammatica

https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/test/java/com/bitplan/antlr/TestIRIParser.java
heeft de unit tests ervoor.

Persoonlijk vond ik dit het lastigste om goed te krijgen. Zie http://wiki.bitplan.com/index.php/ANTLR_maven_plugin

https:/ /github.com/BITPlan/com.bitplan.antlr/tree/master/src/main/antlr4/com/bitplan/expr

bevat nog drie voorbeelden die zijn gemaakt voor een prestatieprobleem van ANTLR4 in een eerdere versie. In de tussentijd is dit probleem opgelost als de testcase https://github.com/BITPlan/com.bitplan.antlr/blob/master/src/test/java/com/bitplan/antlr/TestIssue994.javalaat zien.

Other episodes