Dans un article précédent, j'ai implémenté un interpréteur (http://qiita.com/quwahara/items/30e93dfd2690913d66c0). L'interpréteur peut désormais exécuter quatre règles, mais les résultats du calcul ne peuvent pas être affichés. Ensuite, je suis seul, je vais donc ajouter println pour afficher le résultat du calcul.
Confirmez ce que vous voulez faire lors de la mise en œuvre. Par exemple, si vous avez un programme comme celui ci-dessous, nous visons à afficher «1» et un saut de ligne dans la sortie standard.
println(1)
De plus, si une variable avec «2» assigné est spécifiée comme argument de «println ()», nous visons à afficher le contenu «2» comme un saut de ligne vers la sortie standard.
a = 2
println(a)
Jusqu'à présent, nous avons implémenté l'analyse de phrases (Lexer), l'analyse syntaxique (Parser) et l'interpréteur (Interpreter). Nous examinerons comment mettre en œuvre chacun à son tour.
L'analyse de phrase implémentée n'a pas la capacité d'analyser les parenthèses, (
et )
.
Ajoutez-le d'abord.
De plus, dans les spécifications d'analyse des phrases, l'ordre alphabétique était censé être variable.
Cependant, println
, qui est l'une des séquences alphabétiques, est un nom de fonction, pas un nom de variable.
Le fait qu'il s'agisse à lui seul d'une séquence d'alphabets ne détermine pas s'il s'agit d'un nom de variable ou d'un nom de fonction.
Par conséquent, changez la signification de la séquence alphabétique en ident.
Pour réfléchir à la façon de l'implémenter, regardez d'abord attentivement la séquence de jetons dans l'appel println (1)
.
Il est décomposé en quatre jetons suivants.
println
et (ʻand
1 et
) `Cette séquence est similaire à la séquence de «a + 1». Je vais regarder. ʻA + 1` essaie également d'organiser les jetons de la même manière.
,
+et
1`Comparons-les.
println
→ ʻa` → Les deux sont des identifiants(
→ +
→ Les deux symboles1
→ 1
→ Les deux nombres)
→ Pas de supportL'analyse syntaxique de «a + 1» consistait à attribuer les jetons à gauche et à droite avec «+» au milieu.
En observant le contraste, println (1)
a également une composition dans laquelle les jetons qui viennent à gauche et à droite viennent avec (
au milieu.
D'après le résultat de l'observation, afin d'analyser syntaxiquement println (1)
, il semble que l'analyse syntaxique puisse être effectuée en traitant ʻa + 1pour analyser et imprimer, et en confirmant que le jeton
)` arrive à la fin. ..
En d'autres termes, il semble qu'il puisse être implémenté en modifiant légèrement l'analyse syntaxique des jetons d'opérateur.
Il prend également en charge le changement de nom de variable à ident, qui a été mentionné dans la méthode précédente de mise en œuvre de l'analyse de phrase.
Le problème avec l'implémentation de l'appel println (1)
était que l'interpréteur représentait implicitement la variable comme une chaîne.
C'était bien car il n'y avait aucune possibilité qu'il y ait un nom de fonction tel quel, mais maintenant nous devons faire la distinction entre le nom de la variable et le nom de la fonction.
Par conséquent, nous allons introduire une classe qui représente le nom de la variable et le nom de la fonction pour chacun.
De plus, comme nous l'avons observé dans l'implémentation de l'analyse syntaxique, (
est le jeton clé pour l'appel println (1)
.
Invitez-les à appeler la méthode qui gère le (
quand ils rencontrent un token.
Enfin, ici aussi, nous pouvons gérer le changement de nom de variable à ident, qui a été mentionné dans la méthode précédente d'implémentation de l'analyse de phrase.
Passez à la mise en œuvre.
Encore une fois, pour l'analyse de phrases (Lexer), l'analyse syntaxique (Parser) et l'interpréteur (Interpreter), Pour chacun, examinons les modifications et les ajouts dans l'ordre.
Lexer.java
Une implémentation de Lexer.java.
Tout d'abord, ajoutez la fonction pour analyser les parenthèses, (
et )
.
Lexer.java
private boolean isParenStart(char c) {
return c == '(' || c == ')';
}
private Token paren() throws Exception {
Token t = new Token();
t.kind = "paren";
t.value = Character.toString(next());
return t;
}
Vient ensuite la réponse aux changements de noms de variables à idents.
Lexer.java
private boolean isIdentStart(char c) throws Exception {
return Character.isAlphabetic(c);
}
private Token ident() throws Exception {
StringBuilder b = new StringBuilder();
b.append(next());
while (!isEOT() && (Character.isAlphabetic(c()) || Character.isDigit(c()))) {
b.append(next());
}
Token t = new Token();
t.kind = "ident";
t.value = b.toString();
return t;
}
Enfin, si vous ajoutez la partie appel de l'analyse des parenthèses et modifiez la partie appel du changement en ident, L'implémentation de Lexer.java est terminée.
Lexer.java
public Token nextToken() throws Exception {
skipSpace();
if (isEOT()) {
return null;
} else if (isSignStart(c())) {
return sign();
} else if (isDigitStart(c())) {
return digit();
} else if (isIdentStart(c())) {
return ident();
} else if (isParenStart(c())) {
return paren();
} else {
throw new Exception("Not a character for tokens");
}
}
Parser.java
Une implémentation de Parser.java.
Tout d'abord, changez la définition de son fonctionnement par rapport à la signification du jeton.
Ajoutez (
à degrés
, qui définit le degré d'ordre des opérateurs à <-Ajouter
.
(
Est aussi une correspondance à traiter comme un opérateur.
println ()
veut se combiner avec la priorité sur d'autres opérateurs, alors réglez le degré sur 80
.
Correspond au changement de ident à la place de <-Update
.
Parser.java
public Parser() {
degrees = new HashMap<>();
degrees.put("(", 80); // <-- Add
degrees.put("*", 60);
degrees.put("/", 60);
degrees.put("+", 50);
degrees.put("-", 50);
degrees.put("=", 10);
factorKinds = Arrays.asList(new String[] { "digit", "ident" }); // <-- Update
binaryKinds = Arrays.asList(new String[] { "sign" });
rightAssocs = Arrays.asList(new String[] { "=" });
}
C'est un changement de pièce à analyser.
Ajout du processus à analyser à l'instruction if avec <-Add
.
Assigner des jetons à ʻoperator.left et ʻoperator.right
revient presque à analyser l'opérateur de l'instruction if ci-dessus.
ʻOperator.right = expression (0); ʻexpression ()
argument est 0
contrairement à l'instruction if ci-dessus
Cela est dû au fait qu'une expression est considérée indépendamment comme argument de la fonction et qu'il n'est pas nécessaire de comparer la priorité avec les opérateurs précédents et suivants.
consume ()
confirme que le jeton suivant est une parenthèse fermante et avance le jeton d'intérêt dans l'analyse au suivant.
L'implémentation de Parser.java est terminée.
Parser.java
private Token bind(Token left, Token operator) throws Exception {
if (binaryKinds.contains(operator.kind)) {
operator.left = left;
int leftDegree = degree(operator);
if (rightAssocs.contains(operator.value)) {
leftDegree -= 1;
}
operator.right = expression(leftDegree);
return operator;
} else if(operator.kind.equals("paren") && operator.value.equals("(")) { // <-- Add
operator.left = left;
operator.right = expression(0);
consume(")");
return operator;
} else {
throw new Exception("The token cannot place there.");
}
}
private Token consume(String expectedValue) throws Exception {
if (!expectedValue.equals(token().value)) {
throw new Exception("Not expected value");
}
return next();
}
Interpreter.java
Une implémentation d'Interpreter.java. Tout d'abord, afin de faire la distinction entre les noms de variables et les noms de fonctions, nous allons introduire une classe qui représente chacun. La fonction est une classe abstraite pour une expansion future. La méthode ʻInvoke () `implémente le traitement réellement effectué par la fonction.
Interpreter.java
public static class Variable {
public String name;
public Integer value;
@Override
public String toString() {
return name + " " + value;
}
}
public static abstract class Func {
public String name;
abstract public Object invoke(Object arg) throws Exception;
}
public static class Println extends Func {
public Println() {
name = "println";
}
@Override
public Object invoke(Object arg) throws Exception {
System.out.println(arg);
return null;
}
}
C'est un changement de la partie d'initialisation.
Ajout d'une carte pour contenir le nom de la fonction et sa valeur pour correspondre à la fonction.
Ajoutez le corps de println ()
ici.
Interpreter.java
public class Interpreter {
public Map<String, Func> functions;
public Map<String, Variable> variables;
List<Token> body;
public Interpreter init(List<Token> body) {
functions = new HashMap<>();
Func f = new Println();
functions.put(f.name, f);
variables = new HashMap<>();
this.body = body;
return this;
}
C'est un changement de la partie qui appelle la méthode pour traiter chacun selon la signification du jeton. Ajout d'un appel de fonction où «<-Add» est. Le changement à ident est l'endroit où «<-Update» est.
Interpreter.java
public Object expression(Token expr) throws Exception {
if (expr.kind.equals("digit")) {
return digit(expr);
} else if (expr.kind.equals("ident")) { // <-- Update
return ident(expr);
} else if (expr.kind.equals("paren")) { // <-- Add
return invoke(expr);
} else if (expr.kind.equals("sign") && expr.value.equals("=")) {
return assign(expr);
} else if (expr.kind.equals("sign")) {
return calc(expr);
} else {
throw new Exception("Expression error");
}
}
Changement de var ()
en ʻident () `.
Si l'identifiant est un nom de fonction, il sera résolu en fonction.
Sinon, il se résout en une variable.
Interpreter.java
public Object ident(Token token) {
String name = token.value;
if (functions.containsKey(name)) {
return functions.get(name);
}
if (variables.containsKey(name)) {
return variables.get(name);
} else {
Variable v = new Variable();
v.name = name;
v.value = 0;
variables.put(name, v);
return v;
}
}
Le med suivant prend en charge la classe Variable introduite pour représenter les noms de variables.
Interpreter.java
public Variable assign(Token expr) throws Exception {
Variable variable = variable(expression(expr.left));
Integer value = value(expression(expr.right));
variable.value = value;
return variable;
}
public Variable variable(Object value) throws Exception {
if (value instanceof Variable) {
return (Variable) value;
} else {
throw new Exception("left value error");
}
}
public Integer value(Object value) throws Exception {
if (value instanceof Integer) {
return (Integer) value;
} else if (value instanceof Variable) {
Variable v = (Variable) value;
return v.value;
}
throw new Exception("right value error");
}
ʻInvoke () fait un appel de fonction. C'est ce que je voulais faire dans cet article.
func ()est le processus de ʻinvoke ()
et confirme que le résultat de ʻexpr.left` est le nom de la fonction.
Interpreter.java
private Object invoke(Token expr) throws Exception {
Func f = func(expression(expr.left));
Integer value = value(expression(expr.right));
return f.invoke(value);
}
public Func func(Object value) throws Exception {
if (value instanceof Func) {
return (Func) value;
} else {
throw new Exception("Not a function");
}
}
La chaîne de caractères dans le programme ci-dessous en utilisant l'implémentation ci-dessus
a = 3 + 4 * 5
println(a)
Est calculée et la valeur affectée à la variable est imprimée sur la sortie standard.
Interpreter.java
public static void main(String[] args) throws Exception {
String text = "a = 3 + 4 * 5";
text += "println(a)";
List<Token> tokens = new Lexer().init(text).tokenize();
List<Token> blk = new Parser().init(tokens).block();
new Interpreter().init(blk).run();
// --> 23
}
}
C'est tout pour la mise en œuvre. Merci beaucoup.
La source complète est disponible ici.
Calc https://github.com/quwahara/Calc/tree/article-4-interpreter/Calc/src/main/java
Il y a un article de suite.
** Correspond aux parenthèses prioritaires ** http://qiita.com/quwahara/items/b76c6e438aeb32450391
Recommended Posts