[JAVA] Entspricht dem Geltungsbereich

Einführung

Im vorherigen Artikel Unterstützt while-Anweisungen. Jetzt möchte ich der Variablen Scope entsprechen.

Was Sie mit der Scope-Unterstützung tun möchten

Bestätigen Sie, was Sie mit der Scope-Unterstützung tun möchten.

Zum Beispiel gibt es das folgende Programm. Die Variablen "a", "b" und "c" werden innerhalb und außerhalb der Funktion "f ()" verwendet. Da sich der Bereich innerhalb und außerhalb der Funktion ändert, sind "a" und "b", die mit "var" innerhalb der Funktion deklariert wurden, nur innerhalb der Funktion gültig. "Println (a)" in den Funktionsausgängen "10" und außerhalb wird der vom globalen Bereich zugewiesene Wert "1" ausgegeben. In ähnlicher Weise wird für "b" "20" innerhalb und "2" außerhalb ausgegeben. c wird nicht innerhalb der Funktion deklariert und ist sowohl innerhalb als auch außerhalb eine globale Bereichsvariable. println (c) druckt 30 innen und außen.

Mit der var-Deklaration können Sie Variablen zuweisen und durch, (Komma) trennen, sodass Sie mehrere Variablen gleichzeitig deklarieren können.

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)

Wie zu implementieren

Wir werden überlegen, wie es in der Reihenfolge Syntaxanalyse (Parser) und Interpreter (Interpreter) implementiert werden kann.

So implementieren Sie Parser

Da das Konzept von Scope selbst innerhalb oder außerhalb der Funktion ausgedrückt wird, Aus Sicht der Analyseimplementierung Wenn die Funktionsdefinition analysiert werden kann, kann das Konzept des Bereichs unterstützt werden.

Die Variablendeklaration "var" wird neu eingeführt. Das Schlüsselwort "var" steht also am Anfang der Token-Sequenz [Syntaxanalyse von Funktionsdefinitionsanweisungen], implementiert im vorherigen Artikel (http://qiita.com/quwahara/items/be71bac4b4359f5e6727#%E6%A7%8B%E6%96%87%E8%A7%A3%E6% Implementieren Sie auf die gleiche Weise wie 9E% 90parser% E3% 81% AE% E5% AE% 9F% E8% A3% 85% E3% 81% AE% E4% BB% 95% E6% 96% B9).

So implementieren Sie Interpreter

Stellen Sie eine Klasse vor, die das Konzept von Scope darstellt. Die Scope-Klasse bereitet Feldvariablen vor, die die Eltern-Kind-Beziehung ausdrücken können, damit die hierarchische Struktur von Scope ausgedrückt werden kann.

Erstellt einen globalen Standardbereich, wenn der Interpreter gestartet wird. Erstellen Sie beim Aufrufen einer Funktion einen Bereich in der Funktion und machen Sie den Bereich des Funktionsaufrufers zum übergeordneten Bereich des Bereichs in der Funktion. Nach dem Aufrufen der Funktion wird der Bereich in der Funktion verworfen und der übergeordnete Bereich wird an den aktuellen Bereich zurückgegeben.

Versuchen Sie, in Java zu implementieren

Fahren Sie mit der Implementierung fort. Informationen zu Syntaxparser (Parser) und Interpreter (Interpreter) Schauen wir uns die Änderungen und Ergänzungen der Reihe nach an.

Parser.java

Eine Implementierung von Parser.java. Fügen Sie eine Definition hinzu, wie es für die Bedeutung des Tokens funktioniert. Da var ein reserviertes Wort ist, habe ich es zu // Update hinzugefügt.

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" });
    }

Es ist eine Änderung des zu analysierenden Teils. Der Funktion, die var to // Add analysiert, wurde ein Aufruf hinzugefügt.

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.");
        }
    }

Es ist eine Änderung des zu analysierenden Teils. Methode var () hinzugefügt, um var-Deklarationen zu analysieren. Das Analyseergebnis der var-Deklaration ist im Token-Argument zusammengefasst. Die Methode var () analysiert die folgenden Muster.

Die Zuweisung zu "token.kind" zu Beginn der Verarbeitung bestimmt die Bedeutung des Tokens zu "var". token.block enthält das analysierte Ergebnis. Da mehrere Deklarationen mit "," (Komma) abgegeben werden können, wird das Analyseergebnis in "token.block" gespeichert, einer Liste, die mehrere Deklarationen enthalten kann. Führt die Analyse unmittelbar nach var durch. Da var immer mit dem Variablennamen kommen sollte, fordern Sie den Variablennamen mit ident () an. Wenn nach dem Variablennamen eine Zuweisung erfolgt, wird ein Token = = hinzugefügt. Wenn ein = Token eintrifft, wird es auf die gleiche Weise wie der Binäroperator analysiert und das Analyseergebnis in token.block gespeichert. Wenn es sich nicht um ein "=" Token handelt, wird das Ergebnis von "ident ()" unverändert in "token.block" gespeichert. Wenn danach "," Token vorhanden sind, analysieren Sie diese auf die gleiche Weise weiter und behalten Sie sie in "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

Eine Implementierung von Interpreter.java.

Einführung einer Klasse, die Scope darstellt. Ich portiere die Feldvariablen Funktionen und Variablen, die in der Klasse Interpreter waren. Es hat eine Feldvariable "parent", die die übergeordnete Hierarchie darstellt, um die hierarchische Struktur des Bereichs darzustellen.

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<>();
        }
    }

Dies ist der Initialisierungsteil. Die eingeführte Scope-Klasse wird als Feldvariable verwendet. Bereiten Sie Bereiche für "global" und "lokal" vor. localScope weist bei jeder Ausführung innerhalb einer Funktion eine neue Instanz der Scope-Klasse zu.

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;
    }

Dies ist eine Änderung der Methode body (), die Ausdrücke nacheinander ausführt.

Es wurde ein Methodenaufruf hinzugefügt, um die var-Deklaration zu // <-Add zu verarbeiten.

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;
    }

Die var () -Methode, die var-Deklarationen verarbeitet. Da mehrere Deklarationen in "token.block" gespeichert sind, werden sie der Reihe nach verarbeitet. Wenn das Element von "token.block" ein Token des Variablennamens ist, definieren Sie die Variable einfach im lokalen Bereich. Wenn das Element ein = Token ist, verwenden Sie das Variablennamens-Token item.left, um die Variable im lokalen Bereich zu definieren. Führen Sie dann das Token = = mit dem Ausdruck (Ausdruck) aus.

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;
    }

Dies ist eine Änderung der Methode ident () aufgrund der Einführung von Scope. Suchen Sie nach Funktionen und Variablen, die in der while-Anweisung definiert sind. Verwenden Sie im lokalen Bereich die Feldvariable "parent", um zum höheren Bereich zurückzukehren.

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);
    }

Dies ist eine Änderung der func () Methode aufgrund der Einführung von Scope. // Update Das Folgende sind die Änderungen. Von dem, was nur eine Zuordnung von "Dolmetscher" zu "Kontext" war Geändert, um "Interpreter" zu klonen und zuzuweisen. Suche nach Funktionen und Variablen, die in der while-Anweisung definiert sind. Verwenden Sie im lokalen Bereich die Feldvariable "parent", um zum höheren Bereich zurückzukehren.

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;
    }

Dies ist eine Änderung der DynamicFunc -Klasse aufgrund der Einführung von Scope. Erstellen Sie vor einem Funktionsaufruf einen neuen Bereich mit dem aktuellen lokalen Bereich als übergeordnetem Bereich. Nach dem Aufruf wird der lokale Bereich auf den ursprünglichen Bereich zurückgesetzt. Definieren Sie das formale Argument im neuen Bereich und führen Sie den Funktionsverarbeitungsblock aus.

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;
        }
    }

Das folgende Programm verwendet die obige Implementierung

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)

Wird ausgeführt und die Werte der Variablen "a", "b" und "c" für jeden Bereich werden an die Standardausgabe ausgegeben.

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
    }

Das ist alles für die Implementierung. Vielen Dank.

abschließend

Die vollständige Quelle finden Sie hier.

Calc https://github.com/quwahara/Calc/tree/article-13-scope-r3/Calc/src/main/java

Es gibt einen Folgeartikel.

** Entspricht einem Funktionsausdruck ** http://qiita.com/quwahara/items/ae33ed944afc34cc5fac

Recommended Posts

Entspricht dem Geltungsbereich
17 Entspricht einem Array
Entspricht 15 Zeichenfolgen
8 Entspricht mehreren Argumenten
10 Entspricht der if-Anweisung
14 Entspricht einem Funktionsausdruck
19 Entspricht der Objekterstellung
16 Entspricht dem Methodenaufruf
9 Entspricht dem Rückgabewert
18 Entspricht der JSON-ähnlichen Objektdefinition
Übergeben Sie Variablen an Scope.
Umfang
20 Entspricht statischen Methodenaufrufen
to_ ○
[Schienen] Verwendung von Scope
11 Entspricht Vergleichs- und logischen Operatoren
Verein (1 zu 1)! !!
So testen Sie den privaten Bereich mit JUnit
Über den Umfang
form_with scope
Verwendung der Scope- und Pass-Verarbeitung (Servist)