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.
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
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).
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.
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" ] >
}
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é.)
<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.
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.
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.
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.
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());
}
}
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.
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