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.
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)
Bisher haben wir Phrasenanalyse (Lexer), Syntaxanalyse (Parser) und Interpreter (Interpreter) implementiert. Wir werden überlegen, wie die einzelnen nacheinander implementiert werden sollen.
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.
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.
println
und (
und 1
und )
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.
a
,+
und 1
Vergleichen wir sie.
println
→ a
→ Beide Bezeichner(
→ +
→ Beide Symbole1
→ 1
→ Beide Zahlen)
→ Keine UnterstützungDie 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.
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.
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.
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