Println to interpreter im vorherigen Artikel hinzugefügt. Lassen Sie es uns ein wenig erweitern, um Funktionsdefinitionen und Aufrufe zu ermöglichen.
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.
Wir werden überlegen, wie es in der Reihenfolge der Phrasenanalyse (Lexer), Syntaxanalyse (Parser) und Interpreter (Interpreter) implementiert werden kann.
Da die implementierte Phrasenanalyse nicht die Funktion hat, gewellte Klammern "{" und "}" zu analysieren, werden wir sie implementieren.
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.
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.
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
Kontext
, um den Interpreter zu steuernparam
, das ein formales Argument darstelltes 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.
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