[JAVA] 16 Correspond à l'invocation de méthode

introduction

Je voudrais prendre en charge un tableau de structures de données de base. Cet article est Correspondant aux chaînes de caractères. Lorsqu'elle correspond à une chaîne de caractères, je souhaite pouvoir gérer une sous-chaîne de la chaîne de caractères. Ici, je voudrais pouvoir appeler une méthode pour qu'une sous-chaîne puisse être extraite de la chaîne.

Ce que vous voulez faire avec la prise en charge des appels de méthode

Confirmez ce que vous voulez faire avec la prise en charge des appels de méthode. Par exemple, il existe le programme suivant. Après avoir assigné une chaîne à la variable hw, nous cherchons à pouvoir appeler la méthode d'instance substring () ʻ de la variable hw. L'appel de méthode println ()à la fin du programme vise à afficher une sous-chaîne de la variablehw, Hello`.

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

Ce que vous ne faites pas, c'est de ne pas prendre en charge les appels de méthode statiques.

Comment mettre en œuvre

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

Comment implémenter l'analyse de phrases (Lexer)

Puisqu'il n'y a pas de fonction à analyser ., ajoutez-la.

Comment implémenter l'analyseur

Le jeton . sera ajouté en ajoutant la fonction de Lexer. Ajoutez la possibilité de l'analyser. Le jeton . peut être analysé comme un opérateur binaire, changez-le en conséquence.

Comment implémenter Interpreter

Nous implémenterons les appels de méthode d'instance afin qu'ils puissent être traités de la même manière que les appels de fonction qui ont déjà été implémentés. Dans un appel de fonction, le "à gauche du" jeton est le déclencheur de l'appel de fonction. Il n'y avait qu'un seul jeton pour le nom de la fonction ou de la variable. Le (à gauche du jeton est le jeton dans l'appel de méthode est Le jeton «.» Et ses jetons gauche et droit sont également impliqués dans l'appel. Par conséquent, le jeton «.» Et les jetons à gauche et à droite de celui-ci peuvent être traités comme une seule information. Présentez une classe qui les rassemble. Nous allons permettre de gérer les classes groupées de la même manière que les appels de fonction.

Essayez de mettre en œuvre en Java

Passez à la mise en œuvre. À propos de l'analyse de phrases (Lexer), de l'analyse syntaxique (Parser), de l'interpréteur (Interpreter) Jetons un coup d'œil aux modifications et aux ajouts dans l'ordre.

Lexer.java

Une implémentation de Lexer.java.

Ajoutez la fonction d'analyse de phrase de ..

ʻAjoutez la méthode isDotStart () `. Détecte «.».

Lexer.java


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

Ajoutez la méthode dot (). Analyser . en un jeton. Le «genre» qui représente «.» Devrait être «point».

Lexer.java


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

Modifiez la méthode nextToken (). Ajoutez l'appel à la méthode ajoutée à // Add. Vous pouvez maintenant décomposer «.» En jetons.

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

C'est tout pour changer Lexer.java.

Parser.java

Une implémentation de Parser.java.

Ajoutez une définition de la façon dont cela fonctionne pour la signification du jeton.

Ajout du degré de classement de . à // Update 1. . Est meilleur que les opérateurs arithmétiques comme + ʻet ` Parce qu'il relie fortement les jetons gauche et droit, Il est de 80, ce qui est plus grand que le degré de «+» et «».

Ajout de «point» à «// Update 2» La syntaxe de . équivaut à l'opérateur binaire, Comme l'analyse syntaxique peut être effectuée de la même manière que les opérateurs binaires, A traiter pour l'analyse syntaxique des opérateurs binaires Ajout de «point» à «types binaires».

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

Le processus d'analyse syntaxique lui-même n'apporte aucune modification ni aucun ajout. En effet, il est soumis à une analyse syntaxique des opérateurs binaires existants en plus de la définition. C'est tout pour changer Parser.java.

Interpreter.java

Une implémentation d'Interpreter.java.

Modification de la méthode ʻExpression () . C'est un processus qui se ramifie en fonction de la signification (sorte) du jeton qui représente l'expression. Ajout d'une branche sous // Add pour que le jeton dot représente .`.

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

Ajout de la méthode dot (). Appelé sur la branche ajoutée ci-dessus. La méthode dot () est une classe qui associe les côtés gauche et droit de . Renvoie une instance de «Dotted». Le côté gauche de . assigne la valeur de retour de la méthode value (), qui garantit qu'il s'agit d'une valeur. Ceci permet de garantir que le côté gauche de «.» Est toujours garanti comme instance de l'appel de méthode.

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

Ceci est une modification de la méthode func (). La méthode func () garantit que l'argument value est appelable comme une fonction. Le traitement lorsque le «pointillé» ajouté ci-dessus est passé par l'argument «valeur» a été ajouté sous «// Ajouter». Si l'argument est «Dotted», il renvoie une instance de la classe «MethodFunc». La classe MethodFunc hérite de la classe abstraite appelableFunc et Les informations requises pour l'appel de méthode sont stockées dans la variable de champ.

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

Ajout de la classe MethodFunc. Il est retourné par la méthode func (). Faites un appel de méthode d'instance avec la méthode ʻinvoke` de cette classe.

Pour effectuer un appel de méthode d'instance, vous devez obtenir les informations qui représentent la méthode. Les informations de méthode peuvent être obtenues à partir des informations qui représentent la classe. La variable de champ class_ est l'information pour cette classe.

Vous pouvez obtenir les informations de méthode à partir des informations de classe, Parmi les multiples méthodes définies dans la classe Vous devez sélectionner une information pour la méthode que vous souhaitez appeler.

La méthode ʻInvoke est utilisée pour sélectionner les informations de la méthode que vous voulez appeler. Utilisez le nom de la méthode et les informations de type de l'argument de la méthode ʻinvoke pour affiner.

Si les informations de méthode correspondantes ne peuvent pas être trouvées ou réduites à une, une erreur se produit.

La méthode de réduction est très simple et est différente de l'implémentation dans le compilateur Java. J'ai fait un commentaire avec une petite explication détaillée.

Interpreter.java


    public static class MethodFunc extends Func {

        //Représente le type de cible d'appel de méthode
        public Class<?> class_;
        //Représente l'instance pour laquelle la méthode est appelée
        public Object target;

        @Override
        public Object invoke(List<Object> args) throws Exception {
            
            //Faire une liste des types d'arguments à partir des arguments
            List<Class<?>> aClasses = argClasses(args);
            
            //Une liste d'informations de méthode que le type à appeler la méthode a
            //Ne listez que ceux qui portent le même nom que ce MethodFunc
            List<Method> mByName = methodByName(class_.getMethods(), name);
            
            //Une liste d'informations sur les méthodes classées par nom,
            //La liste des types d'arguments est limitée à ceux avec des signatures assignables.
            List<Method> mByAssignable = methodByAssignable(mByName, aClasses);
            
            //En raison de la réduction, une erreur se produit s'il n'y a pas d'informations de méthode correspondantes
            if (mByAssignable.size() == 0) {
                throw new Exception("MethodFunc.invoke error");
            }
            
            Method method;
            if (mByAssignable.size() == 1) {
                
                //En raison de la réduction, s'il n'existe qu'une seule information de méthode applicable
                //Ce sont les informations de méthode à appeler
                method = mByAssignable.get(0);
            
            } else {
                
                //En raison de la réduction, s'il y a deux ou plusieurs informations de méthode applicables, affinez davantage.
                //Une liste d'informations sur les méthodes réduites par des signatures assignables,
                //Réduisez la liste des types d'arguments à ceux dont les signatures correspondent exactement.
                List<Method> mByAbsolute = methodByAbsolute(mByAssignable, aClasses);
                
                //En raison de la réduction, une erreur se produira si les informations de méthode correspondantes ne le deviennent pas.
                if (mByAbsolute.size() != 1) {
                    throw new Exception("MethodFunc.invoke error");
                }
                
                //En raison de la réduction, s'il n'existe qu'une seule information de méthode applicable
                //Ce sont les informations de méthode à appeler
                method = mByAbsolute.get(0);
                
            }
            
            //Effectuer un appel de méthode en utilisant une seule information de méthode
            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;
    }

Le programme ci-dessous utilisant l'implémentation ci-dessus

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

Et L'appel de méthode println () à la fin du programme imprime Hello.

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
    }

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

Il y a un article de suite.

** Correspond au tableau ** http://qiita.com/quwahara/items/b4f821a797a146c8d873

Recommended Posts

16 Correspond à l'invocation de méthode
20 Correspond aux appels de méthode statiques
Méthode de recherche
17 Correspond à un tableau
Correspond à la portée
8 Correspond à plusieurs arguments
10 Correspond à l'instruction if
14 Correspond à une expression de fonction
5 Correspond aux parenthèses prioritaires
19 Correspond à la création d'objet
9 Correspond à la valeur de retour
12 Correspond à l'instruction while
18 Correspond à la définition d'objet de type JSON
Array.map facile à utiliser (&: méthode)
Comment utiliser la méthode link_to
Rubrique 43: Préférez les références de méthode aux lambdas
Comment utiliser la méthode include?
11 Correspond aux opérateurs de comparaison et logiques
[Ruby] Méthode pour compter des caractères spécifiques
[Java] Comment utiliser la méthode de jointure
Introduction aux algorithmes avec la méthode java-Shakutori
Resilience4j TimeLimiter expire les appels de méthode
Introduction aux modèles de conception (méthode d'usine)
Essayez d'extraire la méthode publique de java
Comment utiliser la méthode Ruby inject
à_ ○
[Java] Résumez comment comparer avec la méthode equals
Comment utiliser la méthode de soumission (Java Silver)
[Ruby] Des bases à la méthode inject
[Rails] Comment utiliser la méthode de la carte
[Java] Comment utiliser la méthode toString ()
Comment avoir des paramètres dans la méthode link_to
Que faire lorsque la méthode non définie ʻuser_signed_in? '