[JAVA] 16 Entspricht dem Methodenaufruf

Einführung

Ich möchte eine Reihe grundlegender Datenstrukturen unterstützen. Dieser Artikel ist Entspricht Zeichenketten. Wenn es einer Zeichenfolge entspricht, möchte ich in der Lage sein, eine Teilzeichenfolge der Zeichenfolge zu verarbeiten. Hier möchte ich eine Methode aufrufen können, damit ein Teilstring aus dem String extrahiert werden kann.

Was Sie mit der Unterstützung von Methodenaufrufen tun möchten

Bestätigen Sie, was Sie mit der Unterstützung von Methodenaufrufen tun möchten. Zum Beispiel gibt es das folgende Programm. Nachdem wir der Variablen "hw" einen String zugewiesen haben, möchten wir die Instanzmethode "substring ()" der Variablen "hw" aufrufen können. Der Methodenaufruf println () am Ende des Programms zielt darauf ab, einen Teilstring der Variablen hw, Hello auszugeben.

var hw = "Hello world!"
var h = hw.substring(0, 5)
println(h)

Was Sie nicht tun, ist, statische Methodenaufrufe nicht zu unterstützen.

Wie zu implementieren

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

So implementieren Sie eine Phrasenanalyse (Lexer)

Da es keine Funktion zum Analysieren von "." Gibt, fügen Sie sie hinzu.

So implementieren Sie Parser

Das . Token wird durch Hinzufügen der Funktion von Lexer hinzugefügt. Fügen Sie die Fähigkeit hinzu, es zu analysieren. Das . Token kann wie ein binärer Operator analysiert werden. Ändern Sie es daher entsprechend.

So implementieren Sie Interpreter

Wir werden Instanzmethodenaufrufe implementieren, damit sie genauso behandelt werden können wie bereits implementierte Funktionsaufrufe. In einem Funktionsaufruf ist das (das Token links vom Token das Token, das den Funktionsaufruf auslöst) Es gab nur ein Token für den Funktions- oder Variablennamen. Das (links vom Token ist das Token im Methodenaufruf ist Das . Token und seine linken und rechten Token sind ebenfalls an dem Aufruf beteiligt. Daher können das "." -Token und die Token links und rechts davon als eine Information behandelt werden. Stellen Sie eine Klasse vor, die sie zusammenstellt. Wir werden es ermöglichen, die gruppierten Klassen wie Funktionsaufrufe zu behandeln.

Versuchen Sie, in Java zu implementieren

Fahren Sie mit der Implementierung fort. Informationen zur Phrasenanalyse (Lexer), Syntaxanalyse (Parser), Interpreter (Interpreter) Schauen wir uns die Änderungen und Ergänzungen der Reihe nach an.

Lexer.java

Eine Implementierung von Lexer.java.

Fügen Sie die Phrasenanalysefunktion von . hinzu.

Fügen Sie die Methode isDotStart () hinzu. Erkennt ..

Lexer.java


    private boolean isDotStart(char c) {
        return c == '.';
    }

Fügen Sie die Methode dot () hinzu. Analysieren Sie . in einen Token. Die "Art", die "." Darstellt, sollte "Punkt" sein.

Lexer.java


    private Token dot() throws Exception {
        Token t = new Token();
        t.kind = "dot";
        t.value = Character.toString(next());
        return t;
    }

Ändern Sie die Methode nextToken (). Fügen Sie den Aufruf der hinzugefügten Methode zu // Add hinzu. Jetzt können Sie . in Token zerlegen.

Lexer.java


    public Token nextToken() throws Exception {
        skipSpace();
        if (isEOT()) {
            return null;
        } else if (isSignStart(c())) {
            return sign();
            // Add
        } else if (isDotStart(c())) {
            return dot();
        } else if (isDigitStart(c())) {
            return digit();
        } else if (isStringStart(c())) {
            return string();
        } else if (isIdentStart(c())) {
            return ident();
        } else if (isParenStart(c())) {
            return paren();
        } else if (isCurlyStart(c())) {
            return curly();
        } else if (isSymbolStart(c())) {
            return symbol();
        } else {
            throw new Exception("Not a character for tokens");
        }
    }

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

Parser.java

Eine Implementierung von Parser.java.

Fügen Sie eine Definition hinzu, wie es für die Bedeutung des Tokens funktioniert.

Der Ordnungsgrad von "." Zu "// Update 1" wurde hinzugefügt. . Ist besser als arithmetische Operatoren wie + und* Weil es die linken und rechten Token stark verbindet, Es ist 80, was größer ist als der Grad von "+" und "*".

"Punkt" zu "// Update 2" hinzugefügt. Die Syntax von . Entspricht dem binären Operator, Da die Syntaxanalyse auf die gleiche Weise wie binäre Operatoren durchgeführt werden kann, Zur Syntaxanalyse von Binäroperatoren zu verarbeiten "Punkt" zu "binären Arten" hinzugefügt.

Parser.java


    public Parser() {
        degrees = new HashMap<>();
        // Update 1
        degrees.put(".", 80);
        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", "string" });
        // Update 2
        binaryKinds = Arrays.asList(new String[] { "sign", "dot" });
        rightAssocs = Arrays.asList(new String[] { "=" });
        unaryOperators = Arrays.asList(new String[] { "+", "-", "!" });
        reserved = Arrays.asList(new String[] { "function", "return", "if", "else", "while", "break", "var" });
    }

Der Syntaxanalyseprozess selbst nimmt keine Änderungen oder Ergänzungen vor. Dies liegt daran, dass zusätzlich zur Definition eine syntaktische Analyse vorhandener Binäroperatoren durchgeführt wird. Das ist alles, um Parser.java zu ändern.

Interpreter.java

Eine Implementierung von Interpreter.java.

Dies ist eine Modifikation der Methode expression (). Es ist ein Prozess, der nach der Bedeutung (Art) des Tokens verzweigt, das den Ausdruck darstellt. Es wurde ein Zweig unter "// Hinzufügen" hinzugefügt, damit der Token "Punkt" "." Darstellt.

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("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);
            // Add
        } else if (expr.kind.equals("dot")) {
            return dot(expr);
        } else {
            throw new Exception("Expression error");
        }
    }

Dot () Methode hinzugefügt. Wird in der oben hinzugefügten Filiale aufgerufen. Die dot () Methode ist eine Klasse, die die linke und rechte Seite von . paart Gibt eine Instanz von Dotted zurück. Die linke Seite von . weist den Rückgabewert der value () -Methode zu, wodurch garantiert wird, dass es sich um einen Wert handelt. Dies soll sicherstellen, dass die linke Seite von . immer als Instanz des Methodenaufrufs garantiert ist.

Interpreter.java


    public Object dot(Token token) throws Exception {
        Dotted d = new Dotted();
        d.left = value(expression(token.left));
        d.right = token.right;
        return d;
    }

    public static class Dotted {
        public Object left;
        public Token right;
    }

Dies ist eine Modifikation der Methode func (). Die Methode func () garantiert, dass das Argument value wie eine Funktion aufgerufen werden kann. Die Verarbeitung, wenn das oben hinzugefügte "Gepunktete" vom Argument "Wert" übergeben wird, wurde unter "// Hinzufügen" hinzugefügt. Wenn das Argument "Gepunktet" lautet, wird eine Instanz der Klasse "MethodFunc" zurückgegeben. Die MethodFunc -Klasse erbt von der aufrufbaren abstrakten KlasseFunc und Die für den Methodenaufruf erforderlichen Informationen werden in der Feldvariablen gespeichert.

Interpreter.java


    public Func func(Object value) throws Exception {
        if (value instanceof Func) {
            return (Func) value;
            // Add
        } else if (value instanceof Dotted) {
            Dotted d = (Dotted) value;
            MethodFunc mf = new MethodFunc();
            mf.name = d.right.value;
            mf.class_ = d.left.getClass();
            mf.target = d.left;
            return mf;
        } else if (value instanceof Variable) {
            Variable v = (Variable) value;
            return func(v.value);
        } else {
            throw new Exception("Not a function");
        }
    }

Die MethodFunc Klasse wurde hinzugefügt. Es wird von der Methode func () zurückgegeben. Führen Sie einen Instanzmethodenaufruf mit der Methode "invoke" dieser Klasse durch.

Um einen Instanzmethodenaufruf durchzuführen, müssen Sie die Informationen abrufen, die die Methode darstellen. Methodeninformationen können aus den Informationen abgerufen werden, die die Klasse darstellen. Die Feldvariable class_ ist die Information für diese Klasse.

Sie können die Methodeninformationen aus den Klasseninformationen abrufen. Von den mehreren in der Klasse definierten Methoden Sie müssen eine Information für die Methode auswählen, die Sie aufrufen möchten.

Um in der Methode "invoke" eine Information der Methode auszuwählen, die Sie aufrufen möchten, Verwenden Sie den Methodennamen und die Typinformationen des Arguments der Methode "invoke", um sie einzugrenzen.

Wenn die entsprechenden Methodeninformationen 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 static class MethodFunc extends Func {

        //Stellt den Typ des Methodenaufrufziels dar
        public Class<?> class_;
        //Stellt die Instanz dar, für die die Methode aufgerufen wird
        public Object target;

        @Override
        public Object invoke(List<Object> args) throws Exception {
            
            //Erstellen Sie aus Argumenten eine Liste der Argumenttypen
            List<Class<?>> aClasses = argClasses(args);
            
            //Eine Liste der Methodeninformationen, über die der Typ verfügt, der die Methode aufruft
            //Listen Sie nur diejenigen auf, die denselben Namen wie diese MethodFunc haben
            List<Method> mByName = methodByName(class_.getMethods(), name);
            
            //Eine Liste von Methodeninformationen, eingegrenzt nach Namen,
            //Die Liste der Argumenttypen ist auf diejenigen mit zuweisbaren Signaturen beschränkt.
            List<Method> mByAssignable = methodByAssignable(mByName, aClasses);
            
            //Infolge der Eingrenzung tritt ein Fehler auf, wenn keine entsprechenden Methodeninformationen vorhanden sind
            if (mByAssignable.size() == 0) {
                throw new Exception("MethodFunc.invoke error");
            }
            
            Method method;
            if (mByAssignable.size() == 1) {
                
                //Infolge der Eingrenzung, wenn es nur eine anwendbare Methodeninformation gibt,
                //Das sind die aufzurufenden Methodeninformationen
                method = mByAssignable.get(0);
            
            } else {
                
                //Wenn aufgrund der Eingrenzung zwei oder mehr anwendbare Methodeninformationen vorhanden sind, werden diese weiter eingegrenzt.
                //Eine Liste von Methodeninformationen, die durch zuweisbare Signaturen eingegrenzt werden.
                //Grenzen Sie die Liste der Argumenttypen auf diejenigen mit genau übereinstimmenden Signaturen ein.
                List<Method> mByAbsolute = methodByAbsolute(mByAssignable, aClasses);
                
                //Infolge der Eingrenzung tritt ein Fehler auf, wenn die entsprechenden Methodeninformationen nicht eins werden.
                if (mByAbsolute.size() != 1) {
                    throw new Exception("MethodFunc.invoke error");
                }
                
                //Infolge der Eingrenzung, wenn es nur eine anwendbare Methodeninformation gibt,
                //Das sind die aufzurufenden Methodeninformationen
                method = mByAbsolute.get(0);
                
            }
            
            //Führen Sie einen Methodenaufruf mit nur einer Methodeninformation durch
            Object val = method.invoke(target, 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 List<Method> methodByName(Method[] methods, String name) {
            List<Method> ms = new ArrayList<Method>();
            for (Method m : methods) {
                if (m.getName().equals(name)) {
                    ms.add(m);
                }
            }
            return ms;
        }

        public List<Method> methodByAssignable(List<Method> methods, List<Class<?>> aClasses) {
            List<Method> candidates = new ArrayList<Method>();

            int aSize = aClasses.size();
            for (Method m : methods) {
                Class<?>[] pTypes = m.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(m);
                }
            }
            return candidates;
        }

        public List<Method> methodByAbsolute(List<Method> candidates, List<Class<?>> aClasses) {
            List<Method> screened = new ArrayList<Method>();
            int aSize = aClasses.size();
            for (int i = 0; i < aSize; ++i) {
                Class<?> ac = aClasses.get(i);
                if (ac == null) {
                    return screened;
                }
            }
            for (Method m : candidates) {
                Class<?>[] pTypes = m.getParameterTypes();
                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(m);
                }
            }
            return screened;
        }
    }

    public static Class<?> toBoxClass(Class<?> c) {
        Class<?> bc;
        if (c == boolean.class) {
            bc = Boolean.class;
        } else if (c == char.class) {
            bc = Character.class;
        } else if (c == byte.class) {
            bc = Byte.class;
        } else if (c == short.class) {
            bc = Short.class;
        } else if (c == int.class) {
            bc = Integer.class;
        } else if (c == long.class) {
            bc = Long.class;
        } else if (c == float.class) {
            bc = Float.class;
        } else if (c == double.class) {
            bc = Double.class;
        } else {
            bc = c;
        }
        return bc;
    }

Das folgende Programm verwendet die obige Implementierung

var hw = "Hello world!"
var h = hw.substring(0, 5)
println(h)

Und Der Methodenaufruf println () am Ende des Programms gibt Hello aus.

Interpreter.java


    public static void main(String[] args) throws Exception {
        String text = "";
        text += "var hw = \"Hello world!\"";
        text += "var h = hw.substring(0, 5)";
        text += "println(h)";
        List<Token> tokens = new Lexer().init(text).tokenize();
        List<Token> blk = new Parser().init(tokens).block();
        new Interpreter().init(blk).run();
        // --> Hello
    }

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-16-method-call/Calc/src/main/java

Es gibt einen Folgeartikel.

** Entspricht dem Array ** http://qiita.com/quwahara/items/b4f821a797a146c8d873

Recommended Posts

16 Entspricht dem Methodenaufruf
20 Entspricht statischen Methodenaufrufen
Suchmethode
17 Entspricht einem Array
Entspricht dem Geltungsbereich
8 Entspricht mehreren Argumenten
10 Entspricht der if-Anweisung
14 Entspricht einem Funktionsausdruck
5 Entspricht priorisierten Klammern
19 Entspricht der Objekterstellung
9 Entspricht dem Rückgabewert
12 Entspricht der while-Anweisung
18 Entspricht der JSON-ähnlichen Objektdefinition
Einfach zu bedienende array.map (&: Methode)
Verwendung der link_to-Methode
Punkt 43: Bevorzugen Sie Methodenverweise auf Lambdas
Verwendung der include? -Methode
11 Entspricht Vergleichs- und logischen Operatoren
[Ruby] Methode zum Zählen bestimmter Zeichen
[Java] Verwendung der Join-Methode
Einführung in Algorithmen mit der Java-Shakutori-Methode
Resilience4j TimeLimiter überschreitet Methodenaufrufe
Einführung in Entwurfsmuster (Factory-Methode)
Versuchen Sie, die öffentliche Java-Methode zu extrahieren
Verwendung der Ruby-Inject-Methode
to_ ○
[Java] Fassen Sie zusammen, wie Sie mit der Methode equals vergleichen können
Verwendung der Submit-Methode (Java Silver)
[Ruby] Von den Grundlagen bis zur Injektionsmethode
[Schienen] Verwendung der Kartenmethode
[Java] Verwendung der toString () -Methode
Wie man Parameter in der link_to-Methode hat
undefinierte Methode Was tun, wenn Sie "user_signed_in" erhalten?