[JAVA] 17 Entspricht einem Array

Einführung

Ich möchte eine Reihe grundlegender Datenstrukturen unterstützen. Dieser Artikel ist eine Fortsetzung von "Entspricht Methodenaufrufen".

Was Sie mit Array-Unterstützung tun möchten

Bestätigen Sie, was Sie mit der Array-Unterstützung tun möchten. Zum Beispiel gibt es das folgende Programm. Erstellen Sie ein Array in der ersten Zeile und weisen Sie es der Variablen ar zu. Die zweite Zeile gibt die Array-Länge "3" aus. Die dritte Zeile zielt darauf ab, das dritte Element des Arrays "c" auszugeben.

var ar = ["a", "b", "c"]
println(ar.size())
println(ar[2])

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 "[" und "]" gibt, fügen Sie sie hinzu.

So implementieren Sie Parser

Die Implementierung für das Parsen von Syntax entspricht zwei Syntaxen: eine, die ein Array erstellt, und eine, die auf Elemente des Arrays zugreift.

Die Syntax zum Generieren eines Arrays beginnt mit einem [ Token. Implementieren Sie es in der Methode "lead ()", genau wie die anderen ersten Token-Konstrukte.

Die Syntax für den Zugriff auf die Elemente eines Arrays entspricht der Syntax für einen Funktionsaufruf mit einem Argument. Wenn Sie im Programmbeispiel "[]" von "ar [2]" durch "()" ersetzen, erhalten Sie "ar (2)". Sie können sehen, dass sie gleichwertig sind, da sie wie Funktionsaufrufe aussehen. Implementieren Sie es in der Methode bind () auf die gleiche Weise wie die Syntaxanalyse von Funktionsaufrufen.

So implementieren Sie Interpreter

Da die Entität des Arrays Elemente hinzufügen und die Länge durch die Instanzmethode ermitteln kann, Ich habe mich für ArrayList entschieden. Im vorherigen Artikel Unterstützt Methodenaufrufe, damit Sie davon Gebrauch machen können.

Die Verarbeitung für die Syntax implementiert die Generierung des Arrays und den Zugriff auf die Array-Elemente. Bei der Array-Generierung wird "ArrayList " generiert und Elemente hinzugefügt. Um auf ein Array-Element zuzugreifen, rufen Sie die Methode ArrayList <Object> :: get () auf.

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 "[" und "]" hinzu.

Fügen Sie die Methode "isBracketStart ()" hinzu. Erkennt "[" und "]".

Lexer.java


    private boolean isBracketStart(char c) {
        return c == '[' || c == ']';
    }

Fügen Sie die Methode bracket () hinzu. Analysieren Sie "[" und "]" in Token. "kind", was für "[" und "]" steht, sollte "Klammer" sein.

Lexer.java


    private Token bracket() throws Exception {
        Token t = new Token();
        t.kind = "bracket";
        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 "[" und "]" in Token zerlegen.

Lexer.java


    public Token nextToken() throws Exception {
        skipSpace();
        if (isEOT()) {
            return null;
        } else if (isSignStart(c())) {
            return sign();
        } 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();
            // Add
        } else if (isBracketStart(c())) {
            return bracket();
        } 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 [ to // Add 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 "*".

Parser.java


    public Parser() {
        degrees = new HashMap<>();
        degrees.put(".", 80);
        degrees.put("(", 80);
        degrees.put("[", 80);   // Add
        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" });
        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" });
    }

Implementiert das Parsen der Syntax zur Sequenzgenerierung. Von der lead () Methode, die die durch das erste Token bestimmte Syntax implementiert Aufruf der Methode newArray () hinzugefügt, die bei // Add eine Syntaxanalyse zur Array-Generierung durchführt.

Parser.java


    private Token lead(Token token) throws Exception {
        if (token.kind.equals("ident") && token.value.equals("function")) {
            return func(token);
        } else if (token.kind.equals("ident") && token.value.equals("return")) {
            token.kind = "ret";
            if (!token().kind.equals("eob")) {
                token.left = expression(0);
            }
            return token;
        } else if (token.kind.equals("ident") && token.value.equals("if")) {
            return if_(token);
        } else if (token.kind.equals("ident") && token.value.equals("while")) {
            return while_(token);
        } else if (token.kind.equals("ident") && token.value.equals("break")) {
            token.kind = "brk";
            return token;
        } else if (token.kind.equals("ident") && token.value.equals("var")) {
            return var(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;
            // Add
        } else if (token.kind.equals("bracket") && token.value.equals("[")) {
            return newArray(token);
        } else {
            throw new Exception("The token cannot place there.");
        }
    }

Die Methode newArray () wurde hinzugefügt, um eine Syntaxanalyse zur Array-Generierung durchzuführen. Sammeln Sie die durch "," getrennten Elemente in den "Parametern" des "[" Tokens ", bis das"] "Token erreicht ist. Der Token-Typ "kind" ist auf "newArray" gesetzt. Wenn das letzte Element des Arrays leer ist, wie z. B. [1,2,], wird es als Element ignoriert. Wenn in der Mitte der Sequenz ein leeres Element angegeben wird, z. B. "[1 ,, 3]" im Array, Der im Voraus vorbereitete "leere" Token wird zugewiesen.

Parser.java


    private Token newArray(Token token) throws Exception {
        token.kind = "newArray";
        token.params = new ArrayList<Token>();
        while(true) {
            if (token().value.equals("]")) {
                consume("]");
                break;
            }
            if (token().value.equals(",")) {
                token.params.add(blank);
                consume(",");
                continue;
            }
            token.params.add(expression(0));
            if (token().value.equals(",")) {
                consume(",");
                continue;
            } else {
                consume("]");
                break;
            }
        }
        return token;
    }

Ich habe das leere Token hinzugefügt, das das leere Element darstellt, das zuvor zur statischen Feldvariablen ausgegeben wurde.

Parser.java


    public static Token blank;
    static {
        blank = new Token();
        blank.kind = "blank";
        blank.value = "";
    }

Implementiert die Array-Zugriffssyntax. Von der bind () Methode, die die Funktionsaufrufsyntax usw. analysiert Array-Zugriffssyntaxanalyse zu // Add hinzugefügt. Für die "Linke" des "Operators", die dem "[" Token entspricht, wird das Token zugewiesen, das dem "ar" des Arrays selbst entspricht, beispielsweise "ar [2]". Weisen Sie für "Recht" des "Operators" den Index zu, auf den zugegriffen werden soll, z. B. das Token, das "2" von "ar [2]" entspricht.

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("(")) {
            operator.left = left;
            operator.params = new ArrayList<Token>();
            if (!token().value.equals(")")) {
                operator.params.add(expression(0));
                while (!token().value.equals(")")) {
                    consume(",");
                    operator.params.add(expression(0));
                }
            }
            consume(")");
            return operator;
            // Add
        } else if (operator.kind.equals("bracket") && operator.value.equals("[")) {
            operator.left = left;
            operator.right = expression(0);
            consume("]");
            return operator;
        } else {
            throw new Exception("The token cannot place there.");
        }
    }

Das ist alles für das Ändern von Parser.java.

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. Unter // Add, Rufen Sie die Methode blank () für leere Elemente auf. Rufen Sie die newArray () -Methode zur Array-Generierung auf. Der Methodenaufruf accessArray () für den Array-Zugriff wurde hinzugefügt.

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

Es wurde eine blank () Methode für leere Elemente hinzugefügt. Es wird einfach "null" zurückgegeben.

Interpreter.java


    public Object blank(Token token) {
        return null;
    }

NewArray () Methode zur Array-Generierung hinzugefügt. Erzeugt "ArrayList ", fügt ein Element hinzu und gibt es zurück.

Interpreter.java


    public Object newArray(Token expr) throws Exception {
        List<Object> a = new ArrayList<>();
        for (Token item : expr.params) {
            a.add(value(expression(item)));
        }
        return a;
    }

Die Methode value () der Methode wurde geändert, die garantiert, dass das Argument ein Wert ist. Das erste // Add erlaubt jetzt ArrayList <Object> als Wert. Das nächste // Add erlaubt jetzt null als Wert.

Interpreter.java


    public Object value(Object value) throws Exception {
        if (value instanceof Integer) {
            return value;
        } else if (value instanceof String) {
            return value;
            // Add
        } else if (value instanceof List<?>) {
            return value;
            // Add
        } else if (value == null) {
            return value;
        } else if (value instanceof Func) {
            return value;
        } else if (value instanceof Variable) {
            Variable v = (Variable) value;
            return value(v.value);
        }
        throw new Exception("right value error");
    }

AccessArray () Methode für den Array-Zugriff hinzugefügt. Dem Argument expr wird ein [ Token übergeben. Das [ Token left wird in das Array selbst aufgelöst. Das "[" Token "-Rechte" wird in den Index aufgelöst, auf den zugegriffen werden soll. Verwenden Sie sie und verwenden Sie die Methode ArrayList <Object> :: get (), um die Elemente abzurufen und zurückzugeben.

Interpreter.java


    public Object accessArray(Token expr) throws Exception {
        List<Object> ar = array(expression(expr.left));
        Integer index = integer(expression(expr.right));
        return ar.get(index);
    }

Die Methode array () wurde hinzugefügt, eine Methode, die garantiert, dass das Argument ein Array ist (List <Object>). Ich werde garantieren, dass das, worauf mit der vorherigen accessArray () -Methode zugegriffen wird, ein Array ist.

Interpreter.java


    @SuppressWarnings("unchecked")
    public List<Object> array(Object value) throws Exception {
        if (value instanceof List<?>) {
            return (List<Object>) value;
        } else if (value instanceof Variable) {
            Variable v = (Variable) value;
            return array(v.value);
        }
        throw new Exception("right value error");
    }

Das folgende Programm verwendet die obige Implementierung

var ar = ["a", "b", "c"]
println(ar.size())
println(ar[2])

Ausgabe der Länge des Arrays "3" und des dritten Elements des Arrays "c".

Interpreter.java


    public static void main(String[] args) throws Exception {
        String text = "";
        text += "var ar = [\"a\", \"b\", \"c\"]";
        text += "println(ar.size())";
        text += "println(ar[2])";
        List<Token> tokens = new Lexer().init(text).tokenize();
        List<Token> blk = new Parser().init(tokens).block();
        new Interpreter().init(blk).run();
        // --> 3
        // --> c
    }

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-17-array/Calc/src/main/java

Es gibt einen Folgeartikel.

** Entspricht der JSON-ähnlichen Objektdefinition ** http://qiita.com/quwahara/items/5e6cee17b7b03bd994ee

Recommended Posts