[JAVA] Correspond à la portée

introduction

Dans l'article précédent, Prend en charge les déclarations while. Maintenant, je voudrais correspondre à la variable Scope.

Ce que vous voulez faire avec le support 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, ʻaetb 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 renvoie10, 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)

Comment mettre en œuvre

Nous examinerons comment l'implémenter dans l'ordre de l'analyse syntaxique (Parser) et de l'interpréteur (Interpreter).

Comment implémenter un analyseur

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).

Comment implémenter Interpreter

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.

Essayez de mettre en œuvre en Java

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.

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é danstoken.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». localScope 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.

en conclusion

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

Correspond à la portée
17 Correspond à un tableau
Correspond à 15 chaînes
8 Correspond à plusieurs arguments
10 Correspond à l'instruction if
14 Correspond à une expression de fonction
19 Correspond à la création d'objet
16 Correspond à l'invocation de méthode
9 Correspond à la valeur de retour
18 Correspond à la définition d'objet de type JSON
Passez des variables à Scope.
Portée
20 Correspond aux appels de méthode statiques
à_ ○
[Rails] Comment utiliser Scope
11 Correspond aux opérateurs de comparaison et logiques
Association (1 à 1)! !!
Comment tester l'étendue privée avec JUnit
À propos de la portée
form_with scope
Comment utiliser la portée et le traitement des passes (servist)