Mach das letzte Mal eine Sprache! Ich habe über die Grammatik eines einfachen Taschenrechners in [Erstellen eines einfachen Taschenrechners ①] nachgedacht (https://qiita.com/toru0408/items/9d7263bce7f9a4bdce13). Lassen Sie uns diesmal ein Programm erstellen, das tatsächlich funktioniert.
Wenn Sie die Formel in die Konsole eingeben, wird sie berechnet und der numerische Wert angezeigt.
> 2 + 3 * 5
17
> 100 * 1.08
108.00
Ich werde die obigen vier Punkte beschreiben. Wie schreibe ich Java CC im Detail https://javacc.org/javaccgrm ↑ Es ist auf der Website geschrieben (Englisch).
Ich habe das folgende BNF definiert, das ich beim letzten Mal wie folgt definiert habe.
BNF mit einer einfachen Formel
<Formel> ::= <加算Formel>
<Additionsformel> ::= <Multiplikationsformel> ( <Additionsoperator> <Multiplikationsformel> )*
<Multiplikationsformel> ::= <Einzelter Begriff> ( <Operator multiplizieren> <Einzelter Begriff> )*
<Einzelter Begriff> ::= <Halterung öffnen> <Formel> <Verschlusshalterung> | <Dezimaldarstellung>
<Additionsoperator> ::= "+" | "-"
<Operator multiplizieren> ::= "*" | "/" | "%"
<Halterung öffnen> ::= "("
<Verschlusshalterung> ::= ")"
<Dezimaldarstellung> ::= (<Zahlen>)+ ("." (<Zahlen>)+)?
<Zahlen> ::= "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "0"
Ich werde dies der Reihe nach in die JJT-Datei einfügen.
Definieren wir zunächst den
< ADD_OP : "+"|"-" >
Es wird beschrieben als. Die Bedeutung der Symbole entspricht fast der Erklärung der in BNF verwendeten Symbole. http://qiita.com/toru0408/items/9d7263bce7f9a4bdce13#bnf Bitte beziehen Sie sich auf. Wenn Sie auf diese Weise alles durch die Javacc-Beschreibung ersetzen
< ADD_OP : "+" | "-" >
< MUL_OP : "*" | "/" | "%" >
< OPEN_BRACKET : "(" >
< CLOSE_BRACKET : ")" >
< DECIMAL : (< DIGIT >)+ ("." (< DIGIT >)+)? >
< DIGIT : [ "0"-"9" ] >
Es wird sein. "[" 0 "-" 9 "]" bezieht sich auf alle Zeichen von "0" bis "9" in der Zeichencodetabelle. Sie können auch "[" 0 "," 1 "," 2 "," 3 "," 4 "," 5 "," 6 "," 7 "," 8 "," 9 "]" schreiben , Hier werden "0", "1", ... "9" nacheinander im Zeichencode aufgereiht, sodass Sie "[" 0 "-" 9 "]" schreiben können. In ähnlicher Weise können obere und untere Buchstaben als "[" a "-" z "," A "-" Z "]" geschrieben werden.
Zum Abschluss wird es in "TOKEN {}" eingeschlossen.
TOKEN :
{
< ADD_OP : "+" | "-" >
| < MUL_OP : "*" | "/" | "%" >
| < OPEN_BRACKET : "(" >
| < CLOSE_BRACKET : ")" >
| < DECIMAL : (< DIGIT >)+ ("." (< DIGIT >)+)? >
| < DIGIT : [ "0"-"9" ] >
}
Tatsächlich wurde die versteckte Phrase nicht definiert. Zum Beispiel Leerzeichen und Tabulatoren. Es ist auch erforderlich, die Verarbeitung zu schreiben, wenn der Phrasenanalysator auf diese Zeichen trifft. Dieses Mal werden diese Zeichen ignoriert (das Verhalten ändert sich nicht mit oder ohne sie). Schreiben Sie daher Folgendes.
SKIP :
{
" "
| "\t"
| "\r"
}
(Das Zeilenvorschubzeichen wird später als Trennzeichen verwendet, sodass es nicht ignoriert wird.)
<Formel> ::= <加算Formel>
Diese BNF kann geschrieben werden als:
void Expr() :
{}
{
AddExpr()
}
Es ist zu beachten, dass vor und nach :
die Vorderseite der linken Seite von BNF und die Rückseite der rechten Seite entspricht.
Sie können es wie oben in einer Java-ähnlichen Notation schreiben. Wobei "
<Additionsformel> ::= <Multiplikationsformel> ( <Additionsoperator> <Multiplikationsformel> )*
Ist wie folgt.
void AddExpr() :
{}
{
MulExpr()
(
< ADD_OP >
MulExpr()
)*
}
Der
SimpleNode Root() :
{}
{
Expr() "\n"
{
return jjtThis;
}
}
void Expr() :
{}
{
AddExpr()
}
void AddExpr() :
{}
{
MulExpr()
(
< ADD_OP >
MulExpr()
)*
}
void MulExpr() :
{}
{
UnaryExpr()
(
< MUL_OP >
UnaryExpr()
)*
}
void UnaryExpr() :
{}
{
< OPEN_BRACKET > Expr() < CLOSE_BRACKET >
| Decimal()
}
void Decimal() :
{}
{
< DECIMAL >
}
Root versucht, einen Knoten zurückzugeben. Damit ist der Teil zur Syntaxanalyse abgeschlossen.
Definieren Sie die folgende Ausführungsmethode in der SimpleCalculatorParser-Klasse.
PARSER_BEGIN(SimpleCalculatorParser)
package simple_calculator;
import java.util.List;
import java.util.ArrayList;
public class SimpleCalculatorParser
{
public static void main(String [] args)
{
SimpleCalculatorParser.eval(System.in);
}
public static void eval(java.io.InputStream in)
{
//Parser erstellen
SimpleCalculatorParser parser = new SimpleCalculatorParser(in);
try
{
//Die Dump-Methode der SimpleNode-Klasse zeigt einen abstrakten Syntaxbaum unter einem eigenen Knoten an
parser.Root().dump(" ");
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
PARSER_END(SimpleCalculatorParser)
Infolgedessen sieht die zum Erstellen des abstrakten Syntaxbaums erstellte JJT-Datei folgendermaßen aus:
SimpleCalculatorGrammar.jjt
/**
* Simple calculator JJTree file
*/
options
{
STATIC = false; //Machen Sie Parser-Klassenmethoden nicht statisch
MULTI = true; //Generieren Sie eine ASTXXX-Klasse
VISITOR = true; //Generieren Sie eine Besucherklasse
UNICODE_INPUT = false; //Analysieren Sie nicht mit Unicode (verwenden Sie kein Japanisch usw.)
}
PARSER_BEGIN(SimpleCalculatorParser)
package simple_calculator;
import java.util.List;
import java.util.ArrayList;
public class SimpleCalculatorParser
{
public static void main(String [] args)
{
SimpleCalculatorParser.eval(System.in);
}
public static void eval(java.io.InputStream in)
{
SimpleCalculatorParser parser = new SimpleCalculatorParser(in);
try
{
parser.Root().dump(" ");
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
PARSER_END(SimpleCalculatorParser)
SKIP :
{
" "
| "\t"
| "\r"
}
TOKEN :
{
< ADD_OP :
"+"
| "-" >
| < MUL_OP :
"*"
| "/"
| "%" >
| < OPEN_BRACKET : "(" >
| < CLOSE_BRACKET : ")" >
| < DECIMAL :
(< DIGIT >)+
(
"." (< DIGIT >)+
)? >
| < DIGIT : [ "0"-"9" ] >
}
SimpleNode Root() :
{}
{
Expr() "\n"
{
return jjtThis;
}
}
void Expr() :
{}
{
AddExpr()
}
void AddExpr() :
{}
{
MulExpr()
(
< ADD_OP >
MulExpr()
)*
}
void MulExpr() :
{}
{
UnaryExpr()
(
< MUL_OP >
UnaryExpr()
)*
}
void UnaryExpr() :
{}
{
< OPEN_BRACKET > Expr() < CLOSE_BRACKET >
| Decimal()
}
void Decimal() :
{}
{
< DECIMAL >
}
Wenn Sie die SimpleCalculatorParser-Klasse ausführen Da es auf die Eingabe wartet, geben Sie "1 * 2 + 3" ein, um den abstrakten Syntaxbaum anzuzeigen.
1*2+3
Root
Expr
AddExpr
MulExpr
UnaryExpr
Decimal
UnaryExpr
Decimal
MulExpr
UnaryExpr
Decimal
Sie können sehen, dass der Syntaxbaum korrekt generiert wurde.
Es ist ein Entwurfsmuster zum Nachzeichnen der Holzstruktur. Dieses Muster wird beim Erkunden und Bearbeiten von Bäumen verwendet. Es heißt so, weil es so aussieht, als würde ein Besucher einen Knoten im Baum besuchen.
Daher akzeptiert der Knoten den besuchenden Besucher, und der Besucher muss den Knoten nach Bedarf besuchen.
Lassen Sie die Knoten AddExpr und MulExpr eine Liste von Operatoren enthalten. Dezimal hält das Token.
Zum Beispiel bei der Eingabe von 1 * 2 * 3 + 4
Root
Expr
AddExpr [+] List<Token>
MulExpr [*, *] List<Token>
UnaryExpr
Decimal 1 Token
UnaryExpr
Decimal 2 Token
UnaryExpr
Decimal 3 Token
MulExpr
UnaryExpr [] List<Token>
Decimal 4 Token
Zu jedem Knoten.
Der Knoten ist ein Objekttyp und kann einen Wert haben. Insbesondere wird "jjtThis.jjtSetValue (Object)" verwendet, um den Wert zu speichern. Die Phrase kann auch als "t = <ADD_OP>" zugewiesen werden.
void AddExpr() :
{
/* 1.Vorverarbeitung*/
List tokens = new ArrayList();
Token t = null;
}
{
MulExpr()
(
t = < ADD_OP >
MulExpr() { /* 2. < ADD_OP > MulExpr()Verarbeitung für*/ tokens.add(t); }
)*
{
/* 3. MulExpr() ( < ADD_OP > MulExpr() )*Verarbeitung für*/
jjtThis.jjtSetValue(tokens);
}
}
Wenn Sie die Verarbeitung auf diese Weise hinzufügen
SimpleNode Root() :
{}
{
Expr() "\n"
{
return jjtThis;
}
}
void Expr() :
{}
{
AddExpr()
}
void AddExpr() :
{
List tokens = new ArrayList();
Token t = null;
}
{
MulExpr()
(
t = < ADD_OP >
MulExpr() { tokens.add(t); }
)*
{
jjtThis.jjtSetValue(tokens);
}
}
void MulExpr() :
{
List tokens = new ArrayList();
Token t = null;
}
{
UnaryExpr()
(
t = < MUL_OP >
UnaryExpr() { tokens.add(t); }
)*
{
jjtThis.jjtSetValue(tokens);
}
}
void UnaryExpr() :
{}
{
< OPEN_BRACKET > Expr() < CLOSE_BRACKET >
| Decimal()
}
void Decimal() :
{
Token t = null;
}
{
t = < DECIMAL >
{
jjtThis.jjtSetValue(t);
}
}
Es wird sein.
SimpleCalculatorParserVisitor.java
/* Generated By:JavaCC: Do not edit this line. SimpleCalculatorParserVisitor.java Version 5.0 */
package simple_calculator;
public interface SimpleCalculatorParserVisitor
{
public Object visit(SimpleNode node, Object data);
public Object visit(ASTRoot node, Object data);
public Object visit(ASTExpr node, Object data);
public Object visit(ASTAddExpr node, Object data);
public Object visit(ASTMulExpr node, Object data);
public Object visit(ASTUnaryExpr node, Object data);
public Object visit(ASTDecimal node, Object data);
}
/* JavaCC - OriginalChecksum=afb311a7bd4476d0ee434db749efc016 (do not edit this line) */
Oben ist die automatisch generierte Besucheroberfläche. Implementieren Sie dies.
Jede Methode beschreibt die Verarbeitung, wenn jeder Knoten besucht wird.
Speziell,
public Object visit (ASTRoot-Knoten, Objektdaten);
ist beim Besuch von Root
public Object visit (ASTExpr-Knoten, Objektdaten);
ist, wenn Sie Expr besuchen
public Object visit (ASTAddExpr-Knoten, Objektdaten);
ist, wenn Sie AddExpr besuchen
・ ・ ・
Entspricht.
Das Argument "Knoten" entspricht zu diesem Zeitpunkt "jjtThis", wenn "jjtThis.jjtSetValue (Token)" ausgeführt wird, und dieser eingestellte Wert kann "node.jjtGetValue ()" sein. Der Knoten enthält ein untergeordnetes Element und kann mit "node.jjtGetChild (index)" abgerufen werden.
Es wird normalerweise verwendet, wenn ein untergeordneter Knoten wie "node.jjtGetChild (index) .jjtAccept (this, data)" besucht wird.
Eine Beispielimplementierung ist unten dargestellt.
SimpleCalculatorParserVisitorImpl.java
package simple_calculator;
import java.util.List;
public class SimpleCalculatorParserVisitorImpl implements
SimpleCalculatorParserVisitor {
@Override
public Object visit(SimpleNode node, Object data) {
//Ich benutze es normalerweise nicht.
return null;
}
@Override
public Object visit(ASTRoot node, Object data) {
//Besuchen Sie den Knoten Ihres Kindes. Aus der Syntaxdefinition geht hervor, dass es nur ein Kind gibt.
return node.jjtGetChild(0).jjtAccept(this, null);
}
@Override
public Object visit(ASTExpr node, Object data) {
//Besuchen Sie den Knoten Ihres Kindes. Aus der Syntaxdefinition geht hervor, dass es nur ein Kind gibt.
return node.jjtGetChild(0).jjtAccept(this, null);
}
@Override
public Object visit(ASTAddExpr node, Object data) {
List<Token> ops = (List<Token>) node.jjtGetValue();
//Holen Sie sich die Anzahl der Kinder
int size = node.jjtGetNumChildren();
Double x = (Double) node.jjtGetChild(0).jjtAccept(this, null);
//Führen Sie Operationen aus, während Sie Operatoren und Werte drehen. Fahren Sie mit der Berechnung fort, während Sie einen Satz erstellen. Beispiel) x0 (+ x1) (+ x2)・ ・ ・
for (int i = 1; i < size; i++) {
switch (ops.get(i - 1).toString()) {
case "+":
x = x + (Double) node.jjtGetChild(i).jjtAccept(this, null);
break;
case "-":
x = x - (Double) node.jjtGetChild(i).jjtAccept(this, null);
break;
}
}
return x;
}
@Override
public Object visit(ASTMulExpr node, Object data) {
// jjtGetValue()Mit jjtSetValue(Object)Sie können den Wert erhalten.
List<Token> ops = (List<Token>) node.jjtGetValue();
int size = node.jjtGetNumChildren();
Double x = (Double) node.jjtGetChild(0).jjtAccept(this, null);
//Führen Sie Operationen aus, während Sie Operatoren und Werte drehen. Fahren Sie mit der Berechnung fort, während Sie einen Satz erstellen. Beispiel) x0 (* x1) (* x2)・ ・ ・
for (int i = 1; i < size; i++) {
switch (ops.get(i - 1).toString()) {
case "*":
x = x * (Double) node.jjtGetChild(i).jjtAccept(this, null);
break;
case "/":
x = x / (Double) node.jjtGetChild(i).jjtAccept(this, null);
break;
case "%":
x = x % (Double) node.jjtGetChild(i).jjtAccept(this, null);
break;
}
}
return x;
}
@Override
public Object visit(ASTUnaryExpr node, Object data) {
//Ein Kind wird das Double zurückgeben, also geben Sie es so zurück, wie es ist.
return node.jjtGetChild(0).jjtAccept(this, null);
}
@Override
public Object visit(ASTDecimal node, Object data) {
//Konvertieren Sie die Phrase in Double und geben Sie sie zurück.
return Double.valueOf(((Token) node.jjtGetValue()).toString());
}
}
Erstellen Sie unten SimpleCalculatorGrammar.jjt, bereiten Sie SimpleCalculatorParserVisitorImpl.java vor und starten Sie SimpleCalculatorParser.
SimpleCalculatorGrammar.jjt
/**
* Simple calculator JJTree file
*/
options
{
STATIC = false; //Machen Sie Parser-Klassenmethoden nicht statisch
MULTI = true; //Generieren Sie eine ASTXXX-Klasse
VISITOR = true; //Generieren Sie eine Besucherklasse
UNICODE_INPUT = false; //Analysieren Sie nicht mit Unicode (verwenden Sie kein Japanisch usw.)
}
PARSER_BEGIN(SimpleCalculatorParser)
package simple_calculator;
import java.util.List;
import java.util.ArrayList;
public class SimpleCalculatorParser
{
public static void main(String [] args)
{
System.out.println(SimpleCalculatorParser.eval(System.in));
}
public static double eval(java.io.InputStream in)
{
SimpleCalculatorParser parser = new SimpleCalculatorParser(in);
double x = 0.0;
SimpleCalculatorParserVisitor visitor = new SimpleCalculatorParserVisitorImpl();
try {
x = (double) parser.Root().jjtAccept(visitor, null);
} catch (ParseException e) {
e.printStackTrace();
}
return x;
}
}
PARSER_END(SimpleCalculatorParser)
SKIP :
{
" "
| "\t"
| "\r"
}
TOKEN :
{
< ADD_OP :
"+"
| "-" >
| < MUL_OP :
"*"
| "/"
| "%" >
| < OPEN_BRACKET : "(" >
| < CLOSE_BRACKET : ")" >
| < DECIMAL :
(< DIGIT >)+
(
"." (< DIGIT >)+
)? >
| < DIGIT : [ "0"-"9" ] >
}
SimpleNode Root() :
{}
{
Expr() "\n"
{
return jjtThis;
}
}
void Expr() :
{}
{
AddExpr()
}
void AddExpr() :
{
List tokens = new ArrayList();
Token t = null;
}
{
MulExpr()
(
t = < ADD_OP >
MulExpr() { tokens.add(t); }
)*
{
jjtThis.jjtSetValue(tokens);
}
}
void MulExpr() :
{
List tokens = new ArrayList();
Token t = null;
}
{
UnaryExpr()
(
t = < MUL_OP >
UnaryExpr() { tokens.add(t); }
)*
{
jjtThis.jjtSetValue(tokens);
}
}
void UnaryExpr() :
{}
{
< OPEN_BRACKET > Expr() < CLOSE_BRACKET >
| Decimal()
}
void Decimal() :
{
Token t = null;
}
{
t = < DECIMAL >
{
jjtThis.jjtSetValue(t);
}
}
Ausführungsergebnis
1+2*3*4
25.0
Es ging gut! !! !! Nächstes Mal möchte ich so etwas wie eine Programmiersprache machen.
Mach eine Sprache! Ich habe versucht, Bison und Flex zu verwenden Mach eine Sprache! (Java CC-Umgebungskonstruktion) Mach eine Sprache! (Erstellen eines einfachen Taschenrechners ①)
Recommended Posts