[JAVA] 4 Fügen Sie dem Interpreter println hinzu

Einführung

In einem früheren Artikel habe ich einen Interpreter implementiert (http://qiita.com/quwahara/items/30e93dfd2690913d66c0). Der Interpreter kann jetzt vier Regeln ausführen, die Berechnungsergebnisse können jedoch nicht angezeigt werden. Dann bin ich einsam, also füge ich println hinzu, um das Berechnungsergebnis anzuzeigen.

Was Sie tun möchten, indem Sie println hinzufügen

Bestätigen Sie, was Sie bei der Implementierung tun möchten. Wenn Sie beispielsweise ein Programm wie das folgende haben, möchten wir "1" und einen Zeilenumbruch in der Standardausgabe ausgeben.

println(1)

Wenn eine Variable mit "2" als Argument von "println ()" angegeben wird, versuchen wir, den Inhalt "2" als Zeilenvorschub für die Standardausgabe auszugeben.

a = 2
println(a)

Wie zu implementieren

Bisher haben wir Phrasenanalyse (Lexer), Syntaxanalyse (Parser) und Interpreter (Interpreter) implementiert. Wir werden überlegen, wie die einzelnen nacheinander implementiert werden sollen.

Implementierung in die Phrasenanalyse (Lexer)

Die implementierte Phrasenanalyse kann keine Klammern "(" und ")" analysieren. Fügen Sie es zuerst hinzu. In den Spezifikationen für die Phrasenanalyse sollte die alphabetische Reihenfolge variabel sein. Println, eine der alphabetischen Sequenzen, ist jedoch ein Funktionsname, kein Variablenname. Die Tatsache, dass es sich um eine Folge von Alphabeten handelt, reicht nicht mehr aus, um festzustellen, ob es sich um einen Variablennamen oder einen Funktionsnamen handelt. Ändern Sie daher die Bedeutung der alphabetischen Reihenfolge in ident.

So implementieren Sie Parser

Um zu überlegen, wie es implementiert werden soll, schauen Sie sich zunächst die Reihenfolge der Token im Aufruf println (1) genau an. Es wird in die folgenden vier Token zerlegt.

Diese Sequenz ähnelt der Sequenz "a + 1". Ich werde es mir ansehen. Versuchen Sie, die Token für "a + 1" auf die gleiche Weise anzuordnen.

Vergleichen wir sie.

Die Syntaxanalyse von "a + 1" setzt "+" in die Mitte und weist links und rechts Token zu. Unter Berücksichtigung des Kontrasts hat "println (1)" auch eine Komposition, in der die links und rechts angeordneten Token mit "(" in der Mitte "versehen sind. Aus dem Beobachtungsergebnis geht hervor, dass zur Analyse der Syntax von "println (1)" eine Analyse möglich ist, indem "a + 1" zum Analysieren und Drucken verarbeitet und bestätigt wird, dass das ")" Token am Ende steht. .. Mit anderen Worten, es scheint, dass es implementiert werden kann, indem die Syntaxanalyse für Operator-Token geringfügig geändert wird. Es unterstützt auch den Wechsel von Variablenname zu Ident, der in der vorherigen Methode zur Implementierung der Phrasenanalyse erwähnt wurde.

So implementieren Sie Interpreter

Das Problem bei der Implementierung des Aufrufs "println (1)" bestand darin, dass der Interpreter die Variable implizit als String darstellte. Das war in Ordnung, da es keine Möglichkeit gab, dass es einen Funktionsnamen gab, wie er war, aber jetzt müssen wir zwischen dem Variablennamen und dem Funktionsnamen unterscheiden. Daher werden wir eine Klasse einführen, die den Variablennamen und den Funktionsnamen für jede Klasse darstellt. Wie wir bei der Implementierung der Syntaxanalyse festgestellt haben, ist ( das Schlüssel-Token für den Aufruf von println (1). Bitten Sie sie, die Methode aufzurufen, die das ( behandelt, wenn sie auf ein Token stoßen. Schließlich können wir auch hier den Wechsel von Variablenname zu Ident behandeln, der in der vorherigen Methode zur Implementierung der Phrasenanalyse erwähnt wurde.

Versuchen Sie, in Java zu implementieren

Fahren Sie mit der Implementierung fort.

Wiederum für die Phrasenanalyse (Lexer), Syntaxanalyse (Parser) und Interpreter (Interpreter) Schauen wir uns jeweils die Änderungen und Ergänzungen der Reihe nach an.

Lexer.java

Eine Implementierung von Lexer.java. Fügen Sie zunächst die Funktion zum Analysieren von Klammern "(" und ")" hinzu.

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

Als nächstes folgt die Antwort auf Änderungen von Variablennamen zu 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;
    }

Wenn Sie schließlich den Aufrufteil der Analyse von Klammern hinzufügen und den Aufrufteil der Änderung in ident ändern, Die Implementierung von Lexer.java ist abgeschlossen.

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

Eine Implementierung von Parser.java. Ändern Sie zunächst die Definition der Funktionsweise in Bezug auf die Bedeutung des Tokens. Fügen Sie "(" zu "Grad" hinzu, wodurch der Grad der Operatorreihenfolge bei "<-Add" definiert wird. ( Ist auch eine Korrespondenz, die wie ein Operator zu behandeln ist. println () möchte mit Priorität vor anderen Operatoren kombiniert werden, setzen Sie also den Grad auf 80. Entspricht der Änderung von ident anstelle von "<-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[] { "=" });
    }

Es ist eine Änderung des zu analysierenden Teils. Der zu analysierende Prozess wurde zur if-Anweisung mit <-Add hinzugefügt. Das Zuweisen von Token zu "operator.left" und "operator.right" entspricht fast dem Parsen des Operators der obigen if-Anweisung. Das Argument von expression () vonoperator.right = expression (0);ist im Gegensatz zu der obigen if-Anweisung 0. Dies liegt daran, dass ein Ausdruck unabhängig als Argument der Funktion verwendet wird und die Priorität nicht mit den vorhergehenden und nachfolgenden Operatoren verglichen werden muss. accept () bestätigt, dass das nächste Token eine schließende Klammer ist, und rückt das in der Analyse interessierende Token zum nächsten vor. Die Implementierung von Parser.java ist abgeschlossen.

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

Eine Implementierung von Interpreter.java. Um zwischen Variablennamen und Funktionsnamen zu unterscheiden, werden wir zunächst eine Klasse einführen, die jede darstellt. Die Funktion ist eine abstrakte Klasse für zukünftige Erweiterungen. Die Methode invoke () implementiert die tatsächlich von der Funktion ausgeführte Verarbeitung.

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

Es ist eine Änderung des Initialisierungsteils. Es wurde eine Map hinzugefügt, die den Funktionsnamen und seinen Wert enthält, der der Funktion entspricht. Fügen Sie dort den Text von println () hinzu.

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

Es ist eine Änderung des Teils, der die Methode aufruft, um jeden Teil gemäß der Bedeutung des Tokens zu verarbeiten. Funktionsaufruf hinzugefügt, bei dem <-Add ist. Bei der Änderung von ident befindet sich "<-Update".

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

"Var ()" wurde in "ident ()" geändert. Wenn der Bezeichner ein Funktionsname ist, wird er in eine Funktion aufgelöst. Andernfalls wird es in eine Variable aufgelöst.

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

Das folgende Med unterstützt die Variablenklasse, die zur Darstellung von Variablennamen eingeführt wurde.

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 () führt einen Funktionsaufruf durch. Das wollte ich in diesem Artikel tun. func () ist der Prozess von invoke () und bestätigt, dass das Ergebnis von expr.left der Funktionsname ist.

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

Die Zeichenfolge im folgenden Programm unter Verwendung der obigen Implementierung

a = 3 + 4 * 5
println(a)

Wird berechnet und der der Variablen zugewiesene Wert wird in die Standardausgabe gedruckt.

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

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

Es gibt einen Folgeartikel.

** Entspricht priorisierten Klammern ** http://qiita.com/quwahara/items/b76c6e438aeb32450391

Recommended Posts

4 Fügen Sie dem Interpreter println hinzu
So fügen Sie die Löschfunktion hinzu
Ich wollte der Methode @VisibleForTesting hinzufügen
Fügen Sie JDK zum TeamCity Build Agent-Container hinzu
Klassenpfad hinzufügen: zu dem in spring.datasource.schema angegebenen Pfad
Datei zur JAR-Datei hinzufügen
Ich möchte der Kommentarfunktion eine Löschfunktion hinzufügen
9 Entspricht dem Rückgabewert
Fügen Sie dem Header-Link mit Rails fontawesome ein Symbol hinzu
So fügen Sie die ActionText-Funktion hinzu
12 Entspricht der while-Anweisung
Hinzufügen von Elementen ohne Angabe der Länge des Arrays
[Schienen] Spalte zum Entwickeln hinzufügen
So fügen Sie dieselben Indizes in ein verschachteltes Array ein
Fügen Sie dem Swift-Button (und auch dem Kreis) einen Schatten hinzu.
Eingabe in die Java-Konsole
Methode, um die Anzahl der Jahre zu addieren und das Monatsende zu erhalten
Fügen Sie dem JAR-Dateinamen in Gradle einen Zeitstempel hinzu
Ich möchte die deaktivierte Option abhängig von der Bedingung zu f.radio_button hinzufügen
[JQuery] So zeigen Sie das ausgewählte Bild als sofortige Vorschau an + Fügen Sie ein Bildposting-Juwel hinzu
So fügen Sie Hyperledger Iroha Peer hinzu
Verwendung der link_to-Methode
Über die Sprache, die von nun an zu lernen ist
Verwendung der include? -Methode
Wechseln Sie dynamisch die Datenbank, zu der eine Verbindung hergestellt werden soll
Verwendung der Methode form_with
Übergeben Sie das Gebietsschema i18n an JavaScript
So finden Sie den durchschnittlichen Winkel
Verwendung der Wrapper-Klasse
[Schienen] So fügen Sie neue Seiten hinzu
Ich habe versucht, die Methode zu erklären
Willkommen im Sumpf der Java-Bibliotheken! !!
Fügen Sie dem Selenium Grid einen Bildwähler hinzu
Fügen Sie dem PDF-Dokument ein Wasserzeichen zu Java hinzu
[Schienen] Fügen Sie starke Parameter hinzu, um sie zu entwickeln
Fügen Sie Jackson die Verarbeitung zu Ihren eigenen Anmerkungen hinzu
[Java] WordArt zum Word-Dokument hinzufügen
Der Weg von JavaScript nach Java
Fügen Sie Android eine vorgefertigte JAR-Bibliothek hinzu und rufen Sie sie im Framework auf
Fügen Sie das Datum zu den von gcutil erfassten GC-Statistiken hinzu und geben Sie es aus.
[Rails 6] cocoon_ Fügen Sie dem hinzuzufügenden Formular ID- und Datenattribute hinzu
Grund, L zu der Zahl hinzuzufügen, die in den langen Java-Typ eingegeben werden soll
[Schienen] So erstellen Sie eine Tabelle, fügen eine Spalte hinzu und ändern den Spaltentyp