[JAVA] 7 Ajoutez une définition de fonction simple et appelez

introduction

Ajout de println to interpreter dans l'article précédent. Étendons-le un peu pour permettre les définitions de fonctions et les appels.

Ce que vous voulez faire et ce que vous ne faites pas avec la définition et l'appel de fonction

Lors de la mise en œuvre, assurez-vous que vous faites ce que vous voulez faire et ce que vous ne faites pas. Par exemple, si vous avez un programme comme celui ci-dessous, la fonction ʻaddV () est définie et nous visons à afficher 3` sur la sortie standard.

v = 0
function addV(num) {
  v = v + num
}
addV(3)
println(v)

Et je décide de ne pas le faire par souci de simplicité. Un seul argument de fonction est pris en charge. Il ne prend en charge aucun argument ou plus d'un argument. Il ne correspond pas à la valeur de retour de la fonction. Ne correspond pas à la portée de la variable. Le nom de variable de l'argument formel est également défini globalement.

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 dans l'analyse de phrase (Lexer)

Puisque l'analyse de phrase implémentée n'a pas la fonction d'analyser les parenthèses ondulées, { et }, nous la mettrons en œuvre.

Comment implémenter un analyseur

Tout d'abord, considérons une implémentation qui analyse l'instruction de définition de fonction. Observez la séquence de jetons dans la définition de fonction function addV (num) {} pour réfléchir à la façon de l'implémenter. En l'observant, vous pouvez voir que le mot-clé «fonction» vient toujours au début de la séquence de jetons. Considérant s'il existe un autre modèle d'arrangement similaire, où un arrangement fixe de jetons vient en premier, Demandez-vous s'il peut être implémenté sous une forme proche de celle-là. Dans cet esprit, les opérateurs monomorphes "-1" et "+ 1" sont proches. C'est parce que si le «-» ou «+» du jeton qui vient au début est décidé, il sera décidé comme un opérateur monomorphe. Il semble que l'analyse syntaxique de la définition de la fonction puisse être implémentée de manière similaire.

Ensuite, considérons l'implémentation de l'analyse syntaxique de l'instruction de l'appel de fonction. L'appel a la même structure syntaxique que le println (v) précédemment implémenté. Par conséquent, il est déjà mis en œuvre.

Comment implémenter Interpreter

Tout d'abord, comment implémenter la définition de fonction. Introduction d'une classe qui représente une fonction lors de l'implémentation de println (). L'implémentation de la fonction println () hérite de la classe Func. Il semble que l'implémentation de la définition de fonction dans l'interpréteur doit également hériter de la classe Func de la même manière. Et si vous rencontrez la partie de définition de fonction pendant que l'interpréteur est en cours d'exécution, Je vais instancier dynamiquement la classe héritée.

Ensuite, comment implémenter l'appel de fonction. Quant à l'appel, l'implémentation de println () peut être utilisée telle quelle, comme dans le cas de l'analyse syntaxique. Il est prêt à être mis en œuvre.

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. Tout d'abord, ajoutez la fonction pour analyser les parenthèses, { et }. La fin de la parenthèse ondulée } est traitée spécialement comme la fin du bloc, donc La signification est «eob» (fin de bloc).

Lexer.java


    
   private boolean isCurlyStart(char c) {
        return c == '{' || c == '}';
    }

    private Token curly() throws Exception {
        Token t = new Token();
        if (c() == '{') {
            t.kind = "curly";
        } else {
            t.kind = "eob";
        }
        t.value = Character.toString(next());
        return t;
    }

J'ajouterai la partie appel de l'analyse des tranches d'onde. L'implémentation de Lexer.java est terminée.

Lexer.java


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

Parser.java

Une implémentation de Parser.java. Tout d'abord, ajoutez une définition de la façon dont cela fonctionne pour la signification du jeton. Ajoutez réservé pour définir le mot réservé à <-Ajouter, et ajoutez-y fonction.

Parser.java


private Map<String, Integer> degrees;
    private List<String> factorKinds;
    private List<String> binaryKinds;
    private List<String> rightAssocs;
    private List<String> unaryOperators;
    private List<String> reserved;  // <-- Add

    public Parser() {
        degrees = new HashMap<>();
        degrees.put("(", 80);
        degrees.put("*", 60);
        degrees.put("/", 60);
        degrees.put("+", 50);
        degrees.put("-", 50);
        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[] { "+", "-" });
        reserved = Arrays.asList(new String[] { "function" });  // <-- Add
    }

C'est un changement de la pièce à analyser. Ajout d'un appel à la méthode func () pour analyser la définition de la fonction dans l'instruction if avec <-Add. Semblable à la gestion de l'unaire, le premier type de jeton détermine la méthode d'analyse syntaxique.

Parser.java



    private Token lead(Token token) throws Exception {
        if (token.kind.equals("ident") && token.value.equals("function")) {  // <-- Add
            return func(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.");
        }
    }

Ajout de la méthode func () pour analyser les définitions de fonction. Le résultat de l'analyse de la définition de fonction est résumé dans l'argument token. Pour résumer, nous ajoutons des variables de champ à la classe Token. Les variables de champ ajoutées sont ʻident, param, block. ʻIdent contient un jeton qui représente le nom de la fonction. param contient un jeton qui représente un argument formel. block contient le bloc de traitement de la fonction et son type est List <Token>.

Le traitement dans la méthode func () trace les jetons dans la définition de fonction dans l'ordre. Tout d'abord, déterminez la signification du jeton avec func. Ensuite, récupérez le nom de la fonction en appelant la méthode ʻident () . La méthode ʻident () vérifie que le token signifie ʻidentet renvoie ce token. Ensuite, il consomme le(au début de l'argument. Récupérez à nouveau l'argument formel avec l'appel de la méthode ʻident (). Il consomme alors le ) à la fin de l'argument et le { au début du bloc. Le contenu du bloc se verra attribuer la valeur de retour du block () existant tel quel. Enfin, si vous consommez le «}» à la fin du bloc, l'analyse syntaxique de la définition de la fonction est terminée. L'implémentation de Parser.java est également terminée.

Parser.java


  private Token func(Token token) throws Exception {
        token.kind = "func";
        token.ident = ident();
        consume("(");
        token.param = ident();
        consume(")");
        consume("{");
        token.block = block();
        consume("}");
        return token;
    }

    private Token ident() throws Exception {
        Token id = next();
        if (!id.kind.equals("ident")) {
            throw new Exception("Not an identical token.");
        }
        if (reserved.contains(id.value)) {
            throw new Exception("The token was reserved.");
        }
        return id;
    }

Interpreter.java

Une implémentation d'Interpreter.java.

Introduisez une classe DynamicFunc qui représente une fonction. Il hérite de la classe Func.

Variables de champ

il y a.

L'implémentation de la méthode ʻinvoke () résout l'argument comme une valeur et le garde dans la valeur de l'argument formel. Dans cet état, laissez l'interpréteur context execute block`.

Interpreter.java


    public static class DynamicFunc extends Func {

        public Interpreter context;
        public Token param;
        public List<Token> block;

        @Override
        public Object invoke(Object arg) throws Exception {
            Variable v = context.variable(context.ident(param));
            v.value = context.value(arg);
            context.body(block);
            return null;
        }
    }

C'est un changement de la partie qui appelle la méthode pour traiter chacun selon la signification du jeton. Ajout d'une définition de fonction où «<-Add» est.

Interpreter.java


    public Object expression(Token expr) throws Exception {
        if (expr.kind.equals("digit")) {
            return digit(expr);
        } else if (expr.kind.equals("ident")) {
            return ident(expr);
        } else if (expr.kind.equals("func")) { // <-- Add
            return func(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 {
            throw new Exception("Expression error");
        }
    }

Implémentation de la partie définition de fonction. Tout d'abord, vérifiez si le nom de la fonction et le nom de l'argument formel sont déjà utilisés. Générez une classe DynamicFunc qui représente une fonction et attribuez une valeur à une variable de champ. L'argument formel param est défini comme une variable à l'avance, Allouez l'instance qui représente la variable à la variable de champ. Enfin, ajoutez-le à la carte qui contient le nom de la fonction et sa valeur, et la définition de la fonction est terminée.

Interpreter.java


    public Object func(Token token) throws Exception {
        String name = token.ident.value;
        if (functions.containsKey(name)) {
            throw new Exception("Name was used");
        }
        if (variables.containsKey(name)) {
            throw new Exception("Name was used");
        }
        DynamicFunc func = new DynamicFunc();
        func.context = this;
        func.name = name;
        func.param = token.param;
        func.block = token.block;
        functions.put(name, func);
        return null;
    }

La chaîne de caractères dans le programme ci-dessous en utilisant l'implémentation ci-dessus

v = 0
function addV(num) {
  v = v + num
}
addV(3)
println(v)

Pour imprimer la valeur «3» affectée à la variable «v» sur la sortie standard.

Interpreter.java


public static void main(String[] args) throws Exception {
        String text = "";
        text += "v = 0";
        text += "function addV(num) {";
        text += "  v = v + num";
        text += "}";
        text += "addV(3)";
        text += "println(v)";
        List<Token> tokens = new Lexer().init(text).tokenize();
        List<Token> blk = new Parser().init(tokens).block();
        new Interpreter().init(blk).run();
        // --> 3
    }
}

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-7-function-r2/Calc/src/main/java

Il y a un article de suite.

** Correspond à plusieurs arguments ** http://qiita.com/quwahara/items/0bea3bad4eedd2d803cf

Recommended Posts

7 Ajoutez une définition de fonction simple et appelez
Comment ajouter la fonction ActionText
Créer un tableau et ajouter des colonnes