Im vorherigen Artikel Unterstützt while-Anweisungen. Jetzt möchte ich der Variablen Scope entsprechen.
Bestätigen Sie, was Sie mit der Scope-Unterstützung tun möchten.
Zum Beispiel gibt es das folgende Programm.
Die Variablen "a", "b" und "c" werden innerhalb und außerhalb der Funktion "f ()" verwendet.
Da sich der Bereich innerhalb und außerhalb der Funktion ändert, sind "a" und "b", die mit "var" innerhalb der Funktion deklariert wurden, nur innerhalb der Funktion gültig.
"Println (a)" in den Funktionsausgängen "10" und außerhalb wird der vom globalen Bereich zugewiesene Wert "1" ausgegeben.
In ähnlicher Weise wird für "b" "20" innerhalb und "2" außerhalb ausgegeben.
c
wird nicht innerhalb der Funktion deklariert und ist sowohl innerhalb als auch außerhalb eine globale Bereichsvariable.
println (c)
druckt 30
innen und außen.
Mit der var
-Deklaration können Sie Variablen zuweisen und durch,
(Komma) trennen, sodass Sie mehrere Variablen gleichzeitig deklarieren können.
a = 1
b = 2
c = 3
function f() {
var a = 10, b
b = 20
c = 30
println(a)
println(b)
println(c)
}
f()
println(a)
println(b)
println(c)
Wir werden überlegen, wie es in der Reihenfolge Syntaxanalyse (Parser) und Interpreter (Interpreter) implementiert werden kann.
Da das Konzept von Scope selbst innerhalb oder außerhalb der Funktion ausgedrückt wird, Aus Sicht der Analyseimplementierung Wenn die Funktionsdefinition analysiert werden kann, kann das Konzept des Bereichs unterstützt werden.
Die Variablendeklaration "var" wird neu eingeführt. Das Schlüsselwort "var" steht also am Anfang der Token-Sequenz [Syntaxanalyse von Funktionsdefinitionsanweisungen], implementiert im vorherigen Artikel (http://qiita.com/quwahara/items/be71bac4b4359f5e6727#%E6%A7%8B%E6%96%87%E8%A7%A3%E6% Implementieren Sie auf die gleiche Weise wie 9E% 90parser% E3% 81% AE% E5% AE% 9F% E8% A3% 85% E3% 81% AE% E4% BB% 95% E6% 96% B9).
Stellen Sie eine Klasse vor, die das Konzept von Scope darstellt. Die Scope-Klasse bereitet Feldvariablen vor, die die Eltern-Kind-Beziehung ausdrücken können, damit die hierarchische Struktur von Scope ausgedrückt werden kann.
Erstellt einen globalen Standardbereich, wenn der Interpreter gestartet wird. Erstellen Sie beim Aufrufen einer Funktion einen Bereich in der Funktion und machen Sie den Bereich des Funktionsaufrufers zum übergeordneten Bereich des Bereichs in der Funktion. Nach dem Aufrufen der Funktion wird der Bereich in der Funktion verworfen und der übergeordnete Bereich wird an den aktuellen Bereich zurückgegeben.
Fahren Sie mit der Implementierung fort. Informationen zu Syntaxparser (Parser) und Interpreter (Interpreter) Schauen wir uns die Änderungen und Ergänzungen der Reihe nach an.
Parser.java
Eine Implementierung von Parser.java.
Fügen Sie eine Definition hinzu, wie es für die Bedeutung des Tokens funktioniert.
Da var
ein reserviertes Wort ist, habe ich es zu // Update
hinzugefügt.
Parser.java
public Parser() {
degrees = new HashMap<>();
degrees.put("(", 80);
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" });
binaryKinds = Arrays.asList(new String[] { "sign" });
rightAssocs = Arrays.asList(new String[] { "=" });
unaryOperators = Arrays.asList(new String[] { "+", "-", "!" });
// Update
reserved = Arrays.asList(new String[] { "function", "return", "if", "else", "while", "break", "var" });
}
Es ist eine Änderung des zu analysierenden Teils.
Der Funktion, die var
to // Add
analysiert, wurde ein Aufruf hinzugefügt.
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;
// Add
} 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;
} else {
throw new Exception("The token cannot place there.");
}
}
Es ist eine Änderung des zu analysierenden Teils.
Methode var ()
hinzugefügt, um var-Deklarationen zu analysieren.
Das Analyseergebnis der var-Deklaration ist im Token-Argument zusammengefasst.
Die Methode var () analysiert die folgenden Muster.
var
→ var a
steht ein Variablennamevar a = 0
,
(Komma) → var a = 0, b
Die Zuweisung zu "token.kind" zu Beginn der Verarbeitung bestimmt die Bedeutung des Tokens zu "var".
token.block
enthält das analysierte Ergebnis.
Da mehrere Deklarationen mit "," (Komma) abgegeben werden können, wird das Analyseergebnis in "token.block" gespeichert, einer Liste, die mehrere Deklarationen enthalten kann.
Führt die Analyse unmittelbar nach var
durch.
Da var
immer mit dem Variablennamen kommen sollte, fordern Sie den Variablennamen mit ident ()
an.
Wenn nach dem Variablennamen eine Zuweisung erfolgt, wird ein Token = = hinzugefügt.
Wenn ein =
Token eintrifft, wird es auf die gleiche Weise wie der Binäroperator analysiert und das Analyseergebnis in token.block
gespeichert.
Wenn es sich nicht um ein "=" Token handelt, wird das Ergebnis von "ident ()" unverändert in "token.block" gespeichert.
Wenn danach "," Token vorhanden sind, analysieren Sie diese auf die gleiche Weise weiter und behalten Sie sie in "token.block".
Parser.java
private Token var(Token token) throws Exception {
token.kind = "var";
token.block = new ArrayList<Token>();
Token item;
Token ident = ident();
if (token().value.equals("=")) {
Token operator = next();
item = bind(ident, operator);
} else {
item = ident;
}
token.block.add(item);
while (token().value.equals(",")) {
next();
ident = ident();
if (token().value.equals("=")) {
Token operator = next();
item = bind(ident, operator);
} else {
item = ident;
}
token.block.add(item);
}
return token;
}
Interpreter.java
Eine Implementierung von Interpreter.java.
Einführung einer Klasse, die Scope darstellt.
Ich portiere die Feldvariablen Funktionen
und Variablen
, die in der Klasse Interpreter
waren.
Es hat eine Feldvariable "parent", die die übergeordnete Hierarchie darstellt, um die hierarchische Struktur des Bereichs darzustellen.
Interpreter.java
public static class Scope {
public Scope parent;
public Map<String, Func> functions;
public Map<String, Variable> variables;
public Scope() {
functions = new HashMap<>();
variables = new HashMap<>();
}
}
Dies ist der Initialisierungsteil.
Die eingeführte Scope-Klasse wird als Feldvariable verwendet.
Bereiten Sie Bereiche für "global" und "lokal" vor.
local
Scope weist bei jeder Ausführung innerhalb einer Funktion eine neue Instanz der Scope-Klasse zu.
Interpreter.java
Scope global;
Scope local;
List<Token> body;
public Interpreter init(List<Token> body) {
global = new Scope();
local = global;
Func f = new Println();
global.functions.put(f.name, f);
this.body = body;
return this;
}
Dies ist eine Änderung der Methode body ()
, die Ausdrücke nacheinander ausführt.
Es wurde ein Methodenaufruf hinzugefügt, um die var-Deklaration zu // <-Add
zu verarbeiten.
Interpreter.java
public Object body(List<Token> body, boolean[] ret, boolean[] brk) throws Exception {
for (Token exprs : body) {
if (exprs.kind.equals("if")) {
Object val = if_(exprs, ret, brk);
if (ret != null && ret[0]) {
return val;
}
} else if (exprs.kind.equals("ret")) {
if (ret == null) {
throw new Exception("Can not return");
}
ret[0] = true;
if (exprs.left == null) {
return null;
} else {
return expression(exprs.left);
}
} else if (exprs.kind.equals("while")) {
Object val = while_(exprs, ret);
if (ret != null && ret[0]) {
return val;
}
} else if (exprs.kind.equals("brk")) {
if (brk == null) {
throw new Exception("Can not break");
}
brk[0] = true;
return null;
} else if (exprs.kind.equals("var")) { // <-- Add
var(exprs);
} else {
expression(exprs);
}
}
return null;
}
Die var () -Methode, die var-Deklarationen verarbeitet.
Da mehrere Deklarationen in "token.block" gespeichert sind, werden sie der Reihe nach verarbeitet.
Wenn das Element von "token.block" ein Token des Variablennamens ist, definieren Sie die Variable einfach im lokalen Bereich.
Wenn das Element ein =
Token ist, verwenden Sie das Variablennamens-Token item.left
, um die Variable im lokalen Bereich zu definieren.
Führen Sie dann das Token = = mit dem Ausdruck (Ausdruck) aus.
Interpreter.java
public Object var(Token token) throws Exception {
for (Token item : token.block) {
String name;
Token expr;
if (item.kind.equals("ident")) {
name = item.value;
expr = null;
} else if (item.kind.equals("sign") && item.value.equals("=")) {
name = item.left.value;
expr = item;
} else {
throw new Exception("var error");
}
if (!local.variables.containsKey(name)) {
newVariable(name);
}
if (expr != null) {
expression(expr);
}
}
return null;
}
public Variable newVariable(String name) {
Variable v = new Variable();
v.name = name;
v.value = 0;
local.variables.put(name, v);
return v;
}
Dies ist eine Änderung der Methode ident () aufgrund der Einführung von Scope. Suchen Sie nach Funktionen und Variablen, die in der while-Anweisung definiert sind. Verwenden Sie im lokalen Bereich die Feldvariable "parent", um zum höheren Bereich zurückzukehren.
Interpreter.java
public Object ident(Token token) {
String name = token.value;
Scope scope = local;
while (scope != null) {
if (scope.functions.containsKey(name)) {
return scope.functions.get(name);
}
if (scope.variables.containsKey(name)) {
return scope.variables.get(name);
}
scope = scope.parent;
}
return newVariable(name);
}
Dies ist eine Änderung der func ()
Methode aufgrund der Einführung von Scope.
// Update
Das Folgende sind die Änderungen.
Von dem, was nur eine Zuordnung von "Dolmetscher" zu "Kontext" war
Geändert, um "Interpreter" zu klonen und zuzuweisen. Suche nach Funktionen und Variablen, die in der while-Anweisung definiert sind.
Verwenden Sie im lokalen Bereich die Feldvariable "parent", um zum höheren Bereich zurückzukehren.
Interpreter.java
public Object func(Token token) throws Exception {
String name = token.ident.value;
if (local.functions.containsKey(name)) {
throw new Exception("Name was used");
}
if (local.variables.containsKey(name)) {
throw new Exception("Name was used");
}
List<String> paramCheckList = new ArrayList<String>();
for (Token p : token.params) {
String param = p.value;
if (paramCheckList.contains(param)) {
throw new Exception("Parameter name was used");
}
paramCheckList.add(param);
}
DynamicFunc func = new DynamicFunc();
// Update
func.context = new Interpreter();
func.context.global = global;
func.context.local = local;
func.context.body = body;
func.name = name;
func.params = token.params;
func.block = token.block;
local.functions.put(name, func);
return null;
}
Dies ist eine Änderung der DynamicFunc
-Klasse aufgrund der Einführung von Scope.
Erstellen Sie vor einem Funktionsaufruf einen neuen Bereich mit dem aktuellen lokalen Bereich als übergeordnetem Bereich.
Nach dem Aufruf wird der lokale Bereich auf den ursprünglichen Bereich zurückgesetzt.
Definieren Sie das formale Argument im neuen Bereich und führen Sie den Funktionsverarbeitungsblock aus.
Interpreter.java
public static class DynamicFunc extends Func {
public Interpreter context;
public List<Token> params;
public List<Token> block;
@Override
public Object invoke(List<Object> args) throws Exception {
Scope parent = context.local;
context.local = new Scope();
context.local.parent = parent;
for (int i = 0; i < params.size(); ++i) {
Token param = params.get(i);
Variable v = context.newVariable(param.value);
if (i < args.size()) {
v.value = context.value(args.get(i));
} else {
v.value = null;
}
}
Object val;
boolean[] ret = new boolean[1];
val = context.body(block, ret, null);
context.local = parent;
return val;
}
}
Das folgende Programm verwendet die obige Implementierung
a = 1
b = 2
c = 3
function f() {
var a = 10, b
b = 20
c = 30
println(a)
println(b)
println(c)
}
f()
println(a)
println(b)
println(c)
Wird ausgeführt und die Werte der Variablen "a", "b" und "c" für jeden Bereich werden an die Standardausgabe ausgegeben.
Interpreter.java
public static void main(String[] args) throws Exception {
String text = "";
text += "a = 1";
text += "b = 2";
text += "c = 3";
text += "function f() {";
text += " var a = 10, b";
text += " b = 20";
text += " c = 30";
text += " println(a)";
text += " println(b)";
text += " println(c)";
text += "}";
text += "f()";
text += "println(a)";
text += "println(b)";
text += "println(c)";
List<Token> tokens = new Lexer().init(text).tokenize();
List<Token> blk = new Parser().init(tokens).block();
new Interpreter().init(blk).run();
// --> 10
// --> 20
// --> 30
// --> 1
// --> 2
// --> 30
}
Das ist alles für die Implementierung. Vielen Dank.
Die vollständige Quelle finden Sie hier.
Calc https://github.com/quwahara/Calc/tree/article-13-scope-r3/Calc/src/main/java
Es gibt einen Folgeartikel.
** Entspricht einem Funktionsausdruck ** http://qiita.com/quwahara/items/ae33ed944afc34cc5fac
Recommended Posts