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 de
boolean [] 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