Ajout de println to interpreter dans l'article précédent. Étendons-le un peu pour permettre les définitions de fonctions et les appels.
Lors de la mise en œuvre, assurez-vous que vous faites ce que vous voulez faire et ce que vous ne faites pas.
Par exemple, si vous avez un programme comme celui ci-dessous, la fonction ʻaddV () est définie et nous visons à afficher
3` sur la sortie standard.
v = 0
function addV(num) {
v = v + num
}
addV(3)
println(v)
Et je décide de ne pas le faire par souci de simplicité. Un seul argument de fonction est pris en charge. Il ne prend en charge aucun argument ou plus d'un argument. Il ne correspond pas à la valeur de retour de la fonction. Ne correspond pas à la portée de la variable. Le nom de variable de l'argument formel est également défini globalement.
Nous examinerons comment l'implémenter dans l'ordre de l'analyse des phrases (Lexer), de l'analyse syntaxique (Parser) et de l'interpréteur (Interpreter).
Puisque l'analyse de phrase implémentée n'a pas la fonction d'analyser les parenthèses ondulées, {
et }
, nous la mettrons en œuvre.
Tout d'abord, considérons une implémentation qui analyse l'instruction de définition de fonction.
Observez la séquence de jetons dans la définition de fonction function addV (num) {}
pour réfléchir à la façon de l'implémenter.
En l'observant, vous pouvez voir que le mot-clé «fonction» vient toujours au début de la séquence de jetons.
Considérant s'il existe un autre modèle d'arrangement similaire, où un arrangement fixe de jetons vient en premier,
Demandez-vous s'il peut être implémenté sous une forme proche de celle-là.
Dans cet esprit, les opérateurs monomorphes "-1" et "+ 1" sont proches.
C'est parce que si le «-» ou «+» du jeton qui vient au début est décidé, il sera décidé comme un opérateur monomorphe.
Il semble que l'analyse syntaxique de la définition de la fonction puisse être implémentée de manière similaire.
Ensuite, considérons l'implémentation de l'analyse syntaxique de l'instruction de l'appel de fonction.
L'appel a la même structure syntaxique que le println (v)
précédemment implémenté.
Par conséquent, il est déjà mis en œuvre.
Tout d'abord, comment implémenter la définition de fonction.
Introduction d'une classe qui représente une fonction lors de l'implémentation de println ()
.
L'implémentation de la fonction println ()
hérite de la classe Func
.
Il semble que l'implémentation de la définition de fonction dans l'interpréteur doit également hériter de la classe Func
de la même manière.
Et si vous rencontrez la partie de définition de fonction pendant que l'interpréteur est en cours d'exécution,
Je vais instancier dynamiquement la classe héritée.
Ensuite, comment implémenter l'appel de fonction.
Quant à l'appel, l'implémentation de println ()
peut être utilisée telle quelle, comme dans le cas de l'analyse syntaxique.
Il est prêt à être mis en œuvre.
Passez à la mise en œuvre. À propos de l'analyse de phrases (Lexer), de l'analyse syntaxique (Parser), de l'interpréteur (Interpreter) Jetons un coup d'œil aux modifications et aux ajouts dans l'ordre.
Lexer.java
Une implémentation de Lexer.java.
Tout d'abord, ajoutez la fonction pour analyser les parenthèses, {
et }
.
La fin de la parenthèse ondulée }
est traitée spécialement comme la fin du bloc, donc
La signification est «eob» (fin de bloc).
Lexer.java
private boolean isCurlyStart(char c) {
return c == '{' || c == '}';
}
private Token curly() throws Exception {
Token t = new Token();
if (c() == '{') {
t.kind = "curly";
} else {
t.kind = "eob";
}
t.value = Character.toString(next());
return t;
}
J'ajouterai la partie appel de l'analyse des tranches d'onde. 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 if (isCurlyStart(c())) {
return curly();
} else {
throw new Exception("Not a character for tokens");
}
}
Parser.java
Une implémentation de Parser.java.
Tout d'abord, ajoutez une définition de la façon dont cela fonctionne pour la signification du jeton.
Ajoutez réservé
pour définir le mot réservé à <-Ajouter
, et ajoutez-y fonction
.
Parser.java
private Map<String, Integer> degrees;
private List<String> factorKinds;
private List<String> binaryKinds;
private List<String> rightAssocs;
private List<String> unaryOperators;
private List<String> reserved; // <-- Add
public Parser() {
degrees = new HashMap<>();
degrees.put("(", 80);
degrees.put("*", 60);
degrees.put("/", 60);
degrees.put("+", 50);
degrees.put("-", 50);
degrees.put("=", 10);
factorKinds = Arrays.asList(new String[] { "digit", "ident" });
binaryKinds = Arrays.asList(new String[] { "sign" });
rightAssocs = Arrays.asList(new String[] { "=" });
unaryOperators = Arrays.asList(new String[] { "+", "-" });
reserved = Arrays.asList(new String[] { "function" }); // <-- Add
}
C'est un changement de la pièce à analyser.
Ajout d'un appel à la méthode func ()
pour analyser la définition de la fonction dans l'instruction if avec <-Add
.
Semblable à la gestion de l'unaire, le premier type de jeton détermine la méthode d'analyse syntaxique.
Parser.java
private Token lead(Token token) throws Exception {
if (token.kind.equals("ident") && token.value.equals("function")) { // <-- Add
return func(token);
} else if (factorKinds.contains(token.kind)) {
return token;
} else if (unaryOperators.contains(token.value)) {
token.kind = "unary";
token.left = expression(70);
return token;
} else if (token.kind.equals("paren") && token.value.equals("(")) {
Token expr = expression(0);
consume(")");
return expr;
} else {
throw new Exception("The token cannot place there.");
}
}
Ajout de la méthode func ()
pour analyser les définitions de fonction.
Le résultat de l'analyse de la définition de fonction est résumé dans l'argument token
.
Pour résumer, nous ajoutons des variables de champ à la classe Token
.
Les variables de champ ajoutées sont ʻident,
param,
block. ʻIdent
contient un jeton qui représente le nom de la fonction.
param
contient un jeton qui représente un argument formel.
block
contient le bloc de traitement de la fonction et son type est List <Token>
.
Le traitement dans la méthode func ()
trace les jetons dans la définition de fonction dans l'ordre.
Tout d'abord, déterminez la signification du jeton avec func
.
Ensuite, récupérez le nom de la fonction en appelant la méthode ʻident () . La méthode ʻident ()
vérifie que le token signifie ʻidentet renvoie ce token. Ensuite, il consomme le
(au début de l'argument. Récupérez à nouveau l'argument formel avec l'appel de la méthode ʻident ()
.
Il consomme alors le )
à la fin de l'argument et le {
au début du bloc.
Le contenu du bloc se verra attribuer la valeur de retour du block ()
existant tel quel.
Enfin, si vous consommez le «}» à la fin du bloc, l'analyse syntaxique de la définition de la fonction est terminée.
L'implémentation de Parser.java est également terminée.
Parser.java
private Token func(Token token) throws Exception {
token.kind = "func";
token.ident = ident();
consume("(");
token.param = ident();
consume(")");
consume("{");
token.block = block();
consume("}");
return token;
}
private Token ident() throws Exception {
Token id = next();
if (!id.kind.equals("ident")) {
throw new Exception("Not an identical token.");
}
if (reserved.contains(id.value)) {
throw new Exception("The token was reserved.");
}
return id;
}
Interpreter.java
Une implémentation d'Interpreter.java.
Introduisez une classe DynamicFunc
qui représente une fonction.
Il hérite de la classe Func
.
Variables de champ
Context
pour piloter l'interpréteurparam
qui représente un argument formelblock
il y a.
L'implémentation de la méthode ʻinvoke () résout l'argument comme une valeur et le garde dans la valeur de l'argument formel. Dans cet état, laissez l'interpréteur
context execute
block`.
Interpreter.java
public static class DynamicFunc extends Func {
public Interpreter context;
public Token param;
public List<Token> block;
@Override
public Object invoke(Object arg) throws Exception {
Variable v = context.variable(context.ident(param));
v.value = context.value(arg);
context.body(block);
return null;
}
}
C'est un changement de la partie qui appelle la méthode pour traiter chacun selon la signification du jeton. Ajout d'une définition de fonction où «<-Add» est.
Interpreter.java
public Object expression(Token expr) throws Exception {
if (expr.kind.equals("digit")) {
return digit(expr);
} else if (expr.kind.equals("ident")) {
return ident(expr);
} else if (expr.kind.equals("func")) { // <-- Add
return func(expr);
} else if (expr.kind.equals("paren")) {
return invoke(expr);
} else if (expr.kind.equals("sign") && expr.value.equals("=")) {
return assign(expr);
} else if (expr.kind.equals("unary")) {
return unaryCalc(expr);
} else if (expr.kind.equals("sign")) {
return calc(expr);
} else {
throw new Exception("Expression error");
}
}
Implémentation de la partie définition de fonction.
Tout d'abord, vérifiez si le nom de la fonction et le nom de l'argument formel sont déjà utilisés.
Générez une classe DynamicFunc
qui représente une fonction et attribuez une valeur à une variable de champ.
L'argument formel param
est défini comme une variable à l'avance,
Allouez l'instance qui représente la variable à la variable de champ.
Enfin, ajoutez-le à la carte qui contient le nom de la fonction et sa valeur, et la définition de la fonction est terminée.
Interpreter.java
public Object func(Token token) throws Exception {
String name = token.ident.value;
if (functions.containsKey(name)) {
throw new Exception("Name was used");
}
if (variables.containsKey(name)) {
throw new Exception("Name was used");
}
DynamicFunc func = new DynamicFunc();
func.context = this;
func.name = name;
func.param = token.param;
func.block = token.block;
functions.put(name, func);
return null;
}
La chaîne de caractères dans le programme ci-dessous en utilisant l'implémentation ci-dessus
v = 0
function addV(num) {
v = v + num
}
addV(3)
println(v)
Pour imprimer la valeur «3» affectée à la variable «v» sur la sortie standard.
Interpreter.java
public static void main(String[] args) throws Exception {
String text = "";
text += "v = 0";
text += "function addV(num) {";
text += " v = v + num";
text += "}";
text += "addV(3)";
text += "println(v)";
List<Token> tokens = new Lexer().init(text).tokenize();
List<Token> blk = new Parser().init(tokens).block();
new Interpreter().init(blk).run();
// --> 3
}
}
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-7-function-r2/Calc/src/main/java
Il y a un article de suite.
** Correspond à plusieurs arguments ** http://qiita.com/quwahara/items/0bea3bad4eedd2d803cf