[JAVA] Faites une langue! (Faire une simple calculatrice ②)

La dernière fois, faites une langue! J'ai pensé à la grammaire d'une calculatrice simple dans Créer une calculatrice simple ①. Cette fois, faisons-en un programme qui fonctionne réellement.

Image complète

Lorsque vous entrez la formule dans la console, elle sera calculée et la valeur numérique sera affichée.

> 2 + 3 * 5
17
> 100 * 1.08
108.00

Contenu à décrire dans le fichier JJT

  1. Option --Définissez les caractéristiques du code source généré par la construction et le fichier de sortie.
  2. Définition de l'analyseur - Définissez un nom de classe d'analyseur ou ajoutez votre propre méthode.
  3. Définition des phrases - Définit le type de phrases que vous avez. Vous pouvez également définir des mots à ignorer.
  4. Définition de la syntaxe - Combinez des mots pour définir la syntaxe. Vous pouvez écrire des processus simples en même temps.

Je décrirai les quatre points ci-dessus. Comment écrire Java CC en détail https://javacc.org/javaccgrm ↑ Il est écrit sur le site (en anglais).

Fichier JJT BNF vers Java CC

J'ai défini le BNF suivant que j'ai défini la dernière fois comme suit.

BNF avec une formule simple


<formule> ::= <加算formule>
<Formule d'addition> ::= <Formule de multiplication> ( <Opérateur d'addition> <Formule de multiplication> )*
<Formule de multiplication> ::= <Terme unique> ( <Opérateur de multiplication> <Terme unique> )*
<Terme unique> ::= <Support ouvert> <formule> <Support de fermeture> | <Représentation décimale>

<Opérateur d'addition> ::= "+" | "-"
<Opérateur de multiplication> ::= "*" | "/" | "%"
<Support ouvert> ::= "("
<Support de fermeture> ::= ")"
<Représentation décimale> ::= (<Nombres>)+ ("." (<Nombres>)+)?
<Nombres> ::= "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "0"

Je vais mettre cela dans l'ordre dans le fichier JJT.

Définition des mots

Tout d'abord, définissons l '<opérateur d'addition>. La définition dans Java CC est

< ADD_OP : "+"|"-" >

Il est décrit comme. La signification des symboles est presque la même que l'explication des symboles utilisés dans BNF. http://qiita.com/toru0408/items/9d7263bce7f9a4bdce13#bnf Prière de se référer à. Si vous remplacez tout par la description javacc de cette manière

< ADD_OP : "+" | "-" >
< MUL_OP : "*" | "/" | "%" >
< OPEN_BRACKET : "(" >
< CLOSE_BRACKET : ")" >
< DECIMAL : (< DIGIT >)+ ("." (< DIGIT >)+)? >
< DIGIT : [ "0"-"9" ] >

Ce sera. [" 0 "-" 9 "] fait référence à tous les caractères de "0" à "9" sur la table des codes de caractères. Vous pouvez également écrire "[" 0 "," 1 "," 2 "," 3 "," 4 "," 5 "," 6 "," 7 "," 8 "," 9 "] , Ici, "0", "1", ... "9" sont alignés successivement sur le code de caractère, vous pouvez donc écrire [" 0 "-" 9 "]. De même, les lettres majuscules et minuscules peuvent être écrites sous la forme «[" a "-" z "," A "-" Z "]`.

Enfin, il est complété en le plaçant dans TOKEN {}.

TOKEN :
{
  < ADD_OP : "+" | "-" >
| < MUL_OP : "*" | "/" | "%" >
| < OPEN_BRACKET : "(" >
| < CLOSE_BRACKET : ")" >
| < DECIMAL : (< DIGIT >)+ ("." (< DIGIT >)+)? >
| < DIGIT : [ "0"-"9" ] >
}

Définition de phrases spéciales

En fait, la phrase cachée n'a pas été définie. Par exemple, des blancs et des onglets. Il est également nécessaire d'écrire le traitement lorsque l'analyseur de phrases rencontre ces caractères. Cette fois, ces caractères sont ignorés (le comportement ne change pas avec ou sans eux), alors écrivez ce qui suit.

SKIP :
{
  " "
| "\t"
| "\r"
}

(Le caractère de saut de ligne sera utilisé plus tard comme délimiteur, il ne sera donc pas ignoré.)

Définition de la syntaxe

<formule> ::= <加算formule>

Ce BNF peut s'écrire:

void Expr() :
{}
{
  AddExpr()
}

Il est à noter qu'avant et après «:», l'avant correspond au côté gauche du BNF, et l'arrière correspond au côté droit. Vous pouvez l'écrire dans une notation de type Java comme ci-dessus. Où <expression> correspond à ʻExpr () et <expression supplémentaire> correspond à ʻAddExpr (). Ensuite, définissez ʻAddExpr () `.

<Formule d'addition> ::= <Formule de multiplication> ( <Opérateur d'addition> <Formule de multiplication> )*

Est comme suit.

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

Le <opérateur d'addition> a déjà défini la phrase, utilisez donc <ADD_OP> Je la définirai jusqu'au bout avec cette condition.

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 essaie de renvoyer un nœud. Ceci termine la partie analyse de la syntaxe.

Afficher l'arborescence de syntaxe abstraite

Définissez la méthode d'exécution suivante dans la classe SimpleCalculatorParser.

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)
  {
    //Créer un analyseur
    SimpleCalculatorParser parser = new SimpleCalculatorParser(in);
    try
    {
      //La méthode dump de la classe SimpleNode affiche une arborescence de syntaxe abstraite sous son propre nœud
      parser.Root().dump(" ");
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
  }
}

PARSER_END(SimpleCalculatorParser)

En conséquence, le fichier jjt créé pour créer l'arborescence de syntaxe abstraite ressemble à ceci:

SimpleCalculatorGrammar.jjt


/**
 * Simple calculator JJTree file
 */
options
{
  STATIC = false; //Ne rendez pas la méthode de classe parser statique
  MULTI = true; //Générer la classe ASTXXX
  VISITOR = true; //Générer une classe Visiteur
  UNICODE_INPUT = false; //N'analysez pas avec Unicode (n'utilisez pas de japonais, etc.)
}

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

Lorsque vous exécutez la classe SimpleCalculatorParser Puisqu'il attendra l'entrée, entrez «1 * 2 + 3» pour afficher l'arborescence de syntaxe abstraite.

1*2+3
 Root
  Expr
   AddExpr
    MulExpr
     UnaryExpr
      Decimal
     UnaryExpr
      Decimal
    MulExpr
     UnaryExpr
      Decimal

Vous pouvez voir que l'arborescence de syntaxe a été générée correctement.

Analyse sémantique et exécution

Quel est le modèle de visiteur?

C'est un modèle de conception pour tracer la structure en bois. Ce modèle est utilisé lors de l'exploration et de la manipulation des arbres. Il est ainsi nommé car il ressemble à un visiteur visite un nœud de l'arborescence.

Par conséquent, le nœud accepte le visiteur visiteur et le visiteur doit visiter le nœud si nécessaire.

Laisser Node tenir la valeur

Demandez aux nœuds AddExpr et MulExpr de contenir une liste d'opérateurs. Decimal contient le jeton.

Par exemple, lors de la saisie de 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

À chaque nœud.

Node est un type Object et peut avoir une valeur. Plus précisément, «jjtThis.jjtSetValue (Object)» est utilisé pour contenir la valeur. De plus, la phrase peut être assignée comme «t = <ADD_OP>».

void AddExpr() :
{
  /* 1.Prétraitement*/
  List tokens = new ArrayList();
  Token t = null;
}
{
  MulExpr()
  (
    t = < ADD_OP > 
    MulExpr() { /* 2. < ADD_OP > MulExpr()Traitement pour*/ tokens.add(t); }
  )*
  {
    /* 3. MulExpr() ( < ADD_OP > MulExpr() )*Traitement pour*/
    jjtThis.jjtSetValue(tokens);
  }
}

Si vous ajoutez un traitement de cette manière

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

Ce sera.

Implémentation des visiteurs

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) */

Ci-dessus se trouve l'interface visiteur générée automatiquement. Mettez cela en œuvre. Chaque méthode décrit le traitement lorsque chaque nœud est visité. En particulier, public Object visit (ASTRoot node, Object data); est lors de la visite de Root public Object visit (ASTExpr node, Object data); est lorsque vous visitez Expr public Object visit (ASTAddExpr node, Object data); est lorsque vous visitez AddExpr ・ ・ ・ Correspond à. L'argument node à ce moment correspond à jjtThis lorsquejjtThis.jjtSetValue (tokens)est terminé, et cette valeur définie peut êtrenode.jjtGetValue (). node contient un enfant et peut être obtenu avec node.jjtGetChild (index). Il est généralement utilisé lors de la visite d'un nœud enfant comme node.jjtGetChild (index) .jjtAccept (this, data).

Un exemple de mise en œuvre est présenté ci-dessous.

SimpleCalculatorParserVisitorImpl.java


package simple_calculator;

import java.util.List;

public class SimpleCalculatorParserVisitorImpl implements
		SimpleCalculatorParserVisitor {

	@Override
	public Object visit(SimpleNode node, Object data) {
		//Je ne l'utilise généralement pas.
		return null;
	}

	@Override
	public Object visit(ASTRoot node, Object data) {
		//Visitez le nœud de votre enfant. Il ressort de la définition de la syntaxe qu'il n'y a qu'un seul enfant.
		return node.jjtGetChild(0).jjtAccept(this, null);
	}

	@Override
	public Object visit(ASTExpr node, Object data) {
		//Visitez le nœud de votre enfant. Il ressort de la définition de la syntaxe qu'il n'y a qu'un seul enfant.
		return node.jjtGetChild(0).jjtAccept(this, null);
	}

	@Override
	public Object visit(ASTAddExpr node, Object data) {
		List<Token> ops = (List<Token>) node.jjtGetValue();
		//Obtenez le nombre d'enfants
		int size = node.jjtGetNumChildren();
		Double x = (Double) node.jjtGetChild(0).jjtAccept(this, null);
		//Effectuer des opérations en tournant les opérateurs et les valeurs. Poursuivez le calcul tout en faisant un ensemble. Exemple) 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()Avec jjtSetValue(Object)Vous pouvez obtenir la valeur.
		List<Token> ops = (List<Token>) node.jjtGetValue();
		int size = node.jjtGetNumChildren();
		Double x = (Double) node.jjtGetChild(0).jjtAccept(this, null);
		//Effectuer des opérations en tournant les opérateurs et les valeurs. Poursuivez le calcul tout en faisant un ensemble. Exemple) 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) {
		//Un enfant retournera le Double, donc renvoyez-le tel quel.
		return node.jjtGetChild(0).jjtAccept(this, null);
	}

	@Override
	public Object visit(ASTDecimal node, Object data) {
		//Convertissez la phrase en type Double et renvoyez-la.
		return Double.valueOf(((Token) node.jjtGetValue()).toString());
	}

}

Démarrez la calculatrice!

Générez SimpleCalculatorGrammar.jjt ci-dessous, préparez SimpleCalculatorParserVisitorImpl.java et démarrez SimpleCalculatorParser.

SimpleCalculatorGrammar.jjt


/**
 * Simple calculator JJTree file
 */
options
{
  STATIC = false; //Ne rendez pas la méthode de classe parser statique
  MULTI = true; //Générer la classe ASTXXX
  VISITOR = true; //Générer une classe Visiteur
  UNICODE_INPUT = false; //N'analysez pas avec Unicode (n'utilisez pas de japonais, etc.)
}

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

Résultat d'exécution


1+2*3*4
25.0

Ça s'est bien passé! !! !! La prochaine fois, j'aimerais créer quelque chose comme un langage de programmation.

Article associé

Faites une langue! J'ai essayé d'utiliser bison et flex Faites une langue! (Construction de l'environnement Java CC) Faites une langue! (Faire une simple calculatrice ①)

Recommended Posts

Faites une langue! (Faire une simple calculatrice ②)
Faites une langue! (Faire une simple calculatrice ①)
Essayez de faire un simple rappel
Faites une langue! (Construction de l'environnement Java CC)
[docker] [nginx] Créer un ALB simple avec nginx
Créons une application de calcul avec Java
Faire de Frontale un micro service
Entraînez-vous à créer une application de chat simple avec Docker + Sinatra
[Mémo personnel] Créez une copie complète simple avec Java
Faisons un robot! "Une simple démo de Java AWT Robot"
Créer un utilitaire de réflexion ②
Créer un utilitaire de réflexion ③
Créer un utilitaire de réflexion ①
Faisons une application de calcul avec Java ~ Afficher la fenêtre de l'application
[Débutant] Essayez de créer un jeu RPG simple avec Java ①
Créez un CRUD simple avec SpringBoot + JPA + Thymeleaf ① ~ Hello World ~
Faisons une API simple avec EC2 + RDS + Spring boot ①
Créez un CRUD simple avec SpringBoot + JPA + Thymeleaf ⑤ ~ Modèle commun ~
[Java] Rendez-le constant
[Java] Dessine un motif simple
[Rails] Faites une liste de miettes de pain
Faire un diamant en utilisant Java
Une histoire sur la fabrication d'une calculatrice pour calculer le taux de monticule d'obus