[JAVA] 4 Ajoutez println à l'interpréteur

introduction

Dans un article précédent, j'ai implémenté un interpréteur (http://qiita.com/quwahara/items/30e93dfd2690913d66c0). L'interpréteur peut désormais exécuter quatre règles, mais les résultats du calcul ne peuvent pas être affichés. Ensuite, je suis seul, je vais donc ajouter println pour afficher le résultat du calcul.

Ce que vous voulez faire en ajoutant println

Confirmez ce que vous voulez faire lors de la mise en œuvre. Par exemple, si vous avez un programme comme celui ci-dessous, nous visons à afficher «1» et un saut de ligne dans la sortie standard.

println(1)

De plus, si une variable avec «2» assigné est spécifiée comme argument de «println ()», nous visons à afficher le contenu «2» comme un saut de ligne vers la sortie standard.

a = 2
println(a)

Comment mettre en œuvre

Jusqu'à présent, nous avons implémenté l'analyse de phrases (Lexer), l'analyse syntaxique (Parser) et l'interpréteur (Interpreter). Nous examinerons comment mettre en œuvre chacun à son tour.

Comment implémenter dans l'analyse de phrase (Lexer)

L'analyse de phrase implémentée n'a pas la capacité d'analyser les parenthèses, ( et ). Ajoutez-le d'abord. De plus, dans les spécifications d'analyse des phrases, l'ordre alphabétique était censé être variable. Cependant, println, qui est l'une des séquences alphabétiques, est un nom de fonction, pas un nom de variable. Le fait qu'il s'agisse à lui seul d'une séquence d'alphabets ne détermine pas s'il s'agit d'un nom de variable ou d'un nom de fonction. Par conséquent, changez la signification de la séquence alphabétique en ident.

Comment implémenter l'analyseur

Pour réfléchir à la façon de l'implémenter, regardez d'abord attentivement la séquence de jetons dans l'appel println (1). Il est décomposé en quatre jetons suivants.

Cette séquence est similaire à la séquence de «a + 1». Je vais regarder. ʻA + 1` essaie également d'organiser les jetons de la même manière.

Comparons-les.

L'analyse syntaxique de «a + 1» consistait à attribuer les jetons à gauche et à droite avec «+» au milieu. En observant le contraste, println (1) a également une composition dans laquelle les jetons qui viennent à gauche et à droite viennent avec ( au milieu. D'après le résultat de l'observation, afin d'analyser syntaxiquement println (1), il semble que l'analyse syntaxique puisse être effectuée en traitant ʻa + 1pour analyser et imprimer, et en confirmant que le jeton)` arrive à la fin. .. En d'autres termes, il semble qu'il puisse être implémenté en modifiant légèrement l'analyse syntaxique des jetons d'opérateur. Il prend également en charge le changement de nom de variable à ident, qui a été mentionné dans la méthode précédente de mise en œuvre de l'analyse de phrase.

Comment implémenter Interpreter

Le problème avec l'implémentation de l'appel println (1) était que l'interpréteur représentait implicitement la variable comme une chaîne. C'était bien car il n'y avait aucune possibilité qu'il y ait un nom de fonction tel quel, mais maintenant nous devons faire la distinction entre le nom de la variable et le nom de la fonction. Par conséquent, nous allons introduire une classe qui représente le nom de la variable et le nom de la fonction pour chacun. De plus, comme nous l'avons observé dans l'implémentation de l'analyse syntaxique, ( est le jeton clé pour l'appel println (1). Invitez-les à appeler la méthode qui gère le ( quand ils rencontrent un token. Enfin, ici aussi, nous pouvons gérer le changement de nom de variable à ident, qui a été mentionné dans la méthode précédente d'implémentation de l'analyse de phrase.

Essayez de mettre en œuvre en Java

Passez à la mise en œuvre.

Encore une fois, pour l'analyse de phrases (Lexer), l'analyse syntaxique (Parser) et l'interpréteur (Interpreter), Pour chacun, examinons les modifications et les ajouts dans l'ordre.

Lexer.java

Une implémentation de Lexer.java. Tout d'abord, ajoutez la fonction pour analyser les parenthèses, ( et ).

Lexer.java


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

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

Vient ensuite la réponse aux changements de noms de variables à idents.

Lexer.java


    
    private boolean isIdentStart(char c) throws Exception {
        return Character.isAlphabetic(c);
    }

    private Token ident() throws Exception {
        StringBuilder b = new StringBuilder();
        b.append(next());
        while (!isEOT() && (Character.isAlphabetic(c()) || Character.isDigit(c()))) {
            b.append(next());
        }
        Token t = new Token();
        t.kind = "ident";
        t.value = b.toString();
        return t;
    }

Enfin, si vous ajoutez la partie appel de l'analyse des parenthèses et modifiez la partie appel du changement en ident, 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 {
            throw new Exception("Not a character for tokens");
        }
    }

Parser.java

Une implémentation de Parser.java. Tout d'abord, changez la définition de son fonctionnement par rapport à la signification du jeton. Ajoutez ( à degrés, qui définit le degré d'ordre des opérateurs à <-Ajouter. ( Est aussi une correspondance à traiter comme un opérateur. println () veut se combiner avec la priorité sur d'autres opérateurs, alors réglez le degré sur 80. Correspond au changement de ident à la place de <-Update.

Parser.java


    public Parser() {
        degrees = new HashMap<>();
        degrees.put("(", 80);   // <-- Add
        degrees.put("*", 60);
        degrees.put("/", 60);
        degrees.put("+", 50);
        degrees.put("-", 50);
        degrees.put("=", 10);
        factorKinds = Arrays.asList(new String[] { "digit", "ident" }); // <-- Update
        binaryKinds = Arrays.asList(new String[] { "sign" });
        rightAssocs = Arrays.asList(new String[] { "=" });
    }

C'est un changement de pièce à analyser. Ajout du processus à analyser à l'instruction if avec <-Add. Assigner des jetons à ʻoperator.left et ʻoperator.right revient presque à analyser l'opérateur de l'instruction if ci-dessus. ʻOperator.right = expression (0); ʻexpression () argument est 0 contrairement à l'instruction if ci-dessus Cela est dû au fait qu'une expression est considérée indépendamment comme argument de la fonction et qu'il n'est pas nécessaire de comparer la priorité avec les opérateurs précédents et suivants. consume () confirme que le jeton suivant est une parenthèse fermante et avance le jeton d'intérêt dans l'analyse au suivant. L'implémentation de Parser.java est terminée.

Parser.java


    private Token bind(Token left, Token operator) throws Exception {
        if (binaryKinds.contains(operator.kind)) {
            operator.left = left;
            int leftDegree = degree(operator);
            if (rightAssocs.contains(operator.value)) {
                leftDegree -= 1;
            }
            operator.right = expression(leftDegree);
            return operator;
        } else if(operator.kind.equals("paren") && operator.value.equals("(")) {    // <-- Add
            operator.left = left;
            operator.right = expression(0);
            consume(")");
            return operator;
        } else {
            throw new Exception("The token cannot place there.");
        }
    }

    private Token consume(String expectedValue) throws Exception {
        if (!expectedValue.equals(token().value)) {
            throw new Exception("Not expected value");
        }
        return next();
    }

Interpreter.java

Une implémentation d'Interpreter.java. Tout d'abord, afin de faire la distinction entre les noms de variables et les noms de fonctions, nous allons introduire une classe qui représente chacun. La fonction est une classe abstraite pour une expansion future. La méthode ʻInvoke () `implémente le traitement réellement effectué par la fonction.

Interpreter.java


    public static class Variable {
        public String name;
        public Integer value;

        @Override
        public String toString() {
            return name + " " + value;
        }
    }

    public static abstract class Func {
        public String name;

        abstract public Object invoke(Object arg) throws Exception;
    }

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

        @Override
        public Object invoke(Object arg) throws Exception {
            System.out.println(arg);
            return null;
        }
    }

C'est un changement de la partie d'initialisation. Ajout d'une carte pour contenir le nom de la fonction et sa valeur pour correspondre à la fonction. Ajoutez le corps de println () ici.

Interpreter.java


public class Interpreter {

    public Map<String, Func> functions;
    public Map<String, Variable> variables;
    List<Token> body;

    public Interpreter init(List<Token> body) {
        functions = new HashMap<>();
        Func f = new Println();
        functions.put(f.name, f);
        variables = new HashMap<>();
        this.body = body;
        return this;
    }

C'est un changement de la partie qui appelle la méthode pour traiter chacun selon la signification du jeton. Ajout d'un appel de fonction où «<-Add» est. Le changement à ident est l'endroit où «<-Update» est.

Interpreter.java


    public Object expression(Token expr) throws Exception {
        if (expr.kind.equals("digit")) {
            return digit(expr);
        } else if (expr.kind.equals("ident")) { // <-- Update
            return ident(expr);
        } else if (expr.kind.equals("paren")) { // <-- Add
            return invoke(expr);
        } else if (expr.kind.equals("sign") && expr.value.equals("=")) {
            return assign(expr);
        } else if (expr.kind.equals("sign")) {
            return calc(expr);
        } else {
            throw new Exception("Expression error");
        }
    }

Changement de var () en ʻident () `. Si l'identifiant est un nom de fonction, il sera résolu en fonction. Sinon, il se résout en une variable.

Interpreter.java


    public Object ident(Token token) {
        String name = token.value;
        if (functions.containsKey(name)) {
            return functions.get(name);
        }
        if (variables.containsKey(name)) {
            return variables.get(name);
        } else {
            Variable v = new Variable();
            v.name = name;
            v.value = 0;
            variables.put(name, v);
            return v;
        }
    }

Le med suivant prend en charge la classe Variable introduite pour représenter les noms de variables.

Interpreter.java


    public Variable assign(Token expr) throws Exception {
        Variable variable = variable(expression(expr.left));
        Integer value = value(expression(expr.right));
        variable.value = value;
        return variable;
    }

    public Variable variable(Object value) throws Exception {
        if (value instanceof Variable) {
            return (Variable) value;
        } else {
            throw new Exception("left value error");
        }
    }

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

ʻInvoke () fait un appel de fonction. C'est ce que je voulais faire dans cet article. func ()est le processus de ʻinvoke ()et confirme que le résultat de ʻexpr.left` est le nom de la fonction.

Interpreter.java


    private Object invoke(Token expr) throws Exception {
        Func f = func(expression(expr.left));
        Integer value = value(expression(expr.right));
        return f.invoke(value);
    }

    public Func func(Object value) throws Exception {
        if (value instanceof Func) {
            return (Func) value;
        } else {
            throw new Exception("Not a function");
        }
    }

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

a = 3 + 4 * 5
println(a)

Est calculée et la valeur affectée à la variable est imprimée sur la sortie standard.

Interpreter.java


    public static void main(String[] args) throws Exception {
        String text = "a = 3 + 4 * 5";
        text += "println(a)";
        List<Token> tokens = new Lexer().init(text).tokenize();
        List<Token> blk = new Parser().init(tokens).block();
        new Interpreter().init(blk).run();
        // --> 23
    }
}

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-4-interpreter/Calc/src/main/java

Il y a un article de suite.

** Correspond aux parenthèses prioritaires ** http://qiita.com/quwahara/items/b76c6e438aeb32450391

Recommended Posts

4 Ajoutez println à l'interpréteur
Comment ajouter la fonction de suppression
Je voulais ajouter @VisibleForTesting à la méthode
Ajouter JDK au conteneur de l'agent TeamCity Build
Ajouter classpath: au chemin spécifié dans spring.datasource.schema
Ajouter un fichier au fichier jar
Je souhaite ajouter une fonction de suppression à la fonction de commentaire
9 Correspond à la valeur de retour
Ajouter une icône au lien d'en-tête en utilisant Rails fontawesome
Comment ajouter la fonction ActionText
12 Correspond à l'instruction while
Comment ajouter des éléments sans spécifier la longueur du tableau
[Rails] Ajouter une colonne à concevoir
Comment ajouter les mêmes index dans un tableau imbriqué
Ajouter une ombre au bouton Swift (et aussi au cercle)
Entrée dans la console Java
Méthode pour additionner le nombre d'années et obtenir la fin du mois
Ajouter un horodatage au nom de fichier JAR dans Gradle
Je veux ajouter l'option désactivée à f.radio_button en fonction de la condition
[JQuery] Comment afficher l'image sélectionnée sous forme d'aperçu immédiat + Ajouter une gemme de publication d'image
Comment ajouter Hyperledger Iroha Peer
Comment utiliser la méthode link_to
À propos de la langue à apprendre à partir de maintenant
Comment utiliser la méthode include?
Basculer dynamiquement la base de données à laquelle se connecter
Comment utiliser la méthode form_with
Passer les paramètres régionaux i18n à JavaScript
Comment trouver l'angle moyen
Comment utiliser la classe wrapper
[Rails] Comment ajouter de nouvelles pages
J'ai essayé d'expliquer la méthode
L'histoire que je voulais développer Zip
Bienvenue dans le marais des bibliothèques Java! !!
Ajouter un sélecteur d'image à Selenium Grid
Ajouter un filigrane à Java au document PDF
[Rails] Ajoutez des paramètres forts pour concevoir
Ajoutez un traitement à vos propres annotations dans Jackson
[Java] Ajouter WordArt au document Word
La route de JavaScript à Java
Ajoutez une bibliothèque jar pré-construite à Android et appelez-la dans le cadre
Ajoutez la date aux statistiques GC acquises par gcutil et affichez-la.
[Rails 6] cocoon_ Ajouter des attributs d'identifiant et de données au formulaire à ajouter
Raison pour ajouter L au nombre à mettre en type long Java
[Rails] Comment créer une table, ajouter une colonne et changer le type de colonne