[JAVA] Mach eine Sprache! (Einen einfachen Taschenrechner machen ②)

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.

Vollständiges Bild

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

Inhalt, der in der JJT-Datei beschrieben werden soll

  1. Option - Legen Sie die Eigenschaften des vom Build und der Ausgabedatei generierten Quellcodes fest.
  2. Parser-Definition - Definieren Sie einen Parser-Klassennamen oder fügen Sie Ihre eigene Methode hinzu.
  3. Phrasendefinition - Definiert, welche Art von Phrasen Sie haben. Sie können auch Wörter definieren, die ignoriert werden sollen.
  4. Syntaxdefinition - Kombinieren Sie Wörter, um die Syntax zu definieren. Sie können gleichzeitig einfache Prozesse schreiben.

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).

BNF zu Java CC JJT-Datei

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.

Definition von Wörtern

Definieren wir zunächst den . Die Definition in Java CC lautet

< 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" ] >
}

Definition von speziellen Phrasen

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.)

Syntaxdefinition

<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 "" "Expr ()" und "" "AddExpr ()" entspricht. Definieren Sie als Nächstes "AddExpr ()".

<Additionsformel> ::= <Multiplikationsformel> ( <Additionsoperator> <Multiplikationsformel> )*

Ist wie folgt.

void AddExpr() :
{}
{
  MulExpr()
  (
    < ADD_OP > 
    MulExpr()
  )*
}

Der hat die Phrase bereits definiert, verwenden Sie also <ADD_OP>. Ich werde es bis zum Ende mit dieser Bedingung definieren.

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.

Abstrakten Syntaxbaum anzeigen

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.

Semantische Analyse und Ausführung

Was ist das Besuchermuster?

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 Node den Wert halten

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.

Besucherimplementierung

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

}

Starten Sie den Rechner!

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.

In Verbindung stehender Artikel

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

Mach eine Sprache! (Einen einfachen Taschenrechner machen ②)
Mach eine Sprache! (Einen einfachen Taschenrechner machen ①)
Versuchen Sie, einen einfachen Rückruf zu tätigen
Mach eine Sprache! (Java CC-Umgebungskonstruktion)
[Docker] [Nginx] Erstellen Sie mit Nginx eine einfache ALB
Lassen Sie uns eine Taschenrechner-App mit Java erstellen
Frontale zu einem Mikrodienst machen
Üben Sie das Erstellen einer einfachen Chat-App mit Docker + Sinatra
[Persönliches Memo] Erstellen Sie eine einfache, tiefe Kopie mit Java
Lass uns einen Roboter bauen! "Eine einfache Demo von Java AWT Robot"
Erstellen Sie ein Reflexionsprogramm ②
Erstellen Sie ein Reflexionsprogramm ③
Erstellen Sie ein Reflexionsprogramm ①
Erstellen wir eine Taschenrechner-App mit Java ~ Zeigen Sie das Anwendungsfenster an
[Anfänger] Versuchen Sie, mit Java ein einfaches RPG-Spiel zu erstellen ①
Erstellen Sie mit SpringBoot + JPA + Thymeleaf ein einfaches CRUD ~ ~ Hallo Welt ~
Erstellen wir eine einfache API mit EC2 + RDS + Spring Boot ①
Erstellen Sie eine einfache CRUD mit SpringBoot + JPA + Thymeleaf ⑤ ~ Common template ~
[Java] Mach es konstant
[Java] Zeichnen Sie ein einfaches Muster
[Schienen] Machen Sie eine Brotkrumenliste
Machen Sie einen Diamanten mit Java
Eine Geschichte über die Herstellung eines Taschenrechners zur Berechnung der Muschelhügelrate