Dans l'article précédent, Prend en charge les déclarations while. Maintenant, je voudrais correspondre à la variable Scope.
Confirmez ce que vous voulez faire avec la prise en charge de Scope.
Par exemple, il existe le programme suivant.
Les variables «a», «b», «c» sont utilisées à l'intérieur et à l'extérieur de la fonction «f ()».
Puisque la portée change à l'intérieur et à l'extérieur de la fonction, ʻaet
b déclarés avec
varà l'intérieur de la fonction ne sont valides qu'à l'intérieur de la fonction.
Println (a)dans la fonction renvoie
10, et à l'extérieur, la valeur
1attribuée par la portée globale est sortie. De même pour «b», «20» est sorti à l'intérieur et «2» est sorti à l'extérieur.
cn'est pas déclaré à l'intérieur de la fonction et est une variable de portée globale à la fois à l'intérieur et à l'extérieur.
println (c) imprime
30` à la fois à l'intérieur et à l'extérieur.
La déclaration var
vous permet d'affecter des variables afin de pouvoir déclarer plusieurs variables en même temps, séparées par,
(virgule).
a = 1
b = 2
c = 3
function f() {
var a = 10, b
b = 20
c = 30
println(a)
println(b)
println(c)
}
f()
println(a)
println(b)
println(c)
Nous examinerons comment l'implémenter dans l'ordre de l'analyse syntaxique (Parser) et de l'interpréteur (Interpreter).
Puisque le concept de Scope lui-même est exprimé à l'intérieur ou à l'extérieur de la fonction, Du point de vue de la mise en œuvre de l'analyse Si la définition de fonction peut être analysée, le concept de portée peut être pris en charge.
La déclaration de variable «var» est nouvellement introduite.
Le mot-clé var
vient au début de la séquence de jetons, donc
[Analyse de la syntaxe des instructions de définition de fonction] implémentées dans l'article précédent (http://qiita.com/quwahara/items/be71bac4b4359f5e6727#%E6%A7%8B%E6%96%87%E8%A7%A3%E6% Mettre en œuvre de la même manière que 9E% 90parser% E3% 81% AE% E5% AE% 9F% E8% A3% 85% E3% 81% AE% E4% BB% 95% E6% 96% B9).
Introduisez une classe qui représente le concept de Scope. La classe Scope prépare des variables de champ qui peuvent exprimer la relation parent-enfant afin qu'elle puisse représenter la structure hiérarchique de Scope.
Crée une étendue globale par défaut au démarrage de l'interpréteur. Lorsque vous appelez une fonction, créez une portée dans la fonction et faites de la portée de l'appelant de fonction la portée parent de la portée dans la fonction. Après avoir appelé la fonction, l'étendue de la fonction est ignorée et l'étendue parent est renvoyée à l'étendue actuelle.
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 var
est un mot réservé, je l'ai ajouté à // 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("==", 40);
degrees.put("!=", 40);
degrees.put("<", 40);
degrees.put("<=", 40);
degrees.put(">", 40);
degrees.put(">=", 40);
degrees.put("&&", 30);
degrees.put("||", 30);
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[] { "+", "-", "!" });
// Update
reserved = Arrays.asList(new String[] { "function", "return", "if", "else", "while", "break", "var" });
}
C'est un changement de la pièce à analyser.
Ajout d'un appel à la fonction qui analyse var
to // Add
.
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")) {
token.kind = "ret";
if (!token().kind.equals("eob")) {
token.left = expression(0);
}
return token;
} else if (token.kind.equals("ident") && token.value.equals("if")) {
return if_(token);
} else if (token.kind.equals("ident") && token.value.equals("while")) {
return while_(token);
} else if (token.kind.equals("ident") && token.value.equals("break")) {
token.kind = "brk";
return token;
// Add
} else if (token.kind.equals("ident") && token.value.equals("var")) {
return var(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.");
}
}
C'est un changement de la pièce à analyser.
Ajout de la méthode var ()
pour analyser les déclarations var.
Le résultat de l'analyse de la déclaration var est résumé dans l'argument token.
La méthode var ()
analyse les modèles suivants.
var
→ var a
var a = 0
,
(virgule) → var a = 0, b
L'affectation à token.kind
au début du traitement détermine la signification du jeton à var
.
token.block
contient le résultat analysé.
Comme plusieurs déclarations peuvent être faites avec ,
(virgule), le résultat de l'analyse est stocké dans token.block
, qui est une liste pouvant contenir plusieurs.
Effectue l'analyse immédiatement après «var».
Puisque var
doit toujours être fourni avec le nom de la variable, demandez le nom de la variable avec ʻident (). S'il y a une affectation après le nom de la variable, un jeton
=sera ajouté. Lorsqu'un jeton
=arrive, il est analysé de la même manière que l'opérateur binaire, et le résultat de l'analyse est stocké dans
token.block. Si ce n'est pas un token
=, le résultat de ʻident ()
est conservé tel quel dans token.block
.
Après cela, s'il y a des jetons ,
, continuez à les analyser de la même manière et conservez-les dans token.block
.
Parser.java
private Token var(Token token) throws Exception {
token.kind = "var";
token.block = new ArrayList<Token>();
Token item;
Token ident = ident();
if (token().value.equals("=")) {
Token operator = next();
item = bind(ident, operator);
} else {
item = ident;
}
token.block.add(item);
while (token().value.equals(",")) {
next();
ident = ident();
if (token().value.equals("=")) {
Token operator = next();
item = bind(ident, operator);
} else {
item = ident;
}
token.block.add(item);
}
return token;
}
Interpreter.java
Une implémentation d'Interpreter.java.
Introduction d'une classe qui représente Scope. Les fonctions des «variables de champ» et des «variables» qui étaient dans la classe Interpreter sont portées. Il a une variable de champ «parent» qui représente la hiérarchie parent pour représenter la structure hiérarchique de l'étendue.
Interpreter.java
public static class Scope {
public Scope parent;
public Map<String, Func> functions;
public Map<String, Variable> variables;
public Scope() {
functions = new HashMap<>();
variables = new HashMap<>();
}
}
C'est la partie d'initialisation.
La classe Scope introduite est utilisée comme variable de champ.
Préparez les portées pour «global» et «local».
local
Scope alloue une nouvelle instance de la classe Scope chaque fois qu'elle est exécutée dans une fonction.
Interpreter.java
Scope global;
Scope local;
List<Token> body;
public Interpreter init(List<Token> body) {
global = new Scope();
local = global;
Func f = new Println();
global.functions.put(f.name, f);
this.body = body;
return this;
}
Ceci est une modification de la méthode body ()
qui exécute les expressions séquentiellement.
Ajout d'un appel de méthode pour gérer la déclaration var à // <-Add
.
Interpreter.java
public Object body(List<Token> body, boolean[] ret, boolean[] brk) throws Exception {
for (Token exprs : body) {
if (exprs.kind.equals("if")) {
Object val = if_(exprs, ret, brk);
if (ret != null && ret[0]) {
return val;
}
} else 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 if (exprs.kind.equals("while")) {
Object val = while_(exprs, ret);
if (ret != null && ret[0]) {
return val;
}
} else if (exprs.kind.equals("brk")) {
if (brk == null) {
throw new Exception("Can not break");
}
brk[0] = true;
return null;
} else if (exprs.kind.equals("var")) { // <-- Add
var(exprs);
} else {
expression(exprs);
}
}
return null;
}
La méthode var ()
qui gère la déclaration var.
Comme plusieurs déclarations sont conservées dans token.block
, elles sont traitées dans l'ordre.
Si l'élément de token.block
est un jeton du nom de la variable, définissez simplement la variable dans la portée locale.
Si l'élément est un jeton =
, définissez une variable dans la portée locale en utilisant le jeton de nom de variable ʻitem.left. Puis exécutez le jeton
=avec ʻexpression (expr)
.
Interpreter.java
public Object var(Token token) throws Exception {
for (Token item : token.block) {
String name;
Token expr;
if (item.kind.equals("ident")) {
name = item.value;
expr = null;
} else if (item.kind.equals("sign") && item.value.equals("=")) {
name = item.left.value;
expr = item;
} else {
throw new Exception("var error");
}
if (!local.variables.containsKey(name)) {
newVariable(name);
}
if (expr != null) {
expression(expr);
}
}
return null;
}
public Variable newVariable(String name) {
Variable v = new Variable();
v.name = name;
v.value = 0;
local.variables.put(name, v);
return v;
}
Ceci est un changement de la méthode ʻident () dû à l'introduction de Scope. Recherchez les fonctions et les variables définies dans l'instruction while. Depuis la portée locale, utilisez la variable de champ
parent` pour revenir à la portée supérieure.
Interpreter.java
public Object ident(Token token) {
String name = token.value;
Scope scope = local;
while (scope != null) {
if (scope.functions.containsKey(name)) {
return scope.functions.get(name);
}
if (scope.variables.containsKey(name)) {
return scope.variables.get(name);
}
scope = scope.parent;
}
return newVariable(name);
}
Ceci est un changement de la méthode func ()
dû à l'introduction de Scope.
// Update
Voici les changements.
De ce qui n'était qu'une affectation de «context» à «interprète»
Changé pour cloner et assigner l'interpréteur. Rechercher les fonctions et les variables définies dans l'instruction while. Depuis la portée locale, utilisez la variable de champ
parent` pour revenir à la portée supérieure.
Interpreter.java
public Object func(Token token) throws Exception {
String name = token.ident.value;
if (local.functions.containsKey(name)) {
throw new Exception("Name was used");
}
if (local.variables.containsKey(name)) {
throw new Exception("Name was used");
}
List<String> paramCheckList = new ArrayList<String>();
for (Token p : token.params) {
String param = p.value;
if (paramCheckList.contains(param)) {
throw new Exception("Parameter name was used");
}
paramCheckList.add(param);
}
DynamicFunc func = new DynamicFunc();
// Update
func.context = new Interpreter();
func.context.global = global;
func.context.local = local;
func.context.body = body;
func.name = name;
func.params = token.params;
func.block = token.block;
local.functions.put(name, func);
return null;
}
Il s'agit d'un changement de la classe DynamicFunc
dû à l'introduction de Scope.
Avant d'effectuer un appel de fonction, créez une nouvelle étendue avec l'étendue locale actuelle comme étendue parent.
Après l'appel, l'étendue locale est restaurée dans l'étendue d'origine.
Définissez l'argument formel dans la nouvelle étendue et exécutez le bloc de traitement des fonctions.
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 {
Scope parent = context.local;
context.local = new Scope();
context.local.parent = parent;
for (int i = 0; i < params.size(); ++i) {
Token param = params.get(i);
Variable v = context.newVariable(param.value);
if (i < args.size()) {
v.value = context.value(args.get(i));
} else {
v.value = null;
}
}
Object val;
boolean[] ret = new boolean[1];
val = context.body(block, ret, null);
context.local = parent;
return val;
}
}
Le programme ci-dessous utilisant l'implémentation ci-dessus
a = 1
b = 2
c = 3
function f() {
var a = 10, b
b = 20
c = 30
println(a)
println(b)
println(c)
}
f()
println(a)
println(b)
println(c)
Est exécuté, et les valeurs des variables «a», «b» et «c» pour chaque portée sont sorties vers la sortie standard.
Interpreter.java
public static void main(String[] args) throws Exception {
String text = "";
text += "a = 1";
text += "b = 2";
text += "c = 3";
text += "function f() {";
text += " var a = 10, b";
text += " b = 20";
text += " c = 30";
text += " println(a)";
text += " println(b)";
text += " println(c)";
text += "}";
text += "f()";
text += "println(a)";
text += "println(b)";
text += "println(c)";
List<Token> tokens = new Lexer().init(text).tokenize();
List<Token> blk = new Parser().init(tokens).block();
new Interpreter().init(blk).run();
// --> 10
// --> 20
// --> 30
// --> 1
// --> 2
// --> 30
}
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-13-scope-r3/Calc/src/main/java
Il y a un article de suite.
** Correspond à une expression de fonction ** http://qiita.com/quwahara/items/ae33ed944afc34cc5fac
Recommended Posts