[JAVA] 19 Entspricht der Objekterstellung

Einführung

Ich möchte eine Java-Klasse laden und einen Konstruktor aufrufen, um die Objekterstellung durchzuführen. Dieser Artikel ist eine Fortsetzung von "Entspricht der JSON-ähnlichen Objektdefinition".

Was Sie mit der Unterstützung der Objekterstellung tun möchten

Bestätigen Sie, was Sie mit der Unterstützung für die Objekterstellung tun möchten. Zum Beispiel gibt es das folgende Programm. Führt die Funktion "loadClass" als globale Standardfunktion ein. Die Funktion loadClass ist eine Zeichenfolge als Argument. Wenn Sie einen vollständig qualifizierten Java-Klassennamen übergeben, wird diese Klasse geladen. (Die erste Zeile) Der geladene wird der Variablen zugewiesen. Wenn Sie dieser Variablen "new" hinzufügen und sie wie eine Funktion aufrufen, wird eine Instanz erstellt. (2. Zeile)

var dateClass = loadClass("java.util.Date")
var date = new dateClass()
println(date.toString())

Wie zu implementieren

Wir werden überlegen, wie es in der Reihenfolge Syntaxanalyse (Parser) und Interpreter (Interpreter) implementiert werden kann. Es gibt keine Änderung in der Phrasenanalyse (Lexer).

So implementieren Sie Parser

Die neu hinzugefügte Syntax lautet "neu". Überlegen wir uns also, wie Sie "neu" analysieren können. new steht in der Syntax an erster Stelle, also genau wie jedes andere Token in der Syntax. Implementieren Sie den Prozess des Parsens der Syntax von "new" in die Methode "lead ()". Da das Element der Syntax, das nach "new" steht, dieselbe Syntax wie der Funktionsaufruf hat, Durch das Parsen der Syntax von "new" wird überprüft, ob das nächste Element ein Funktionsaufruf ist.

So implementieren Sie Interpreter

Der Interpreter implementiert die neu eingeführte Funktion "loadClass" und die Ausführung der Syntax "new".

Implementieren Sie die Funktion "loadClass" auf die gleiche Weise wie die vorhandene globale Standardfunktion "println".

Die Implementierung der Ausführung "neuer" Syntax ist die Syntax, die nach "neu" folgt Behandle es wie einen Funktionsaufruf und lass es instanziieren.

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.

Implementiert die Analyse der neuen Syntax. Von der lead () Methode, die die durch das erste Token bestimmte Syntax implementiert Der Methode new_ () wurde ein Aufruf hinzugefügt, der new zu // Add analysiert.

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;
        } else if (token.kind.equals("ident") && token.value.equals("var")) {
            return var(token);
            // Add
        } else if (token.kind.equals("ident") && token.value.equals("new")) {
            return new_(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 if (token.kind.equals("bracket") && token.value.equals("[")) {
            return newArray(token);
        } else if (token.kind.equals("curly") && token.value.equals("{")) {
            return newMap(token);
        } else {
            throw new Exception("The token cannot place there.");
        }
    }

New_ () Methode hinzugefügt, um new Syntax Parsing durchzuführen. Das Argument "Token" wird als "neues" Token übergeben. Der "neue" Token-Typ "kind", der der Schlüssel für die Interpreter-Verarbeitung ist, wird auf "new" gesetzt. Das "linke" des "neuen" Tokens weist den Rückgabewert der "Ausdruck" -Methode zu. Stellen Sie sicher, dass der Rückgabewert eine Syntaxanalyse des Funktionsaufrufs ist.

Parser.java


    private Token new_(Token token) throws Exception {
        token.kind = "new";
        token.left = expression(0);
        if (!(token.left.value.equals("(") && token.left.kind.equals("paren"))) {
            throw new Exception("No constructor invocation.");
        }
        return token;
    }

Das ist alles, um Parser.java zu ändern.

Interpreter.java

Eine Implementierung von Interpreter.java.

Es wurde eine Klasse hinzugefügt, die den Inhalt der Funktion "loadClass" darstellt. Das Argument der Methode invoke ist das Argument des Funktionsaufrufs loadClass. Angenommen, das erste Argument ist eine Zeichenfolge vollständig qualifizierter Klassennamen Laden Sie die Klasse mit der Methode Class.forName.

Interpreter.java


    public static class LoadClass extends Func {
        public LoadClass() {
            name = "loadClass";
        }

        @Override
        public Object invoke(List<Object> args) throws Exception {
            return Class.forName((String) args.get(0));
        }
    }

Dies ist eine Modifikation der Methode init (). Eine Methode, die den Interpreter initialisiert. Instanziieren Sie die Klasse LoadClass, die zu // Add und hinzugefügt wurde Damit kann es als globale Standardfunktion aufgerufen werden Hinzufügen zu Funktionen im globalen Bereich.

Interpreter.java


    public Interpreter init(List<Token> body) {
        global = new Scope();
        local = global;
        // Add
        Func loadClass = new LoadClass();
        global.functions.put(loadClass.name, loadClass);
        Func f = new Println();
        global.functions.put(f.name, f);
        this.body = body;
        return this;
    }

Dies ist eine Modifikation der Methode expression (). Es ist ein Prozess, der nach der Bedeutung (Art) des Tokens verzweigt, das den Ausdruck darstellt. Der Methodenaufruf new_ () für die Objekterstellung wurde unter // Add hinzugefügt.

Interpreter.java


    public Object expression(Token expr) throws Exception {
        if (expr.kind.equals("digit")) {
            return digit(expr);
        } else if (expr.kind.equals("string")) {
            return string(expr);
        } else if (expr.kind.equals("ident")) {
            return ident(expr);
        } else if (expr.kind.equals("blank")) {
            return blank(expr);
            // Add
        } else if (expr.kind.equals("new")) {
            return new_(expr);
        } else if (expr.kind.equals("newMap")) {
            return newMap(expr);
        } else if (expr.kind.equals("newArray")) {
            return newArray(expr);
        } else if (expr.kind.equals("bracket")) {
            return accessArrayOrMap(expr);
        } else if (expr.kind.equals("func")) {
            return func(expr);
        } else if (expr.kind.equals("fexpr")) {
            return fexpr(expr);
        } else if (expr.kind.equals("paren")) {
            return invoke(expr);
        } else if (expr.kind.equals("sign") && expr.value.equals("=")) {
            return assign(expr);
        } else if (expr.kind.equals("unary")) {
            return unaryCalc(expr);
        } else if (expr.kind.equals("sign")) {
            return calc(expr);
        } else if (expr.kind.equals("dot")) {
            return dot(expr);
        } else {
            throw new Exception("Expression error");
        }
    }

New_ () Methode zur Objekterstellung hinzugefügt. Diese Methode ruft den Konstruktor auf und gibt das erstellte Objekt zurück.

Das "neue" Token wird an den "Ausdruck" des Methodenarguments "new_ ()" übergeben. Dem "linken" des "neuen" Tokens wird ein Token zugewiesen, das die Funktionsaufrufsyntax hat. Das "linke" des "neuen" Tokens, bei dem es sich um die Syntax des Funktionsaufrufs handelt, und das "linke" sind der Teil, der dem Funktionsnamen entspricht. Die "Parameter" der "linken" des "neuen" Tokens sind das Argument der Funktionsaufrufsyntax.

Der Teil, der dem Funktionsnamen (expr.left.left) entspricht, ist also eine Klasse im Konstruktoraufruf Zu Beginn des Prozesses wird dieser Teil in eine Klasse aufgelöst.

Um einen Konstruktoraufruf durchzuführen, müssen Sie Informationen abrufen, die den Konstruktor darstellen. Informationen zum Konstruktor können aus den Informationen abgerufen werden, die die Klasse darstellen. Die in eine Klasse aufgelöste Variable "c" ist die Information für diese Klasse.

Sie können die Konstruktorinformationen aus den Klasseninformationen abrufen. Von den mehreren in der Klasse definierten Konstruktoren Sie müssen eine Information über den Konstruktor auswählen, den Sie aufrufen möchten.

In der Methode new_, um eine Konstruktorinformation auszuwählen, die Sie aufrufen möchten Filtern Sie anhand der Typinformationen des Arguments des Konstruktoraufrufs.

Wenn die entsprechenden Konstruktorinformationen nicht gefunden oder auf eins eingegrenzt werden können, tritt ein Fehler auf.

Die Methode zur Eingrenzung ist sehr einfach und unterscheidet sich von der Implementierung im Java-Compiler. Ich machte einen Kommentar mit einer kleinen detaillierten Erklärung.

Interpreter.java


    public Object new_(Token expr) throws Exception {
        
        // "("Lösen Sie die linke Seite einer Klasse auf und rufen Sie eine Liste der Konstruktoren für diese Klasse ab
        Class<?> c = class_(expression(expr.left.left));
        Constructor<?>[] ctors = c.getConstructors();
        
        // "("Stellen Sie das Konstruktorargument auf der rechten Seite der Liste in einen Wert auf
        List<Object> args = new ArrayList<Object>();
        for (Token arg : expr.left.params) {
            args.add(value(expression(arg)));
        }

        //Erstellen Sie aus Argumenten eine Liste der Argumenttypen
        List<Class<?>> aClasses = argClasses(args);

        //Die Liste der Argumenttypen ist auf Konstruktoren beschränkt, deren Signaturen zugewiesen werden können.
        List<Constructor<?>> byAssignables = ctorsByAssignable(ctors, aClasses);

        //Infolge der Eingrenzung tritt ein Fehler auf, wenn kein entsprechender Konstruktor vorhanden ist
        if (byAssignables.size() == 0) {
            throw new Exception("No constructor error");
        }

        Constructor<?> ctor;
        if (byAssignables.size() == 1) {

            //Infolge der Eingrenzung, wenn es nur einen anwendbaren Konstruktor gibt,
            //Das ist der zu rufende Konstruktor
            ctor = byAssignables.get(0);

        } else {

            //Wenn aufgrund der Verengung zwei oder mehr anwendbare Konstruktoren vorhanden sind, wird diese weiter eingegrenzt.
            //Eine Liste von Konstruktoren, die durch zuweisbare Signaturen eingegrenzt sind.
            //Grenzen Sie nur Konstruktoren ein, deren Signatur genau mit der Liste der Argumenttypen übereinstimmt.
            List<Constructor<?>> byAbsolutes = ctorsByAbsolute(byAssignables, aClasses);

            //Infolge der Eingrenzung tritt ein Fehler auf, wenn kein anwendbarer Konstruktor vorhanden ist
            if (byAbsolutes.size() != 1) {
                throw new Exception("No constructor error");
            }

            //Infolge der Eingrenzung, wenn es nur einen anwendbaren Konstruktor gibt,
            //Das ist der zu rufende Konstruktor
            ctor = byAbsolutes.get(0);

        }

        //Führen Sie einen Konstruktoraufruf mit einem einzelnen Konstruktor durch
        Object val = ctor.newInstance(args.toArray());
        return val;
    }

    public List<Class<?>> argClasses(List<Object> args) {
        List<Class<?>> classes = new ArrayList<Class<?>>();
        int psize = args.size();
        for (int i = 0; i < psize; ++i) {
            Object a = args.get(i);
            if (a != null) {
                classes.add(a.getClass());
            } else {
                classes.add(null);
            }
        }
        return classes;
    }

    public static List<Constructor<?>> ctorsByAssignable(Constructor<?>[] ctors, List<Class<?>> aClasses) {
        List<Constructor<?>> candidates = new ArrayList<Constructor<?>>();

        int aSize = aClasses.size();
        for (Constructor<?> ctor : ctors) {
            Class<?>[] pTypes = ctor.getParameterTypes();

            if (pTypes.length != aSize) {
                continue;
            }

            Boolean allAssignable = true;
            for (int i = 0; i < aSize; ++i) {
                Class<?> c = pTypes[i];
                Class<?> cc = toBoxClass(c);
                Class<?> ac = aClasses.get(i);
                if (ac != null) {
                    Class<?> acc = toBoxClass(ac);
                    allAssignable &= cc.isAssignableFrom(acc);
                }
                if (!allAssignable) {
                    break;
                }
            }
            if (allAssignable) {
                candidates.add(ctor);
            }
        }
        return candidates;
    }

    public static List<Constructor<?>> ctorsByAbsolute(List<Constructor<?>> candidates, List<Class<?>> aClasses) {
        List<Constructor<?>> screened = new ArrayList<Constructor<?>>();
        int aSize = aClasses.size();
        for (int i = 0; i < aSize; ++i) {
            Class<?> ac = aClasses.get(i);
            if (ac == null) {
                return screened;
            }
        }
        for (Constructor<?> ctor : candidates) {
            Class<?>[] pTypes = ctor.getParameterTypes();
            if (aSize != pTypes.length) {
                continue;
            }
            Boolean allEquals = true;
            for (int i = 0; i < aSize; ++i) {
                Class<?> c = pTypes[i];
                Class<?> ac = aClasses.get(i);
                allEquals &= c == ac;
                if (!allEquals) {
                    break;
                }
            }
            if (allEquals) {
                screened.add(ctor);
            }
        }
        return screened;
    }

Es wurde eine class_ () -Methode hinzugefügt, die garantiert, dass der Wert des Arguments eine Klasse ist. Ueno Wird am Anfang der new () Methode verwendet.

Interpreter.java


    public Class<?> class_(Object value) throws Exception {
        if (value instanceof Class<?>) {
            return (Class<?>) value;
        } else if (value instanceof Variable) {
            Variable v = (Variable) value;
            return class_(v.value);
        }
        throw new Exception("right value error");
    }

Die Methode value () der Methode wurde geändert, die garantiert, dass das Argument ein Wert ist. Bisher bestand die Implementierungsrichtlinie darin, zu überprüfen, ob der Typ des Arguments "Wert" als Wert gültig ist. Jetzt, da Sie Objekte erstellen können, können Sie nicht mehr alle Werttypen untersuchen, um festzustellen, ob sie gültig sind. Daher habe ich der statischen Feldvariablen "void_" hinzugefügt, was darauf hinweist, dass sie als Wert ungeeignet ist. Wenn das Argument value`` void_ ist, wird es als Wert als unangemessen angesehen.

Interpreter.java



    Scope global;
    Scope local;
    List<Token> body;
    // Add
    public static Object void_ = new Object();

    public Object value(Object value) throws Exception {
        if (value == void_) {
            throw new Exception("right value error");
        } else if (value instanceof Variable) {
            Variable v = (Variable) value;
            return value(v.value);
        }
        return value;
    }

Entsprechend der Hinzufügung von "void_", einem Wert, der als Wert unangemessen angibt, Geändert, um "void_" anstelle von "null" für diejenigen zurückzugeben, die bisher keinen Wert zurückgegeben haben. Geändert, um "void_" zurückzugeben, wobei in jeder der folgenden Methoden "// Update" vorhanden ist.

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) {
                    // Update
                    return void_;
                } 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;
                // Update
                return void_;
            } else if (exprs.kind.equals("var")) {
                var(exprs);
            } else {
                expression(exprs);
            }
        }
        // Update
        return void_;
    }

    public Object ret(Token token) throws Exception {
        if (token.left == null) {
            // Update
            return void_;
        }
        return expression(token.left);
    }

    public Object if_(Token token, boolean[] ret, boolean[] brk) throws Exception {
        List<Token> block;
        if (isTrue(token.left)) {
            block = token.block;
        } else {
            block = token.blockOfElse;
        }
        if (block != null) {
            return body(block, ret, brk);
        } else {
            // Update
            return void_;
        }
    }

    public Object while_(Token token, boolean[] ret) throws Exception {
        boolean[] brk = new boolean[1];
        Object val;
        while (isTrue(token.left)) {
            val = body(token.block, ret, brk);
            if (ret != null && ret[0]) {
                return val;
            }
            if (brk[0]) {
                // Update
                return void_;
            }
        }
        // Update
        return void_;
    }

    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);
            }
        }
        // Update
        return void_;
    }

    public static class Println extends Func {
        public Println() {
            name = "println";
        }

        @Override
        public Object invoke(List<Object> args) throws Exception {
            Object arg = args.size() > 0 ? args.get(0) : null;
            System.out.println(arg);
            // Update
            return void_;
        }
    }

Das folgende Programm verwendet die obige Implementierung

var dateClass = loadClass("java.util.Date")
var date = new dateClass()
println(date.toString())

So generieren Sie ein "Date" -Objekt und geben das Ausführungsdatum und die Ausführungszeit aus.

Interpreter.java


    public static void main(String[] args) throws Exception {
        String text = "";
        text += "var dateClass = loadClass(\"java.util.Date\")";
        text += "var date = new dateClass()";
        text += "println(date.toString())";
        List<Token> tokens = new Lexer().init(text).tokenize();
        List<Token> blk = new Parser().init(tokens).block();
        new Interpreter().init(blk).run();
        // --> Sat Jun 17 18:29:13 JST 2017 (Datum und Uhrzeit der Ausführung)
    }

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-19-class-loading/Calc/src/main/java

Es gibt einen Folgeartikel.

** Entspricht statischen Methodenaufrufen ** http://qiita.com/quwahara/items/a8ef47f78b1479a117de

Recommended Posts

19 Entspricht der Objekterstellung
18 Entspricht der JSON-ähnlichen Objektdefinition
17 Entspricht einem Array
Entspricht dem Geltungsbereich
Entspricht 15 Zeichenfolgen
8 Entspricht mehreren Argumenten
Weg zur REPL (?) Schöpfung (3)
10 Entspricht der if-Anweisung
14 Entspricht einem Funktionsausdruck
5 Entspricht priorisierten Klammern
Weg zur REPL (?) Schöpfung (1)
16 Entspricht dem Methodenaufruf
Weg zur REPL (?) Schöpfung (2)
12 Entspricht der while-Anweisung
Konvertieren Sie ein serialisierbares Objekt in Byte []
20 Entspricht statischen Methodenaufrufen
11 Entspricht Vergleichs- und logischen Operatoren
Konvertieren Sie das Ruby-Objekt in das JSON-Format
to_ ○
Versuchen Sie, Ihre eigene Objektorientierung zu organisieren
Der Weg zum Erstellen eines Webdienstes (Teil 2)
Zuordnung von json zu einem bereits instanziierten Objekt
Einführung in Kotlin für iOS-Entwickler lin ー Kotlin-Erstellung