6.9. Blocks von Statements und Schleifen6.9.1. Blocks von StatementsWir arbeiten noch immer an unserem Projekt
Zahlenratespiel.
Machen wir kurz ein Update und schauen wir uns die
Version 3 nochmals an:
Code:
/*************************************************
* DAS ZAHLENRATESPIEL Version 3
*************************************************/
int zufallsZahl; int eingabeZahl
print("Das Zahlenratespiel",endl)
rand(zufallsZahl,100) // 100 = Maximum
// Spielschleife
schleife:
print("Geben Sie eine Zahl zwischen 0 und 100 ein! (",zufallsZahl,")",endl)
input(eingabeZahl)
if (zufallsZahl<eingabeZahl) print("Meine Zahl ist kleiner.",endl,endl)
else if (zufallsZahl>eingabeZahl) print("Meine Zahl ist groesser.",endl,endl)
if eingabeZahl<>zufallsZahl goto schleife
// Siegesmeldung
print("Sie haben gewonnen!",endl)
Ich denke, es wird Zeit, dass wir das Label und das Goto durch ordentliche strukturierte Konstrukte eliminieren.
Die Schleife, die wir hier vorliegen haben, ist
fußgesteuert, d.h. es wird
am ENDE des Durchlaufs entschieden, ob weitergemacht werden soll.
Die Bedingung in unserem Programm ist: Mache weiter, solange eingabeZahl ungleich zufallsZahl ist.
Das ist eine Idealanwendung der C-Schleife do..while.
Doch jetzt tauchen erste Probleme auf!Wir werden die Schleife folgendermaßen aufbauen:
- Schleifenkopf: Kopflabel setzen
- Schleifenkörper: Statement() aufrufen
- Schleifenfuß:
- Condition prüfen
- iJT zu Kompflabel, wenn die Condition stimmt
Das
Problem liegt bei Statement().
Es ist
nur exakt 1 Statement erlaubt (ich habe weiter oben mehrfach darauf hingewiesen).
Das ist zu wenig!
Wir haben viel mehr Statements innerhalb des Schleifenkörpers.
Lösen wir das Problem und führen wir damit endlich
Blöcke von Statements ein, was ohnehin schon lange überfällig ist.
Wir befehlen dem Compiler mittels einer
geöffneten geschwungenen Klammer "{", dass er in einem Statement mehrere Statements erwarten soll, und zwar so lange,
bis er
ein "}" findet.
Wir erklären der Prozedur Statement(), dass sie einen Block von Statements vor sich hat, wenn ein "{" als Lexem erscheint.
Die Prozedure
Statement() ist in ihrer voller Pracht weiter oben abgebildet, ich gebe hier einen kleinen Ausschnitt wieder:
Code:
; // je nach Statement Aktionen setzen //
Select Scanner::Lexem
; Statements
Case "{" : Block() ; <=== HIER IST DIE ERWEITERUNG !!!
Case "int" : Declare_Statement('i')
Case "if" : If_Statement()
;...
Case "end" : End_Statement()
Case "rand" : Rand_Statement()
Default : If Scanner::Look=':' ; -- Sprungmarke (Label) --
EmitX("[UL_"+UCase(Scanner::Lexem)+"]")
Scanner::GetOther() ; ':' holen
Scanner::GetToken() ; nächstes Token-Lexem
Else
Assignment_Statement() ; -- Assignment --
EndIf
EndSelect
Ein "{" beginnt also einen Block (of Statements).
Wir benötigen dazu klarerweise auch die Prozedur
Block(), die die Verwaltung der "{" und "}" übernimmt:
Code:
Procedure Block()
; --> in Token-Lexem ist jetzt '{'
; ueberlesen des '{'
Scanner::GetToken()
; solange kein '}'
While ( Scanner::Token<>'}' )
Statement()
If Scanner::Token=0:Error("Block()",#Block_unbeendet): EndIf; 0-Byte vor '}'
Wend
; ueberlesen des '}'
Scanner::GetToken()
; --> in Token-Lexem ist jetzt das Token-Lexem exakt nach '}'
EndProcedure
Besprechen wir kurz, wie Block arbeitet:
- Statement() erkennt ein "{" und ruft Block() auf
- In Token/Lexem ist immer noch "{" -> GetToken() überspringt das.
WHILE solange bis Token="}" - Token ruft wieder Statement() auf und in Statement() könnte wieder ein Block sein.
- Sollte in Statement() irgendwann wieder "{" sein, dann beginnt wieder eine neue Instanz von Block().
Diese neue Instanz von Block() ruft wieder Statement() auf und in Statement() könnte wieder ein Block sein, und so weiter.
Das ist Rekursion! - Sollte Token einmal 0 (=Source-Code-Ende) sein, dann wurde der Block nie geschlossen, denn am Ende eines Blocks muss ein "}" sein.
Es wird eine passende Fehlermeldung (siehe Fehlercodes) ausgegeben.
ENDWHILE
- In Token/Lexem ist "}" -> GetToken() überspringt das.
Das erklärt auch, warum die Debug-Anzeige von Pure-Basic das schließende "}" nicht anzeigt, denn es wird hier aufgegessen. - Token/Lexem steht auf dem Token/Lexem nach "}".
- Block() endet und kehrt zurück zu Statement, das seinerseits zu Block() zurückkehren könnte und so weiter.
So, mehr braucht es auch wirklich nicht, das war es schon, unglaublich einfach, nicht?
Wir haben jetzt funktionierende Blocks und können innerhalb eines Blocks beliebig viele Statements (und darin wieder Blocks und darin wieder Statements mit darin wieder Blocks ...) verwenden!
Machen wir uns auf, um TTC ein reichhaltiges Sortiment an Schleifen zu schenken.
6.9.2. Die Schleifen Do-While, Do-Until, Do: Das fußgesteuerte UniversalgenieDie Do-Schleife ist
fußgesteuert, das heißt, dass die
Condition erst am Ende (=am Fuß) der Schleife, nach dem ersten Durchlauf, überprüft wird.
do ... whileDas
Zahlenratespiel Version 4 sieht mit einer Do-While-Schleife folgendermaßen aus:
Code:
/*************************************************
* DAS ZAHLENRATESPIEL Version 4 (mit Do-While)
*************************************************/
int zufallsZahl; int eingabeZahl
print("Das Zahlenratespiel",endl)
rand(zufallsZahl,100) // 100 = Maximum
// Spielschleife
do
{
print("Geben Sie eine Zahl zwischen 0 und 100 ein! (",zufallsZahl,")",endl)
input(eingabeZahl)
if (zufallsZahl<eingabeZahl) print("Meine Zahl ist kleiner.",endl,endl)
else if (zufallsZahl>eingabeZahl) print("Meine Zahl ist groesser.",endl,endl)
} while eingabeZahl<>zufallsZahl
// Siegesmeldung
print("Sie haben gewonnen!",endl)
Es wird zu do gesprungen, solange eingabeZahl<>zufallsZahl.
Nehmen wir zum Entwerfen der
Struktur der Do-While-Schleife eine einfachere Angabe als das Zahlenratespiel:
Code:
int x
do
{
x=x+1
print(x)
}
while x<10
Kopf-/True-Label: <------------------------.
|
.- SCHLEIFENKÖRPER-Block -----------------. |
| | |
| x=x+1 | |
| | |
| print(x) | |
| | |
'-----------------------------------------' |
|
.- BEDINGUNG (CONDITION) -----------------. | => Sprung zu
| | | Schleifen-Kopf
| Prüfen : X<10 --> True oder False | |
| | |
| Springen : wenn True |----' True?
| |
'-----------------------------------------'
STATEMENT danach
Der Unterschied zu früher ist, dass die Sprungbedingung true und nicht false ist wie bisher in der If-Anweisung.
Wir benötigen einen neuen ASM-Opcode und werden deshalb iJT (Integer Jumpf if True) erfinden.
Die Prozedur
Do_Statement() ist sehr geradlinig:
Code:
Procedure Do_Statement()
; "do" überspringen -> auf 1. Token-Lexem des nächsten Statements
Scanner::GetToken()
; Labels erzeugen
l1=LabelNr:LabelNr+1 ; für Schleifen-Kopf "do"
; Schleifen-Kopf
EmitX("[L_"+l1+"]")
EmitX("// DO")
; Schleifen-Körper
Statement()
; Schleifen-Fuß:
; "while" überspringen
Scanner::GetToken()
Emit(0,"// DO ... WHILE")
Condition()
Emit(ExpTyp,"JT","L_"+l1)
; Ende-Markierung mit Leerzeile
EmitX("// END DO")
EmitX()
EndProcedure
Wie oben schon erwähnt, ist der Unterschied ist, dass die Sprungbedingung true und nicht false ist.
Darüber hinaus, denke ich, erklärt sich die Prozedur von selbst.
Kompilieren wir jetzt die Angabe:
Code:
[L_1] <-------------------------------.
// DO |
// assignment |
// math expression |
ipushg 0 // x |
ipushc 1 // 1 |
iadd // + |
ipullg 0 // =x |
|
// print |
ipushg 0 // x |
iout |
|
// DO ... WHILE |
// CONDITION |
// math expression |
ipushg 0 // x |
ipushc 10 // 10 |
ilt // < |
iJT L_1 --------------' Wenn Condition true
// END DO
do ... untilWas wir jetzt entwerfen, das kennt C gar nicht.
Es ist eine Schleife, die der Repeat-Until-Schleife von Pure Basic entspricht.
Sie ist so einfach hinzuzufügen, dass ich es mir nicht verkneifen kann.
Das
Zahlenratespiel Version 4 sieht mit einer Do-Until-Schleife folgendermaßen aus:
Code:
/*************************************************
* DAS ZAHLENRATESPIEL Version 4 (mit Do-Until)
*************************************************/
int zufallsZahl; int eingabeZahl
print("Das Zahlenratespiel",endl)
rand(zufallsZahl,100) // 100 = Maximum
// Spielschleife
do
{
print("Geben Sie eine Zahl zwischen 0 und 100 ein! (",zufallsZahl,")",endl)
input(eingabeZahl)
if (zufallsZahl<eingabeZahl) print("Meine Zahl ist kleiner.",endl,endl)
else if (zufallsZahl>eingabeZahl) print("Meine Zahl ist groesser.",endl,endl)
} until eingabeZahl=zufallsZahl
// Siegesmeldung
print("Sie haben gewonnen!",endl)
Es wird zu do gesprungen, bis eingabeZahl=zufallsZahl.
Nehmen wir auch hier zum Entwerfen der
Struktur der Do-Until-Schleife eine einfachere Angabe als das Zahlenratespiel:
Code:
int x
do
{
x=x+1
print(x)
}
until x=10
Kopf-/False-Label: <-----------------------.
|
.- SCHLEIFENKÖRPER-Block -----------------. |
| | |
| x=x+1 | |
| | |
| print(x) | |
| | |
'-----------------------------------------' |
|
.- BEDINGUNG (CONDITION) -----------------. | => Sprung zu
| | | Schleifen-Kopf
| Prüfen : X=10 --> True oder False | |
| | |
| Springen : wenn False |----' False?
| |
'-----------------------------------------'
STATEMENT danach
Mit Until haben wir also wieder eine Prüfung auf false.
Die Prozedur
Do_Statement() ist leicht um diesen Fall erweitert:
Code:
Procedure Do_Statement()
; "do" überspringen -> auf 1. Token-Lexem des nächsten Statements
Scanner::GetToken()
; Labels erzeugen
l1=LabelNr:LabelNr+1 ; für Schleifen-Kopf "do"
; Schleifen-Kopf
EmitX("[L_"+l1+"]")
EmitX("// DO")
; Schleifen-Körper
Statement()
; Schleifen-Fuß:
; "until" vorhanden?
If Scanner::Lexem="until"
Scanner::GetToken()
Emit(0,"// DO ... UNTIL")
Condition()
Emit(ExpTyp,"JF","L_"+l1)
; "while" vorhanden?
ElseIf Scanner::Lexem="while"
Scanner::GetToken()
Emit(0,"// DO ... WHILE")
Condition()
Emit(ExpTyp,"JT","L_"+l1)
EndIf
; Ende-Markierung mit Leerzeile
EmitX("// END DO")
EmitX()
EndProcedure
Wir brauchen also nur prüfen, ob ein While oder ein Until dem Schleifen-Körper folgt.
Beachten wir, dass die Sprungbedingung bei Until false und bei While true ist.
Kompilieren wir jetzt die Angabe für die Do-Until-Schleife:
Code:
[L_1] <-------------------------------.
// DO |
// assignment |
// math expression |
ipushg 0 // x |
ipushc 1 // 1 |
iadd // + |
ipullg 0 // =x |
|
// print |
ipushg 0 // x |
iout |
|
// DO ... UNTIL |
// CONDITION |
// math expression |
ipushg 0 // x |
ipushc 10 // 10 |
ieq // = |
iJF L_1 --------------' Wenn Condition false
// END DO
do ... (forever)Entwerfen wir noch ein Äquivalent zur Repeat-Forever-Schleife von Pure Basic, wenn wir schon im Laufen sind.
Das kennt C in der Form auch nicht.
Obwohl TTC tiny ist, muss ich einfach noch schnell diese Schleife mit nur effektiv 2 (!!!)

Zeilen PB-Code hinzufügen (Rest ist Code-Verschönerung und Kommentierung).
Diese Schleife hat keine Abbruchbedingung, sie läuft also ewig.
Auch sie ist leicht in die bisherige Prozedur Do_Statement() einzubauen.
Es ist der einfache Fall, dass dem Schleifenkörper weder ein While noch ein Until folgt. Es gibt schlichtweg keine Condition.
Die Prozedur
Do_Statement() mit einer unendlichen Schleife:
Code:
Procedure Do_Statement()
; "do" überspringen -> auf 1. Token-Lexem des nächsten Statements
Scanner::GetToken()
; Labels erzeugen
l1=LabelNr:LabelNr+1 ; für Schleifen-Kopf "do"
; Schleifen-Kopf
EmitX("[L_"+l1+"]")
EmitX("// DO")
; Schleifen-Körper
Statement()
; Schleifen-Fuß:
; "until" vorhanden?
If Scanner::Lexem="until"
Scanner::GetToken()
Emit(0,"// DO ... UNTIL")
Condition()
Emit(ExpTyp,"JF","L_"+l1)
; "while" vorhanden?
ElseIf Scanner::Lexem="while"
Scanner::GetToken()
Emit(0,"// DO ... WHILE")
Condition()
Emit(ExpTyp,"JT","L_"+l1)
; unendliche Schleife
Else
Emit(0,"// DO ... FOREVER")
Emit(0,"j","L_"+l1)
EndIf
; Leerzeile
EmitX("// END DO")
EmitX()
EndProcedure
Testen wir schnell folgenden
Source-Code:
Code:
int x
do
{
x=x+1
print(x)
}
Das ergibt als
ASM-Code:
Code:
[L_1] <-------------------------------.
// DO |
// assignment |
// math expression |
ipushg 0 // x |
ipushc 1 // 1 |
iadd // + |
ipullg 0 // =x |
|
// print |
ipushg 0 // x |
iout |
|
// DO ... FOREVER | springt
j L_1 --------------' IMMER
// END DO
Es wird immer zu do gesprungen, ohne Chance, jemals zu enden.
6.9.3. Die While-Schleife: Der kopfgesteuerte KlassikerEine der berühmtesten Schleifen fehlt uns noch. Es ist die Schleife, die auch der in PB geschriebene TTC-Compiler am häufigsten einsetzt.
Es ist dies die kopfgesteuerte While-Schleife.
Bei einer
kopfgesteuerten Schleife wird die
Condition (Bedingung) als Erstes geprüft und erst dann entschieden, ob der Schleifenkörper durchlaufen werden soll.
Es kann also sehr gut sein, dass die Schleife nie durchlaufen wird, was bei den von uns zuvor eingebauten nicht passieren kann, sie werden zumindest 1-mal durchlaufen.
Eine While-Schleife sieht eigentlich genauso aus wie ein If-Konstrukt. Der einzige Unterschied ist, dass zum Kopf der Abfrage zurückgekehrt wird.
Aufbau einer While-Schleife wieder an einem einfachen Beispiel getestet:
Code:
int x
x=0
while x<10
{
print(x)
x=x+1
}
Kopf-Label: <-----------------------------.
|
.- BEDINGUNG (CONDITION) -----------------. |
| | |
| Prüfen : X<10 --> True oder False | |
| | | False?
| Springen : wenn False |----.
| | | | => Schleifen-Körper
'-----------------------------------------' | | überspringen zur
| | passenden
.- SCHLEIFENKÖRPER-Block -----------------. | | Sprungmarke
| | | |
| print(x) | | |
| | | |
| x=x+1 | | |
| | | |
| Springen: immer, zum Kopf |--' |
| | |
'-----------------------------------------' |
|
Fuß-/False-Label: <-------------------------'
STATEMENT danach
Wir implementieren diese Schleife jetzt genau so, wie sie grafisch dargestellt ist.
Es gäbe noch eine andere Möglichkeit - man könnte die Abfragen im ASM-Code umstellen, was für den TTC-Programmierer nicht zu merken wäre, damit würde sie deutlich an Effizienz gewinnen, aber um der Klarheit willen wählen wir jetzt einmal den geraden Weg (und merken uns die While-Schleife als Optimierungskandidaten).
Auch die Prozedur
While_Statement ist kurz und verständlich:
Code:
Procedure While_Statement()
; "while" überspringen, steht auf 1. Token-Lexem der Condition
Scanner::GetToken()
; Labels erzeugen
l1=LabelNr:LabelNr+1 ; für Schleifen-Kopf "while"
l2=LabelNr:LabelNr+1 ; für Schleifen-Ende "wend"
; Schleifen-Kopf, Condition, False -> Schleifen-Körper überspringen
EmitX("[L_"+l1+"]")
EmitX("// WHILE")
Condition()
Emit(ExpTyp,"JF","L_"+l2)
EmitX()
; Schleifen-Körper
Statement()
; Schleifen-Fuß -> springe zu Kopf
Emit(0,"j","L_"+l1)
; Schleifen-Ende
EmitX("// WEND")
EmitX("[L_"+l2+"]")
EndProcedure
Unser Beispiel aus der Grafik sieht kompiliert zu
ASM-Code so aus (nur die While-Schleife dargestellt):
Code:
[L_1]
// WHILE
// CONDITION
// math expression
ipushg 0 // x
ipushc 10 // 10
ilt // <
iJF L_0
// print
ipushg 0 // x
iout
// assignment
// math expression
ipushg 0 // x
ipushc 1 // 1
iadd // +
ipullg 0 // =x
j L_1
// WEND
[L_0]
6.9.4. Die For-Schleife, kein Befehl für TTC 0.5Um gleich vorweg die Enttäuschung zu nehmen, wir werden
in TTC 0.5 KEINE For-Schleife implementieren.
Nicht, dass sie schwer zu verstehen wäre, ich erkläre sie unten, aber es erfordert einen kleinen Eingriff in die Code-Ausgabe (Emit), den ich hier in V0.5 aus Gründen der Übersichtlichkeit nicht vornehmen möchte.
Die For-Schleife stellt uns vor eine Entscheidung, nämlich vor die, welche Syntax wir ihr geben werden.
Ich werde
eine vereinfachte C-Syntax wählen, wobei wir auch die Semikolons ";" zwischen den einzelnen Bedingungen in der For-Anweisung setzen sollten, aber vergessen wird nicht, wir bräuchten keine, denn sie sind White Characters.
Syntax der For-Schleife:
Code:
for ( Assignment [;] Expression [;] Assignment ) Statement
^ ^ ^
| | |
Initialisation --' Ende-Bedingung --' '-- Schrittweite
und zwar solange diese
Bedingung true ist
==> while
Eine
For-Schleife ist nichts anderes als eine übersichtlich bzw. bloß
anders gestaltete While-Schleife. Von der Funktionalität her benötigen wir sie in der Sprache eigentlich nicht.
Jede
For-Schleife kann in eine
While-Schleife übertragen werden:
Code:
for ( x=1 ; x<11 ; x=x+1 )
{
print(x)
}
Das entspricht folgender While-Schleife:
Code:
x=1
while (x<11)
{
print(x)
x=x+1
}
Jetzt sehen wir vielleicht auch, warum ich die For-Schleife jetzt noch nicht einbaue.
Wir müssten den ASM-Code der
Schrittweite zwischenspeichern z.B. in einer List oder Ähnlichem, denn sie befindet sich ja am Schleifen-Kopf.
Dann kommt der Schleifen-Körper und danach müssten wir erst
am Schleifen-Fuß den zwischengespeicherten Code ausgeben.
Dazu brauchen wir eine kleine Veränderung der Code-Ausgabe - nicht schwer und nicht viel -, aber ich möchte das Tutorial nicht schon wieder mit Einzelgenialitäten überfrachten.
6.9.5. Break und continueBreak ermöglicht, eine Schleife jederzeit sofort zu verlassen, während
continue sofort an den Schleifenkopf springt.
Die beiden Befehle simulieren also ein "goto Schleifenfuß" bzw. ein "goto Schleifenkopf", wenn man es anders sagen will.
Um diesen Befehl zu implementieren, benötigt man einen Stapel, also einen Stack, denn Schleifen könnten ja ineinander verschachtelt sein und dann soll
break ja immer nur die letzte innerste Schleife verlassen bzw.
continue am Kopf der letzten innersten Schleife weitermachen.
Die jeweiligen Labels müssen auf diesem Stack gespeichert werden, damit
break und
continue ein Ziel haben.
Crenshaw
zeigt in seinem Text, dass ein externer Stack eigentlich gar nicht nötig wäre, weil man die Labels auch als Procedure-Parameter weiterschicken kann, dann ensteht durch die Aufrufreihenfolge der Prozeduren wie von selbst ein Stack.
Wir gehen diesen Weg bewusst nicht, weil wir - es geht einfach so einfach

- auch einen Break-Level (z.B.
break 2) wie in Pure Basic ermöglichen wollen.
Wir fügen also unserem Parser eine
globale Linked List hinzu:
Code:
; --- break, continue verwalten ---
Structure ls
Continue_.i
Break_.i
EndStructure
Global NewList LabelStack.ls()
In dieser Linked List mit dem sinnvollen Namen LabelStack speichern wir jeweils das letzte Label-Ziel für continue und für break.
Wir müssen nun in allen Schleifen, also immer wenn es ein Label an einem Schleifenkopf sowie an einem Schleifenfuß gibt, unsere LabelStack-Verwaltung hinzufügen.
Die Veränderungen in der Prozedur
Do_Statement() sind erstaunlich gering:
Code:
Procedure Do_Statement() ; mit Break, Continue
; "do" überspringen -> auf 1. Token-Lexem des nächsten Statements
Scanner::GetToken()
; Labels erzeugen, Label-Stack füllen
l1=LabelNr:LabelNr+1 ; für Schleifen-Kopf "do"
l2=LabelNr:LabelNr+1 ; für Schleifen-Fuß "until, while, end do"
Add_ContinueBreakLabel(l1,l2)
; Schleifen-Kopf
EmitX("[L_"+l1+"]") ; <=== KOPFLABEL FÜR CONTINUE
EmitX("// DO")
; Schleifen-Körper
Statement()
; Schleifen-Fuß:
; "until" vorhanden?
If Scanner::Lexem="until"
Scanner::GetToken()
Emit(0,"// DO ... UNTIL")
Condition()
Emit(ExpTyp,"JF","L_"+l1)
; "while" vorhanden?
ElseIf Scanner::Lexem="while"
Scanner::GetToken()
Emit(0,"// DO ... WHILE")
Condition()
Emit(ExpTyp,"JT","L_"+l1)
; unendliche Schleife
Else
Emit(0,"// DO ... FOREVER")
Emit(0,"j","L_"+l1)
EndIf
; Leerzeile
EmitX("// END DO")
EmitX("[L_"+l2+"]") ; <=== FUSSLABEL FÜR BREAK
; Label-Stack um 1 Element verringern ; <=== OBERSTES LÖSCHEN, VORIGES WIRD
; ZUM OBERSTEN ELEMENT
DeleteElement(LabelStack())
EndProcedure
Vergessen wir dabei die Umbauten bitte auch nicht in der While-Schleife und in allen anderen Schleifen, die wir vielleicht noch einbauen wollen.
Denken wir noch einmal darüber nach, was wir getan haben!
Wir benötigen in Schleifen immer 2 Labels:
- Label am - eigentlich exakt vor dem - Schleifenkopf für continue
- Label am - eigenlich nach dem - Schleifenfuß für break
Im Schleifenkörper (=Statement) könnte ja wieder eine Schleife sein. Sollte dort eine neue Schleife sein, dann werden die Labels von dieser Schleife am Stack gespeichert. Die alten Labels bleiben eine Stufe darunter natürlich am Stack weiterhin gemerkt (das ist ja gerade der Sinn eines Stacks).
Wenn eine Schleife endet, also nach dem Schleifen-Körper, wird ganz am Ende der oberste Eintrag des Stacks gelöscht und der vorige Eintrag ist ab dann wieder der oberste.
Wie immer benötigen wir auch eine Prozedur
Continue_Statement() und wie immer dürfen wir auch nicht vergessen, in Statement den neuen Befehl einzutragen:
Code:
Procedure Continue_Statement()
; Zur Übersichtlichkeit
Emit(0,"// CONTINUE")
; Break-Jump ausgeben
Emit(0,"j","L_"+LabelStack()\Continue_)
; Leerzeile
EmitX()
; nächstes Token-Lexem
Scanner::GetToken()
EndProcedure
Es wird einfach das oberste Continue-Label als Jump ausgegeben, fertig!
Die Behandlung von
break wäre genauso einfach, aber ich möchte hier Pure Basic nachahmen und einen Sprung aus beliebig tief geschachtelten Schleifen ermöglichen.
Aus diesem Grund sieht
Break_Statement() etwas komplizierter aus, aber nur auf den ersten Blick:
Code:
Procedure Break_Statement()
; nächstes Token-Lexem laden
Scanner::GetToken()
; ist Token eine Integer wie in 'break 2'
If Scanner::Token='I'
; Zahl hinter 'break' holen
; -1, weil 'break 2' darf nur 1 Element zurückgehen am Stack
break_zahl = Val(Scanner::Lexem) - 1
; Zur Übersichtlichkeit
Emit(0,"// BREAK "+Scanner::Lexem)
; benötigte Elemente zurückgehen
SelectElement(LabelStack(),ListIndex(LabelStack())-break_zahl)
; Break-Jump ausgeben
Emit(0,"j","L_"+LabelStack()\Break_)
; Label Stack zurückstellen
LastElement(LabelStack())
; nächstes Token-Lexem laden
Scanner::GetToken()
; keine Break-Zahl folgt
Else
; Zur Übersichtlichkeit
Emit(0,"// BREAK")
; Break-Jump ausgeben
Emit(0,"j","L_"+LabelStack()\Break_)
EndIf
; Leerzeile
EmitX()
EndProcedure
Der Else-Zweig wäre die ganze Break-Prozedur, wenn wir den Befehl ohne Break-Level haben wollen würden!
Der Fall mit Break-Level sieht folgendermaßen aus:
- Parser holt die Zahl (wenn 'I' - Integer) hinter break.
- Die Zahl wird um 1 verringert. Break 1 würde aus der aktuellen Schleife springen (also 0 Stack-Levels zurückgehen, d.h. der oberste Eintrag wird verwendet), break 2 verlässt 2 Schleifen, also diese und die vorige (also 1 Stack-Level zurückgehen, d.h. der direkt unter dem obersten Eintrag wird verwendet, deshalb -1 statt -2).
Testen wir unser Werk mit folgendem
Source-Code:
Code:
int x
while (x=1)
{
continue // springt vor while
break // springt nach (*)
do
{
continue // springt vor do
break // springt nach (**)
break 2 /* springt nach (*),
verlaesst 2 Schleifen */
}//(**)
}//(*)
Wir erhalten folgende
ASM-Ausgabe:
Code:
[L_1]
// WHILE
^ // CONDITION
| // math expression
| ipushg 0 // x
| ipushc 1 // 1
| ieq // =
| iJF L_2
|
| // CONTINUE
'---- j L_1
// BREAK Außenschleife
j L_2 -----------------.
|
[L_3] |
// DO |
^ // CONTINUE |
'---- j L_3 |
|
// BREAK Innenschleife |
j L_4 ---. |
| |
// BREAK 2 | 2 Schleifen |
j L_2 ---|------------.|
| ||
// DO ... FOREVER | ||
j L_3 | ||
// END DO | ||
[L_4] <--------------------' ||
j L_1 Innenschleife ||
// WEND ||
<---------------------------------'|
[L_2] <----------------------------------'
Außenschleife
Bemerkenswert einfach!
