[JAVA] 7 Fügen Sie eine einfache Funktionsdefinition hinzu und rufen Sie auf

Einführung

Println to interpreter im vorherigen Artikel hinzugefügt. Lassen Sie es uns ein wenig erweitern, um Funktionsdefinitionen und Aufrufe zu ermöglichen.

Was Sie tun möchten und was nicht mit Funktionsdefinition und Aufruf

Stellen Sie bei der Implementierung sicher, dass Sie das tun, was Sie tun möchten und was Sie nicht tun. Wenn Sie beispielsweise ein Programm wie das folgende haben, wird die Funktion addV () definiert und Sie möchten 3 an die Standardausgabe ausgeben.

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

Und ich entscheide mich der Einfachheit halber dagegen. Es wird nur ein Funktionsargument unterstützt. Es werden keine Argumente oder mehr als ein Argument unterstützt. Sie entspricht nicht dem Rückgabewert der Funktion. Entspricht nicht dem Umfang der Variablen. Der Variablenname des formalen Arguments wird ebenfalls global definiert.

Wie zu implementieren

Wir werden überlegen, wie es in der Reihenfolge der Phrasenanalyse (Lexer), Syntaxanalyse (Parser) und Interpreter (Interpreter) implementiert werden kann.

Implementierung in die Phrasenanalyse (Lexer)

Da die implementierte Phrasenanalyse nicht die Funktion hat, gewellte Klammern "{" und "}" zu analysieren, werden wir sie implementieren.

So implementieren Sie Parser

Stellen Sie sich zunächst eine Implementierung vor, die die Funktionsdefinitionsanweisung analysiert. Beachten Sie die Reihenfolge der Token in der Funktionsdefinition function addV (num) {}, um zu überlegen, wie sie implementiert werden sollen. Wenn Sie dies beobachten, können Sie sehen, dass das Schlüsselwort "Funktion" immer am Anfang der Token-Sequenz steht. In Anbetracht dessen, ob es ein anderes Muster ähnlicher Anordnung gibt, bei dem eine feste Anordnung von Token an erster Stelle steht, Überlegen Sie, ob es in einer ähnlichen Form implementiert werden kann. In diesem Sinne sind die monomorphen Operatoren "-1" und "+ 1" nahe beieinander. Dies liegt daran, dass wenn das - oder + des zuerst kommenden Tokens bestimmt wird, es als monomorpher Operator bestimmt wird. Es scheint, dass die Syntaxanalyse der Funktionsdefinition auf ähnliche Weise implementiert werden kann.

Betrachten Sie als nächstes die Implementierung einer syntaktischen Analyse der Anweisung des Funktionsaufrufs. Der Aufruf hat dieselbe Syntaxstruktur wie der zuvor implementierte "println (v)". Daher ist es bereits implementiert.

So implementieren Sie Interpreter

Zunächst, wie die Funktionsdefinition implementiert wird. Einführung einer Klasse, die eine Funktion bei der Implementierung von "println ()" darstellt. Die Implementierung der Funktion "println ()" erbt von der Klasse "Func". Es scheint, dass die Implementierung der Funktionsdefinition im Interpreter auf die gleiche Weise auch die Func-Klasse erben sollte. Und wenn Sie auf den Teil der Funktionsdefinition stoßen, während der Interpreter ausgeführt wird, Ich werde die geerbte Klasse dynamisch instanziieren.

Als nächstes wird beschrieben, wie der Funktionsaufruf implementiert wird. Für den Aufruf kann die Implementierung von "println ()" unverändert verwendet werden, wie im Fall der Syntaxanalyse. Es ist bereit, implementiert zu werden.

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 zunächst die Funktion zum Analysieren der Klammern "{" und "}" hinzu. Das Ende der wellenförmigen Klammer } wird also speziell als das Ende des Blocks behandelt Die Bedeutung ist "eob" (Ende des Blocks).

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

Ich werde den Aufrufteil der Wellenklammeranalyse hinzufügen. 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 if (isCurlyStart(c())) {
            return curly();
        } else {
            throw new Exception("Not a character for tokens");
        }
    }

Parser.java

Eine Implementierung von Parser.java. Fügen Sie zunächst eine Definition hinzu, wie es für die Bedeutung des Tokens funktioniert. Fügen Sie "reserviert" hinzu, um das reservierte Wort bei "<-Add" zu definieren, und fügen Sie "Funktion" hinzu.

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
    }

Es ist eine Änderung des zu analysierenden Teils. Der Methode func () wurde ein Aufruf hinzugefügt, um die Funktionsdefinition in die if-Anweisung mit <-Add zu analysieren. Ähnlich wie bei der Behandlung von Unary bestimmt der erste Token-Typ die Syntax-Parsing-Methode.

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

Es wurde eine Methode func () hinzugefügt, die Funktionsdefinitionen analysiert. Das Analyseergebnis der Funktionsdefinition ist im Argument "Token" zusammengefasst. Zusammenfassend fügen wir der Token-Klasse Feldvariablen hinzu. Die hinzugefügten Feldvariablen sind "ident", "param" und "block". ident enthält ein Token, das den Funktionsnamen darstellt. param enthält ein Token, das ein formales Argument darstellt. block enthält den Verarbeitungsblock der Funktion und sein Typ ist List <Token>.

Die Verarbeitung in der Methode func () verfolgt die Token in der Funktionsdefinition der Reihe nach. Bestimmen Sie zunächst die Bedeutung des Tokens mit func. Rufen Sie dann den Funktionsnamen ab, indem Sie die Methode ident () aufrufen. Die Methode "ident ()" überprüft, ob das Token "ident" bedeutet, und gibt dieses Token zurück. Dann verbraucht es das ( am Anfang des Arguments. Holen Sie sich das formale Argument erneut mit dem Methodenaufruf ident (). Es verbraucht dann das ) am Ende des Arguments und das { am Anfang des Blocks. Dem Inhalt des Blocks wird der Rückgabewert des vorhandenen "block ()" so wie er ist zugewiesen. Wenn Sie schließlich das} am Ende des Blocks verwenden, ist die Syntaxanalyse der Funktionsdefinition abgeschlossen. Die Implementierung von Parser.java ist ebenfalls abgeschlossen.

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

Eine Implementierung von Interpreter.java.

Führen Sie eine Klasse "DynamicFunc" ein, die eine Funktion darstellt. Es erbt von der Func Klasse.

Feldvariablen

es gibt.

Die Implementierung der Methode "invoke ()" löst das Argument als Wert auf und hält es im Wert des formalen Arguments. Lassen Sie in diesem Zustand den Interpreter context execute block ausführen.

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

Es ist eine Änderung des Teils, der die Methode aufruft, um jeden Teil gemäß der Bedeutung des Tokens zu verarbeiten. Funktionsdefinition hinzugefügt, wobei <-Add ist.

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

Implementierung des Funktionsdefinitionsteils. Überprüfen Sie zunächst, ob der Funktionsname und der formale Argumentname bereits verwendet werden. Generieren Sie eine Klasse "DynamicFunc", die eine Funktion darstellt, und weisen Sie einer Feldvariablen einen Wert zu. Das formale Argument "param" wird im Voraus als Variable definiert. Ordnen Sie der Feldvariablen die Instanz zu, die die Variable darstellt. Fügen Sie es schließlich der Map hinzu, die den Funktionsnamen und seinen Wert enthält, und die Funktionsdefinition ist abgeschlossen.

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

Die Zeichenfolge im folgenden Programm unter Verwendung der obigen Implementierung

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

Drucken des der Variablen "v" zugewiesenen Werts "3" an die Standardausgabe.

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

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

Es gibt einen Folgeartikel.

** Entspricht mehreren Argumenten ** http://qiita.com/quwahara/items/0bea3bad4eedd2d803cf

Recommended Posts

7 Fügen Sie eine einfache Funktionsdefinition hinzu und rufen Sie auf
So fügen Sie die ActionText-Funktion hinzu
Erstellen Sie eine Tabelle und fügen Sie Spalten hinzu