Dans l'article précédent, Supporte plusieurs arguments. Je souhaite continuer à correspondre à la valeur de retour de la fonction.
Assurez-vous de vouloir le faire facilement.
Par exemple, si vous avez un programme comme celui ci-dessous, la valeur de retour de l'appel de fonction ʻadd3 () est renvoyée et affectée à la variable v`.
Nous visons à afficher la valeur «6» de la variable «v» dans la sortie standard.
function add3(a1, a2, a3) {
return a1 + a2 + a3
}
v = add3(1,2,3)
println(v)
C'est le même que l'article précédent qu'il ne correspond pas à la portée des variables.
Nous examinerons comment l'implémenter dans l'ordre de l'analyse syntaxique (Parser) et de l'interpréteur (Interpreter). L'analyse de phrase (Lexer) n'est pas modifiée.
Lors de l'implémentation de l'analyse syntaxique de «return», vous devez faire attention à la syntaxe de «return».
La syntaxe de «return» est parfois spécifiée comme valeur de retour, telle que «return 1».
Dans certains cas, seul «return» est spécifié et aucune valeur de retour n'est spécifiée.
Si la spécification de seulement «return» est correcte dans la syntaxe,
Quand return 1 est écrit,
Il peut être pris comme syntaxe lorsqu'une valeur de retour est spécifiée,
Considérant que «1» est une syntaxe indépendante de «return»,
Elle peut être considérée comme la syntaxe lorsque la valeur de retour n'est pas spécifiée.
Pour résoudre facilement cette situation, nous adopterons une approche bâclée.
La méthode est quand le jeton } vient après le jeton return,
Considéré comme «return» si aucune valeur de retour n'est spécifiée.
Au contraire, si le jeton } ne vient pas, il est considéré comme return lorsque la valeur de retour est spécifiée.
L'analyse lorsqu'aucune valeur de retour n'est spécifiée est traitée de la même manière qu'un nom de variable.
L'analyse lorsqu'une valeur de retour est spécifiée est traitée de la même manière qu'un opérateur de terme unique tel que «-1».
L'interpréteur modifie la méthode body (), qui exécute les expressions séquentiellement.
S'il s'agit d'un jeton return dans body (),
Suspend l'exécution séquentielle et retourne à l'appelant.
De plus, return ne peut être appelé que dans une fonction.
Il détermine également s'il s'agit d'un appel «return» dans la fonction.
Passez à la mise en œuvre. À propos de l'analyseur syntaxique (Parser) et de l'interpréteur (Interpreter) Jetons un coup d'œil aux modifications et aux ajouts dans l'ordre.
Parser.java
Une implémentation de Parser.java.
Ajoutez une définition de la façon dont cela fonctionne pour la signification du jeton.
Puisque return est un mot réservé,
J'ai ajouté return là où il y a <-Update.
Parser.java
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", "return" }); // <-- Update
}
C'est un changement de pièce à analyser.
Ajout d'une instruction if qui analyse return vers où <-Add est.
Sauf si le jeton à côté du jeton «return» est «eob», qui représente le type de parenthèse ondulée fermante.
Juge comme un «retour» qui renvoie une valeur et contient le jeton qui devient la valeur de retour dans le champ «gauche».
Parser.java
private Token lead(Token token) throws Exception {
if (token.kind.equals("ident") && token.value.equals("function")) {
return func(token);
} else if (token.kind.equals("ident") && token.value.equals("return")) { // <-- Add
token.kind = "ret";
if (!token().kind.equals("eob")) {
token.left = expression(0);
}
return 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.");
}
}
Interpreter.java
Une implémentation d'Interpreter.java.
Ceci est une modification de la méthode body () qui exécute les expressions séquentiellement.
body () était un type void qui ne renvoie pas de valeur de retour, mais il a été changé en type ʻObjectafin de pouvoir renvoyer une valeur de retour. Ajout deboolean [] ret à la signature de body (). reta deux rôles. La première est que si «ret» n'est pas «null», alors «return» est possible. La seconde est que s'il s'agissait de «return», il propagerait à l'appelant qu'il était «return». La raison pour laquelle «ret» est un tableau de type «booléen» est de renvoyer une valeur à l'appelant. Il est utilisé de manière malveillante, pas dans le but d'utiliser le tableau d'origine. Je voulais faciliter le travail comme un argument avec le mot-cléref` en C #.
La première chose dans «for» est de déterminer si le jeton à exécuter séquentiellement est «return».
Si ce n'est pas "return", c'est le même processus que le précédent "body ()".
S'il s'agit de «retour», on juge si la situation est possible.
Remplacez «true» pour dire à l'appelant qu'il devait «retourner» à «ret».
Pour un return avec une valeur de retour, exécutez le jeton avec la valeur de retour avec ʻexpression ()`.
Interpreter.java
public Object body(List<Token> body, boolean[] ret) throws Exception {
for (Token exprs : body) {
if (exprs.kind.equals("ret")) {
if (ret == null) {
throw new Exception("Can not return");
}
ret[0] = true;
if (exprs.left == null) {
return null;
} else {
return expression(exprs.left);
}
} else {
expression(exprs);
}
}
return null;
}
Ceci est une modification de la méthode ʻinvoke () de la classe DynamicFunc. «<-Update» est le changement. Prend en charge la modification de la signature et de la valeur de retour de la méthode body (). L'affectation d'une instance à l'argument ret indique que la situation peut être renvoyée. La valeur de retour de body ()est utilisée telle quelle comme valeur de retour de ʻinvoke ().
Interpreter.java
public static class DynamicFunc extends Func {
public Interpreter context;
public List<Token> params;
public List<Token> block;
@Override
public Object invoke(List<Object> args) throws Exception {
for (int i = 0; i < params.size(); ++i) {
Token param = params.get(i);
Variable v = context.variable(context.ident(param));
if (i < args.size()) {
v.value = context.value(args.get(i));
} else {
v.value = null;
}
}
boolean[] ret = new boolean[1]; // <-- Update
return context.body(block, ret); // <-- Update
}
}
Ceci est une modification de la méthode run ().
«<-Update» est le changement.
Prend en charge la modification de la signature et de la valeur de retour de la méthode body ().
La situation où run () est appelé n'est pas à l'intérieur de la fonction.
Puisque la situation ne peut pas être «return», l'argument formel «ret» spécifie «null».
Interpreter.java
public Map<String, Variable> run() throws Exception {
body(body, null); // <-- Update
return variables;
}
Le programme ci-dessous utilisant l'implémentation ci-dessus
function add3(a1, a2, a3) {
return a1 + a2 + a3
}
v = add3(1,2,3)
println(v)
Pour imprimer la valeur «6» affectée à la variable «v» sur la sortie standard.
Interpreter.java
public static void main(String[] args) throws Exception {
String text = "";
text += "function add3(a1, a2, a3) {";
text += " return a1 + a2 + a3";
text += "}";
text += "v = add3(1,2,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();
// --> 6
}
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-9-return-r2/Calc/src/main/java
Il y a un article de suite.
** Correspond à l'instruction if ** http://qiita.com/quwahara/items/96a68cdee4f2a0452836
Recommended Posts