Oh ja! Ganz tolle Idee, einfach den Look beinhart austauschen an der Quelle und ein anderes Ei ins Nest legen, ein Kuckucksei eben ?Ja, das ist am Besten. GetChar() könnte dann #LF, #CR, #LFCR, #CRLF behandeln, Zeilennummer erhöhen, und es
gibt für #LF, #CR, #LFCR, #CRLF immer #LF zurück. Es konvertiert also auch #CR, #CRLF und #LFCR zu #LF -
dann musst Du an anderen Stellen nur auf #LF prüfen.
[Tutorial] Compiler und Virtual Machine
Re: [Tutorial] Compiler und Virtual Machine
Re: [Tutorial] Compiler und Virtual Machine
Genau. Ein klassischer Tokenizer mit Enumerationen gibt für #LF, #CR, #LFCR, #CRLF immer Tokens.Eol zurück. In Deinem Falle eben immer #LF.puretom hat geschrieben:Oh ja! Ganz tolle Idee, einfach den Look beinhart austauschen an der Quelle und ein anderes Ei ins Nest legen, ein Kuckucksei eben ?Ja, das ist am Besten. GetChar() könnte dann #LF, #CR, #LFCR, #CRLF behandeln, Zeilennummer erhöhen, und es
gibt für #LF, #CR, #LFCR, #CRLF immer #LF zurück. Es konvertiert also auch #CR, #CRLF und #LFCR zu #LF -
dann musst Du an anderen Stellen nur auf #LF prüfen.
Was hast Du eigentlich mit Doppelpunkt und Semikolon geplant? Im Moment werden die durch IsWhite() gefiltert, so dass
der folgende Code ohne Fehler kompiliert wird:
Code: Alles auswählen
1::+;1;+::2;;;*;;4:::
...Danilo
"Ein Genie besteht zu 10% aus Inspiration und zu 90% aus Transpiration" - Max Planck
Re: [Tutorial] Compiler und Virtual Machine
Zu dem ";":
Was habe ich geplan? Hmmm !!!
So ganz klar ist mir das leider selbst noch nicht, ich habe es einmal bewusst so gelassen, um zunächst für den Lernprozess des Tutoriallesers zu zeigen: technisch brauchen tut man ihn nicht.
Crenshaw macht den ";" als statement delimiter als Option erlaubt, aber nicht zwingend (an sich ja nicht schwer zu machen mit wenigen Änderungen des Parsers und Compilers, ev. zeige ich das mal?).
Ich mag den ";" auch überhaupt nicht, ich bin ein Kind des Basics der 80'er Jahre und finde ihn deswegen optisch wie eine Ohrfeige - Kann auch nicht aus meiner Haut. Deshalb muss er weg !
Meine Idee war dann, ihn überhaupt einfach rauszufiltern (auch wegen dem Lern-Vorzeigeeffekt, siehe oben), denn technisch ist er für den Compiler unnötig, der kommt ohne ";" auch nicht aus dem Schritt. Sollte ihn dann wer verwenden wollen, dann ist er jederzeit (sogar bis ins Extreme wie du in deinem Beispiel:
Code: Alles auswählen
1::+;1;+::2;;;*;;4:::
Natürlich: Ein Debugger, der Fehler in einer Warnungsliste ausgibt, tut sich leichter, denn den lasse ich (ist jetzt nur so eine Idee von mir) nach einem ";" den Versuch starten, wieder Schritt zu finden.
Da das aber eine Skriptsprache, eventuell sogar nur für den internen Gebrauch eines hypothetischen Game-Entwicklerteams eines Games mit Scriptingmöglichkeit wird (und nach den ersten Lernschritten jeder, der das liest, selber weiterforschen, erweitern, professionalisieren, modernisieren muss) und natürlich keine kommerzielle Hochsprache, kann ich mal fürs Erste damit leben, denn warum sollte ein verantwortungsvoller echter Programmier die Toleranz des Compilers derart auszutzen wie in deinem Beispiel. Wenn er es doch tut, ja und, wenn er will, soll er, tut mir ja nicht weh.
Also abschließend, lange Worte und kurzer Sinn:
- 1) zunächst hatte ich pädagogische Ziele mit dem Rausfiltern
2) Ob ich ein Kapitel mache, wo ich das ";" diskutiere? Vielleicht ähnlich wie Crenshaw später, vielleicht.
3) persönlicher Geschmack und Abneigung gegen ";" an sich
Ich mag Folgendes sehr gern (ähnlich Python und PB)
Code: Alles auswählen
if (a=1) : print("Zahl ist 1")
else if (a=2) : print("Zahl ist 2")
else if (a=3) : print("Zahl ist 3")
else : print("Zahl ist ist nix von allem")
Zu Enumerations und Tokens:
Würde ich auch gerne irgendwann zeigen (Enumeration und Token-Codes vor dem Parser machen), angedeutet glaube habe ich es in meinen Einleitungen schon: Num, Name, Op, eol, usw., Token-Codes für keywords über eine Map, denn die ist flott, dann nur mehr Token-Zahlen in der Select-Case usw.Genau. Ein klassischer Tokenizer mit Enumerationen gibt für #LF, #CR, #LFCR, #CRLF immer Tokens.Eol zurück.
Wirth macht es freilich natürlich auch so.
Auch Crenshaw macht das ähnlich mit der Prozedur Scan() in Block().
Aber ich glaube bei dem weiten Feld des Compilerbaus und der unzähligen Möglichkeiten bin ich froh, wenn ich bis zur Virtuellen Maschine und kleinen laufenden Testprogrammen in der Console komme mit meinem Tutorial .
Ich habe aber beim Lernen erkannt, dass man alles noch VIEL einfacher versteht, wenn ich das für den Beginn in der Select-Case direkt mit Stringvergleichen in Statement() (C-Style Blockenden) oder in Block() (PB-Style Blockenden mit "EndIf" usw.) mache. Je weniger Prozeduren desto besser zum Erlernen des Systems, wie sich die Prozeduren gegenseitig aufrufen, denn diese Art Parser für diese Art Grammatik, die wir hier implementieren, leben von Rekursion.
Wer mal die Grundmethode kann, kriegt dann die anderen Feinheiten so nebenbei dann später selber hin.
LG Puretom
Edit: Lieber Danilo. Ich habe erst jetzt entdeckt, dass du uns einen Expression Parser ein paar Postings zuvor zur Ansicht hochgeladen hast. Vielen Dank dafür! Lg Puretom
(Unter: http://forums.purebasic.com/german/view ... 2&start=30, Verfasst: 28.09.2013 08:30, ca. in der Mitte blauer Link benannt "Expression Parser")
Re: [Tutorial] Compiler und Virtual Machine
*** KAPITEL 9: TEXT ***
9. Wesentliche Erweiterung des mathematischen Parsers
9.1. Hinzufügen neuer Operatoren: Vergleiche, boolesche Operatoren wie < <= <> = >= > and or xor
9.1.1. Die neue Präzedenztabelle. Eine Diskussion
In diesem Abschnitt möchte ich über die Präzedenz der Logischen Operatoren sprechen:
- logische Vergleiche (engl. relational operators oder kurz RelOps): =, <, <=, <>, >=, >
- logische boolesche Verknüpfung: and, or, xor
Von unserer Entscheidung hier hängt viel von dem ab, was man das "look and feel" einer Programmiersprache nennt.
Zur Erinnerung unsere bisherige Toy-C-Präzedenztabelle:
Code: Alles auswählen
Prozedur Operator Präzedenz
-----------------------------------------------------------
ValueFactor() ( ... ) höchste
-----------------------------------------------------------
ValueFactor() Negation
bzw. Unäres -|+
-----------------------------------------------------------
MulTerm() * / %
-----------------------------------------------------------
AddExpression() + - niedrigste
-----------------------------------------------------------
Code: Alles auswählen
Prozedur Operator Präzedenz
-----------------------------------------------------------
ValueFactor() ( ... ) höchste
-----------------------------------------------------------
ValueFactor() Negation
bzw. Unäres -|+
Unäres not
-----------------------------------------------------------
MulTerm() * / and
-----------------------------------------------------------
AddExpression() + - or
-----------------------------------------------------------
Relation() = < <= <> >= > niedrigste
-----------------------------------------------------------
Wir finden "not", "and", "or" und "= < <= <> >= >" neu in dieser Tabelle.
Anmerkung: Ich möchte NICHT die Unterscheidung von C zwischen "=" und "==" übernehmen. Obwohl Toy-C genannt, ist meine Sprache hier näher an Pure Basic als an C. Auch were ich "and" und nicht "&&" und auch "or" verwenden.
Überlegen wir uns, wie mit der Grammatik von Pascal folgende Rechnung gelöst werden würde:
Code: Alles auswählen
if ( a=2 and b<>3 ) <=== Rechnung
if ( a=(2 and b)<>3 ) <=== würde so gelöst
Warum ist das so?
Es liegt daran, dass "and" in der Tabelle höherwertiger ist als "=", also wird "and" vor "=" ausgerechnet, genauso wie eben "* / %" vor "+ -".
Ich schlage folgende vorläufige Präzedenztabelle für Toy-C vor:
Code: Alles auswählen
Prozedur Operator Präzedenz
-----------------------------------------------------------
ValueFactor() ( ... ) höchste
-----------------------------------------------------------
ValueFactor() Negation
bzw. Unäres -|+
-----------------------------------------------------------
MulTerm() * / %
-----------------------------------------------------------
AddExpression() + -
-----------------------------------------------------------
Relation() < <= <> = >= >
-----------------------------------------------------------
BoolAnd() and
-----------------------------------------------------------
BoolOrXor() or xor niedrigste
-----------------------------------------------------------
Wenn wir jetzt nochmals die Beispiele von oben vergleichen, dann ergibt sich folgende Operatorenreihenfolge:
Code: Alles auswählen
if ( 3*4 and 3+5 )
if ( (3*4) and (3+5) ) <=== würde so gelöst
-----------------------------------------------
if ( x<=4 and y>5 )
if ( (x<=4) and (y>5) ) <=== würde so gelöst
Zur Vollständigkeit schauen wir uns noch die Tabelle von Pure Basic (siehe PB-Hilfe -> Variablen, Typen, Operatoren) an, die recht ähnlich ist (bis auf 'not', siehe später):
Code: Alles auswählen
-----------------+---------------------
Prioritäts-Level | Operatoren
-----------------+---------------------
8 (hoch) | ~, -
7 | <<, >>, %, !
6 | |, &
5 | *, /
4 | +, -
3 | >, >=, <, <=, =, <>
2 | Not
1 (niedrig) | And, Or, XOr
-----------------+---------------------
Diese Diskussion über Präzedenzen findet sich auch in Crenshaw und ist sehr interessant, aber leider auf Englisch (http://www.pp4s.co.uk/main/tu-trans-comp-jc-06.html).
9.1.2. Mathematischer Parser, Erweiterung um die Operatoren "< <= <> = >= > and or xor"
Die Erweiterung um diese Operatoren geschieht genau so, wie wir uns das vorstellen.
Jede Präzedenzebene bekommt wieder eine Prozedur zugewiesen.
Hier ein Ausschnitt aus dem Declare-Teil von Modul Parser():
Code: Alles auswählen
; --- Mathematischer Parser ---
Declare ValueFactor() ; holt einen Operanden-Wert, ( ... ), unary -|+
Declare MulTerm() ; bearbeitet Mul-Ops: *, /, %
Declare AddExpression() ; bearbeitet Add-Ops: +, -
Declare Relation() ; logische Vergleiche <, <=, <>, =, >=, >
Declare BoolAnd() ; boolesches And
Declare BoolOrXor() ; boolesches Or, Xor
Declare Expression() ; mathematischer Expression Parser 3+4*(4+3)
Code: Alles auswählen
Procedure Expression()
BoolOrXor()
EndProcedure
9.2. Umbau der Implementation des Unary -|+
Wenn man ein System hat, das man einsetzt, wie hier z.B. eben die Rekursion, dann sollte man dieses System auch konsequent durchziehen. Das sollten wir uns merken.
Während unsere erste "naive" Version des unären + bzw. des unären - bestens und sogar perfekt funktioniert, ist sie eben nicht voll an unser System angepasst.
Bauen wir also unsere Präzedenztabelle nochmals um (und wir werden es noch mehrmals tun ):
Code: Alles auswählen
Prozedur Operator Präzedenz
-----------------------------------------------------------
ValueFactor() ( ... ) höchste
-----------------------------------------------------------
NegValueFactor() Negation
bzw. Unäres -|+
-----------------------------------------------------------
MulTerm() * / %
-----------------------------------------------------------
AddExpression() + -
-----------------------------------------------------------
Relation() < <= <> = >= >
-----------------------------------------------------------
BoolAnd() and
-----------------------------------------------------------
BoolOrXor() or xor niedrigste
-----------------------------------------------------------
Code: Alles auswählen
Procedure NegValueFactor()
; --> Token/Lexem steht auf Value ODER
; --> auf unary + | unary -
; Ist ein 'unary -' vor ValueFactor?
If Scanner::Token='-'
Scanner::GetToken()
ValueFactor() ; Aufstieg zu ValueFactor()
Code::_Neg()
; Ist ein 'unary +' vor ValueFactor?
ElseIf Scanner::Token='+'
Scanner::GetToken()
ValueFactor() ; Aufstieg zu ValueFactor()
; 'normaler' ValueFactor
Else
ValueFactor() ; Aufstieg zu ValueFactor()
EndIf
; --> in Token/Lexem ist Token/Lexem nach Value
; --> wenn die Expression weitergeht, ist das ein Operator
; --> Abstieg zu MulTerm()
EndProcedure
Was passiert hier?
Besprechen wir nur den Teil von "unary -", der andere ist identisch:
- Scanner::GetToken(): Bei einem "unary -" holt der Parser das nächste Token-Lexem-Pärchen
- ValueFactor(): ruft ValueFactor() auf und kümmert sich um Values
- Code::_Neg(): kommt von ValueFactor() zurück und hat sich quasi "gemerkt", dass er danach noch negieren muss
"merken", ob "unary -" war ---> Value holen ---> "neg" ausführen, weil "gemerkt"
Die alte Prozedure ValueFactor() (ohne Kommentare oben/unten):
Code: Alles auswählen
Procedure ValueFactor()
; pruefe auf Vorzeichen (unary - | unary +)
; Ja? Überspringe es und setze bei "-" ein Flag
If Scanner::Token = '-': Scanner::GetToken(): negate = #True
ElseIf Scanner::Token = '+': Scanner::GetToken()
EndIf
; je Token-Art: Ausgabe des Values als ASM-Code
Select Scanner::Token
Case '#' : Code::PushConst(Scanner::Lexem)
Case 'x' : Code::PushGlobal(Scanner::Lexem)
Case '(' : Scanner::GetToken() ; überspringe "("
Expression() ; ")" wird weiter unten übersprungen
Default : Scanner::Expected("Korrekter Operand (Konstante Zahl, Variablen- oder Prozedurname)")
EndSelect
; Teste Flag "negate"
; Ja? Führe "unary -" durch: negiere Value
If negate = #True
Code::_Neg()
EndIf
; holt nächstes Token (=Operator)
; überspringe eventuell ")"
Scanner::GetToken()
EndProcedure
Code: Alles auswählen
Procedure ValueFactor()
; je Token-Art: Ausgabe des Values als ASM-Code
Select Scanner::Token
Case '#' : Code::PushConst(Scanner::Lexem)
Case 'x' : Code::PushGlobal(Scanner::Lexem)
Case '(' : Scanner::GetToken() ; überspringe "("
Expression() ; ")" wird weiter unten übersprungen
Default : Scanner::Expected("Korrekter Operand (Konstante Zahl, Variablen- oder Prozedurname)")
EndSelect
; holt nächstes Token (=Operator)
; überspringe eventuell ")"
Scanner::GetToken()
EndProcedure
Durch die Aufruflogik der Prozeduren ersparen wir uns auch das Flag negate. Wir kommen ja bereits vom erfolgreichen Test nach "unary -" in ValueFactor() an, also können wir bei Rückkehr automatisch "neg" ausführen.
Natürlich - wir sehen es dann in der Gesamtimplementation im Code-Teil - müssen wir auch wieder einen Declare-Teil und die Aufrufe von MultTerm() von ValueFactor() auf NegValueFactor() ändern, das können wir aber mittlerweile blind (hoffe ich).
9.3. Hinzufügen eines neuen Operators: Potenzieren ^
9.3.1. Erster naiver und falscher Ansatz
Auf jeden Fall fügen wir die Potenz (engl exponentiation oder '2 power to 4') mit einer möglichst hohen Präzedenz ein, wie das auch die Programmiersprache Python tut (siehe wieder einmal: http://montcs.bloomu.edu/~bobmon/Inform ... scal.shtml). Die anderen Sprachen erwähne ich gar nicht, denn diese besitzen (wie PB auch!) gar keinen Exponentialoperator.
Wir werden das "^"-Zeichen (Zirkumflex oder engl. caret) verwenden (nebenbei: oben erwähntes Python verwendet übrigens "**").
Präzedenztabelle für die Potenz:
Code: Alles auswählen
Prozedur Operator Präzedenz
-----------------------------------------------------------
ValueFactor() ( ... ) höchste
-----------------------------------------------------------
NegValueFactor() Negation
bzw. Unäres -|+
-----------------------------------------------------------
Exponentiation() ^
-----------------------------------------------------------
MulTerm() * / %
-----------------------------------------------------------
AddExpression() + -
-----------------------------------------------------------
Relation() < <= <> = >= >
-----------------------------------------------------------
BoolAnd() and
-----------------------------------------------------------
BoolOrXor() or xor niedrigste
-----------------------------------------------------------
Die erste falsche Prozedur Exponentiation():
Code: Alles auswählen
Procedure Exponentiation_LA() ; *** falsch: links_assoziativ ***
; kann entfernt werden, wenn nicht benötigt
; --> Token/Lexem steht auf Value
; Aufstieg zu NegValueFactor()
NegValueFactor()
; Power-Op in Token/Lexem?
While Scanner::Token='^'
; vor Aufstieg nächstes Token (=Value) holen
Scanner::GetToken()
; Aufstieg zu NegValueFactor()
; ====> FALSCH! NUR BEI SELBSTAUFRUF RICHTIG RECHTSASSOZIATIV !!! <====
; ====> siehe richtige Lösung weiter unten
NegValueFactor()
; Ausgabe des ASM-Codes des Operators
Code::_Exp()
Wend
; --> in Token/Lexem ist Token/Lexem nach dem Operator ODER
; --> in Token/Lexem ist Token/Lexem nach dem Value
; --> Abstieg zu MulTerm
EndProcedure
Code: Alles auswählen
-2^-4
XXXXXXXXXXXXXXX
2^3
XXXXXXXXXXXXXXX
2^3*3
XXXXXXXXXXXXXXX
2^3^4 // <==== HIER LIEGT EIN PROBLEM VOR
Code: Alles auswählen
push Const 2
neg
push Const 4
neg
exp <=== PASST
push Global XXXXXXXXXXXXXXX
push Const 2
push Const 3
exp <=== PASST
push Global XXXXXXXXXXXXXXX
push Const 2
push Const 3
exp <=== PASST: "^" höhere Präzedenz als "*"
push Const 3
mul
push Global XXXXXXXXXXXXXXX
push Const 2 <=== !!!!!! FALSCH FALSCH FALSCH !!!!!!
push Const 3
exp
push Const 4
exp
Es hat doch auch z.B. bei "+,-" bzw. "*,/,%" problemlos funktioniert.
9.3.2. Operatorassoziativität und der Vorschlag einer Lösung
Beantworten wir die oben gestellte Frage.
So wie wir die falsche Prozedur Exponentiation() programmiert haben, löst der Compiler das Ganze folgendermaßen, also genauso wie 2+3+4 oder 2*3*4 usw.:
Code: Alles auswählen
Angabe: 2^3^4
Lösungsreihenfolge des Parsers: (2^3)^4
Er rechnet also zuerst 2^3 aus und das Ergebnis nimmt er dann ^4. Bei der Multiplikation, der Addition, bei nahezu allen anderen Operatoren würde das stimmen.
Das Phänomen, das wir gerade kennen lernen und das uns jetzt ärgert, nennt man Operatorassoziativität.
(siehe:http://de.wikipedia.org/wiki/Operatoras ... vit%C3%A4t)
Die meisten Operatoren sind linksassoziativ, d.h. sie werden von links nach rechts gelöst.
Der Exponentialoperator ist aber rechtsassoziativ, d.h. wir müssen ihn von rechts nach links lösen (siehe: http://de.wikipedia.org/wiki/Operatorra ... Operatoren).
Auch in den andauernd von mir zitierten Präzendenztabellen (diese da: http://montcs.bloomu.edu/~bobmon/Inform ... scal.shtml) findet sich eine Spalte, die wir bisher wahrscheinlich noch gar nicht bewusst wahrgenommen haben: Sie heißt "Associativity", also Assoziativität.
Ich möchte diese Spalte in den nächsten Präzedenztabellen von Toy-C ebenfalls aufnehmen.
In der Literatur über Compiler habe ich über die Exponentation folgende Aussagen gefunden:
- Die Exponentation sei wegen ihrer Rechtsassoziativität sehr schwer umzusetzen.
- Die meisten Programmiersprachen lösen das Problem, indem sie einfach keinen Exponentialoperator einfügen, auch Pure Basic - siehe PB-Hilfe unter Pow() -, Pascal und alle C-Abarten machen das, wenn wir in die Tabelle schauen. Sie nutzen eine Prozedur, die die Exponentialfunktion umsetzt.
Damit umschifft man das Problem, weil eine Prozedur aufgerufen in einer Prozedur automatisch rechtsassoziativ wird.
Ein Beispiel in Pure Basic:Richtig ergibt das 256. Laut Wikipedia und Assoziativgesetz (http://de.wikipedia.org/wiki/Assoziativ ... Einordnung: gleich oberhalb der Überschrift "Einordnung") ist das die richtige Lösung.Code: Alles auswählen
; ================= ; Angabe ist: 2^2^3 ; ================= RA = Pow(2,Pow(2,3)) ; richtig rechtsassoziativ : 2^(2^3) ==> 256 LA = Pow(Pow(2,2),3) ; falsch linksassoziativ : (2^2)^3 ==> 64 Debug "richtig rechtsassoziativ : "+Str(RA) Debug "falsch linksassoziativ : "+Str(LA)
Würde der Potenzoperator linksassoziativ sein, dann wäre die Lösung 64.
Immerhin macht das C über C++ bis C# mit einer Prozedur bzw. Methode "pow()", auch Pascal kennt diese Prozedur, einzig Python und Lua (http://www.lua.org/pil/3.5.html) machen das unter den mir bekannten Programmiersprachen mit einem eigenen Operator, selbstverständlich mit korrekter Rechtsassoziativität (wieder siehe die schon unzählige Male verlinkte Präzedenztabelle bzw. Lua-Link vorhin).
Also so richtige Schwergewichte, also die Big Boys, "schwindeln" sich mit einer Prozedur aus der Assoziativitätsproblematik, nur Python nicht, also müsste es ja einen Weg geben.
Nach relativ kurzer Überlegung fand ich eine Lösung. Und diese ist so derart einfach wie sexy, dass ich - ich habe oben von einem Vorschlag gesprochen - gar nicht glauben kann, dass sie 1. funktioniert und dass 2. ich sie entdeckt habe.
Aus diesem Grund bitte ich alle, den Parser in allen erdenklichen Lebenslagen zu testen und zu schauen, ob meine Lösung letztlich auch wasserdicht funktioniert (vor allem auch INNERHALB komplizierter mathematischer Ausdrücke mit Klammern und Negationen uvm.).
Ich danke allen Testern aus tiefstem Herzen bereits im Voraus.
Sollten sich Bugs finden, was solls. Die recht häufige Prozedur-Variante können wir immer noch ohne viel Aufwand umsetzen.
Ein Source-Code-Beispiel:
Code: Alles auswählen
2+3+4 // linksassoziativ: (2+3)+4
XXXXXXXXXXXX
2^2^3 // rechtsassoziativ: 2^(2^3)
DAS_GLEICHE
2^(2^3) // ASM-Code sollte gleich sein: 2^(2^3)
FUNKTIONSTEST_1
25*(2^3^4+(25+3)/2) /* testet, ob das Ganze auch eingefügt in einen
* komplizierteren Ausdruck aus der Rekursion herausfindet.
*/
FUNKTIONSTEST_2
25+(2^-3^-(4+5*2) *100) /* 2. Exponent ein Klammerausdruck
* und einige unnötige Negationen
*/
Code: Alles auswählen
push Const 2 <=== 2+3+4 // linksassoziativ: (2+3)+4
push Const 3
add
push Const 4
add
push Global XXXXXXXXXXXX
push Const 2 <=== 2^2^3 // rechtsassoziativ: 2^(2^3)
push Const 2
push Const 3
exp
exp
push Global DAS_GLEICHE
push Const 2 <=== 2^(2^3) // ASM-Code sollte gleich sein: 2^(2^3)
push Const 2
push Const 3
exp
exp
push Global FUNKTIONSTEST_1 <=== 25*(2^3^4+(25+3)/2)
push Const 25
push Const 2
push Const 3
push Const 4
exp
exp
push Const 25
push Const 3
add
push Const 2
div
add
mul
push Global FUNKTIONSTEST_2 <=== 25+(2^-3^-(4+5*2) *100)
push Const 25
push Const 2
push Const 3
neg
push Const 4
push Const 5
push Const 2
mul
add
neg
exp
exp
push Const 100
mul
add
Code: Alles auswählen
Procedure Exponentiation() ; *** richtig: rechts_assoziativ ***
; --> Token/Lexem steht auf Value
; Aufstieg zu NegValueFactor()
NegValueFactor()
; Power-Op in Token/Lexem? <==== IF statt WHILE
If Scanner::Token='^'
; vor Aufstieg nächstes Token (=Value) holen
Scanner::GetToken()
; Rechtsassoziativ !!! --> Selbstaufruf
; ====> BEI SELBSTAUFRUF RICHTIG RECHTSASSOZIATIV !!! <==== ÄNDERUNG HIER
Exponentiation()
; Ausgabe des ASM-Codes des Operators
Code::_Exp()
EndIf
; --> in Token/Lexem ist Token/Lexem nach dem Operator ODER
; --> in Token/Lexem ist Token/Lexem nach dem Value
; --> Abstieg zu MulTerm()
EndProcedure
- Mein Lösungsansatz besteht darin, dass sich die richtige Prozedur Exponentation() immer wieder selbst aufruft - bis zum Ende der Kette der "^"-Operatoren (Beispiel: 2^3^4 +5).
- Am Ende der "^"-Operatorenkette (also bei '+' im Beispiel) beginnt die Prozedurenaufrufkette wieder rückwärts zu laufen und arbeitet alle "exp"-Befehle bis zum letzten ab.
- Dadurch wird meiner Meinung nach der Teilausdruck, der aus "^"-Operatoren besteht, rechtsassoziativ.
- Bitte nicht übersehen, dass ich nicht nur den zweiten Aufruf zur nächsthöheren Prozedur zu einem Selbstaufruf verändert habe, sondern auch die While-Schleife zu einer If-Then-Konstruktion.
- Wie oben bereits von mir erwähnt, bitte ausgiebig testen.
9.4. Hinzufügen des unären logischen Not
9.4.1. Vorüberlegungen
Die Sache mit dem logischen Not-Operator (nicht zu verwechseln mit dem bitweisen Not-Operator) ist auf den ersten Blick einfach, auf den zweiten schwierig.
Nicht dass seine Umsetzung besonders schwierig wäre, das Wie ist also nicht das Problem an sich, aber die Qual der Entscheidung, wo wir den unären Not-Operator in die Präzedenztabelle einfügen, ist groß.
Verschiedene Sprachen gehen da verschiedene Wege:
- In Pure Basic finden wir "not" recht weit unten, ebenso in Python.
- C und Pascal hingegen haben "not" sehr weit oben in der Präzedenztabelle.
9.4.2. Diskussion: logisches Not und die Operatoren and, or und xor?
Ich folge hier einem Argument von Crenshaw im Kapitel http://www.pp4s.co.uk/main/tu-trans-comp-jc-16.html.
Crenshaw als Naturwissenschaftler weiß, dass man den booleschen Operator "xor" (exlusives Or) auch mit anderen booleschen Operatoren umschreiben kann.
Folgende Schreibweise für die Umschreibung des Xor sei üblich in der Naturwissenschaft, meint er:
Code: Alles auswählen
a xor b >= (a and not b) or (not a and b)
Code: Alles auswählen
a xor b >= (a and not b) or ( not (a and b) )
^^^ ^^^
Ich schließe mich ihm an, dass "not" auf jeden Fall höhere Priorität als "and" und "or/xor" haben muss, das ist in den meisten Sprachen der Fall, auch in Pure Basic.
Wenn wir uns an die Präzedenztabelle von Pure Basic erinnern:
Code: Alles auswählen
-----------------+---------------------
Prioritäts-Level | Operatoren
-----------------+---------------------
8 (hoch) | ~, -
7 | <<, >>, %, !
6 | |, &
5 | *, /
4 | +, -
3 | >, >=, <, <=, =, <>
2 | Not
1 (niedrig) | And, Or, XOr
-----------------+---------------------
"Not" ist hier nämlich von sehr niedriger Präzedenz, wohl richtigerweise noch höher als "and" und "or/xor", aber sonst ganz unten in der Tabelle.
9.4.3. Diskussion: logisches Not und die restlichen Operatoren?
Wie hoch soll die Präzedenz des Not-Operators in Bezug auf die übriggebliebenen Operatoren sein?
Wie es um "and, or, xor" steht, wissen wir ja bereits, denn wir haben uns mit Crenshaw (und den anderen Programmiersprachen) darauf geeinigt, dass "not" auf jeden Fall eine höhere Präzedenz als "and" und "or/xor" haben soll, um die Umschreibung für "xor" korrekt aussehen zu lassen. Damit hätten wir die naturwissenschaftliche bzw. ingenieurswissenschaftliche Mathematik befriedigt bzw. nicht gegen uns aufgebracht.
Aber was ist mit all den anderen?
9.4.3.1. Not-Operator mit sehr hoher Präzedenz (ohne Code)
Präzedenztabelle für not mit sehr hoher Präzedenz:
Code: Alles auswählen
Prozedur Operator Assoziativität Präzedenz
-----------------------------------------------------------------------
ValueFactor() ( ... ) höchste
-----------------------------------------------------------------------
NegValueFactor() Negation L *-->
bzw. Unäres -|+
-----------------------------------------------------------------------
LogNot() unäres log. not R <--*
-----------------------------------------------------------------------
Exponentiation() ^ R <--*
-----------------------------------------------------------------------
MulTerm() * / % L *-->
-----------------------------------------------------------------------
AddExpression() + - L *-->
----------------------------------------------------------------------
Relation() < <= <> = >= > L *-->
-----------------------------------------------------------------------
BoolAnd() and L *-->
-----------------------------------------------------------------------
BoolOrXor() or xor L *--> niedrigste
-----------------------------------------------------------------------
Ich folge hier ganz der bereits erwähnten Präzedenztabelle von C (http://montcs.bloomu.edu/~bobmon/Inform ... scal.shtml), aber auch in Pascal finden wir "not" ganz weit oben (siehe ebenfalls voriger Link).
Folgen wir diesem Gedanken kurz einmal und überlegen wir uns, wo uns das beim Rechnen hinbringt.
Rechnen wir folgenden Source-Code mit der obigen Präzendenztabelle durch:
Code: Alles auswählen
not a + 5 * 3 // sollte unserer derzeitigen Präzedenztabelle
// gemäß so gelöst werden: (not a) + 5*3
Code: Alles auswählen
push Global A
not
push Const 5
push Const 3
mul
add
Wenn ich hier den ganzen Ausruck "a+5*3" mit "not" versehen will, brauche ich Klammern:
Code: Alles auswählen
not (a + 5 * 3)
Ein letztes Source-Code-Beispiel zu diesem Diskussionspunkt:
Code: Alles auswählen
not not not -B + -C
Code: Alles auswählen
push Global B
neg
not
not
not
push Global C
neg
add
Wenn ich das so haben will, dann werde ich meinen mathematischen Parser genau so programmieren. Die Prozedur LogNot() füge ich genau dort ein.
Ich kann die Prozedur aus dem Code-Teil von Kapitel 9 hernehmen und wie immer nur die Aufrufe davor und in der Prozedur entsprechend anpassen und schon habe ich "not" auf dieser Ebene.
Im Code-Teil ist die Prozedur LogNot() allerdings nicht so weit oben, sondern gleich über "and, or, xor" zu finden, also wie in Pure Basic, weil diesen Compiler ich schreibe und mir diese Präzedenz besser gefällt .
Und letztlich ist es die Entscheidung des Compiler-Schreibers, wo er sein Not haben will. Das muss jeder für sich selbst entscheiden, denn feste Regeln gibt es hier nicht.
9.4.3.2. Not-Operator mit Präzedenz genau über "and, or, xor" (mit Code)
Ich möchte jetzt hier wieder einmal zur Übersicht den Declare-Teil der Prozeduren des mathematischen Parsers sowie die Präzedenztabelle vorstellen.
Declare-Teil unseres mathematischen Parsers mit einem Not niedrigerer Präzedenz:
Code: Alles auswählen
; --- Mathematischer Parser ---
Declare ValueFactor() ; holt einen Operanden-Wert, ( ... )
Declare NegValueFactor() ; unary -|+
Declare Exponentiation() ; Potenz: Hoch-Operator: ^ (*rechts assoziativ*)
Declare MulTerm() ; bearbeitet Mul-Ops: *, /, %
Declare AddExpression() ; bearbeitet Add-Ops: +, -
Declare Relation() ; logische Vergleiche <, <=, <>, =, >=, >
Declare LogNot() ; unary logisches Not
Declare BoolAnd() ; boolesches And
Declare BoolOrXor() ; boolesches Or, Xor
Declare Expression() ; mathematischer Expression Parser 3+4*(4+3)
Code: Alles auswählen
Prozedur Operator Assoziativität Präzedenz
-----------------------------------------------------------------------
ValueFactor() ( ... ) höchste
-----------------------------------------------------------------------
NegValueFactor() Negation L *-->
bzw. Unäres -|+
-----------------------------------------------------------------------
Exponentiation() ^ R <--*
-----------------------------------------------------------------------
MulTerm() * / % L *-->
-----------------------------------------------------------------------
AddExpression() + - L *-->
----------------------------------------------------------------------
Relation() < <= <> = >= > L *-->
-----------------------------------------------------------------------
LogNot() unäres log. not R <--*
-----------------------------------------------------------------------
BoolAnd() and L *-->
-----------------------------------------------------------------------
BoolOrXor() or xor L *--> niedrigste
-----------------------------------------------------------------------
Das ist das Verhalten von Pure Basic und auch Python, nicht aber von C oder Pascal, man sieht: Wirklich bindende Regeln gibt es hier nicht.
Am besten ich bringe ein Beispiel:
Code: Alles auswählen
not a+32*4 // dieser Code müsste folgender
not (a+32*4) // Reihenfolge entsprechen, also Teil-Expression zuerst
Code: Alles auswählen
push Global A <=== not a+32*4, Reihenfolge ist not (a+32*4)
push Const 32
push Const 4
mul
add
not
push Global A <=== not (a+32*4)
push Const 32 <=== MIT OBEN IDENT, WEIL COMPILER RECHNET
push Const 4 <=== DURCH PRÄZEDENZ AUTOMATISCH GENAU SO
mul
add
not
Jetzt können wir in Toy-C intuitiv Folgendes tun:
Code: Alles auswählen
if (not 5+3*4) // das geht wie in Pure Basic
if (not(5+3*4)) // Klammer dazu nicht notwendig
Code: Alles auswählen
push Global A <=== not a+32*4, also Reihenfolge ist (not a)+32*4
not <=== NOT geht sofort auf den Value
push Const 32
push Const 4
mul
add
push Global A <=== not (a+32*4)
push Const 32 <=== diese Reihenfolge müssen wir mit einer Klammer erzwingen
push Const 4
mul
add
not
Code: Alles auswählen
(a and not b) or ( not a and b )
XXXXXXXXXXXXXXXXXXXXXX
(a and not b) or ( (not a) and b ) // so (lt. Crenshaw) will es die Naturwissenschaft verstehen
Code: Alles auswählen
push Global A
push Global B <=== BEIDES IDENT, RECHNET ALSO,
not <=== ALS WÄRE DIESE KLAMMER DA
and
push Global A
not
push Global B
and
or
push Global XXXXXXXXXXXXXXXXXXXXXX
push Global A
push Global B <=== BEIDES IDENT, RECHNET ALSO,
not <=== ALS WÄRE DIESE KLAMMER DA
and
push Global A
not
push Global B
and
or
Hier die fertige Prozedur LogNot() zum Abschluss:
Code: Alles auswählen
Procedure LogNot()
; --> Token/Lexem steht auf Value ODER
; --> auf unary not
; Ist ein 'unary not' vor ValueFactor? <==== IF statt WHILE
If Scanner::Lexem = "NOT"
; vor Aufstieg nächstes Token holen
; d.i 'not' ODER ein Value
Scanner::GetToken()
; Selbstaufruf für 'not not not ...' <==== VGL. MIT EXPONENTATION()
LogNot()
; Ausgabe des ASM-Codes des Operators
Code::_LogNot()
Else
; Aufstieg zu Relation()
Relation()
EndIf
; --> in Token/Lexem ist Token/Lexem nach Value
; --> wenn die Expression weitergeht, ist das ein Operator
; --> Abstieg zu BoolAnd()
EndProcedure
9.4.3.3. Eine noch niedrigere Präzedenz für not?
Was spricht eigentlich gegen eine noch niedrigere Präzedenz?
Außer der oben erwähnten Naturwissenschaft, die ein gewaltiges Gegenargument hat, nichts.
Ich für meinen Teil möchte über niemanden urteilen, der sein "not" noch tiefer in der Präzedenztabelle gibt - das kann jetzt aber wirklich schon jeder, einfach LogNot() über Expression() und vor BoolOrXor() verschieben und Prozeduraufrufe anpassen -, denn das hätte auch durchaus seine Vorteile.
Vergleichen wir hier beide Interpretationsmöglichkeiten, je nach Präzedenz:
Code: Alles auswählen
not a<2 and b>4 /* bei der derzeitigen Einstellung rechnet der Compiler:
/* (not a<2) and (b>4)
/* ^^^-- hier endet "not", weil "and" niedriger
not (a<2 and b>4) /* wir hätten vielleicht gewollt, dass "not"
/* auf die gesamte Expression geht
/*
Code: Alles auswählen
push Global A <=== (not a<2) and (b>4)
push Const 2
lt
not
push Global B
push Const 4
gt
and
push Global A <=== not (a<2 and b>4)
push Const 2
lt
push Global B
push Const 4
gt
and
not <--- ganz am Ende erst "not", also Gesamtexpression
Das soll jeder für sich entscheiden!
9.4.3.4. Einige Worte zur Assoziativität von not
Manche Programmiersprachen bezeichnen den Not-Operator als links- und manche als rechtsassoziativ. Da es sich hier aber um einen unären Operator handelt, stehe ich dazu, dass mich die Sache etwas verwirrt.
Ich als Nichtmathematiker habe mich gefühlsmäßig dem Lager der "Rechtsassoziativen" angeschlossen, weil auch die Compilerausgabe danach aussieht, dass am Ende einer Not-Kette von rechts nach links über die Rücksprünge der vorher aufgerufenen Prozeduren zurückgekehrt wird.
Im Endeffekt ist aber diese Festlegung, nach allem was ich gesehen habe, nicht wirklich wichtig und hat lediglich Einfluss auf den Tabelleneintrag von "not" in der Präzedenztabelle.
Sollte sich im Forum jemand mit tiefergehenden mathematischen Kenntnissen befinden, um uns den Unterschied auseinanderzusetzen, dann bitte ich darum, sich zu outen!
Ein kleines Beispiel in Source-Code und dann lasse ich die Sache auf sich beruhen, weil sie - glaube ich zumindest - eigentlich keinen Einfluss hat:
Code: Alles auswählen
not not not not not -A + B /* eigentlich irgendwie völlig sinnlos
/* zeigt Rücklauf der Prozeduren
*/
Code: Alles auswählen
push Global A
neg
push Global B
add
not
not
not
not
not
Nicht vergessen dürfen wir, dass im vorigen Unterkapitel die Präzedenz von "not" von mir recht weit (auf Pure-Basic-Niveau) heruntergesetzt wurde, der Compiler übersetzt richtigerweise die (Teil-)expression "(-A + B)" vorher, dann läuft er die Not-Operatoren rückwärts.
Erneut können wir in http://montcs.bloomu.edu/~bobmon/Inform ... scal.shtml die Assoziativität von "not" nachlesen, hier sind die meisten sogar für linksassoziativ .
Meine obige Bitte an die Mathematiker hier im Forum ist weiterhin aufrecht .
9.5. Kurz ein wenig Error Handling (Fehlerbehandlung)
Was passiert eigentlich, wenn wir bei einer unären Operation versehentlich zu viele z.B. "-" schreiben?
- Probieren wir das einmal in Pure Basic und in Toy-C:
Wir erhalten in Pure Basic eine korrekte Fehlermeldung (Syntax Error)!
Code: Alles auswählen
3---4
Und in Toy-C?
Wir bekommen ebenfalls eine Fehlermeldung, unser Compiler erkennt sogar, dass ein "Operand" fehlt, denn beim 1. Minus kennt er sich noch aus (sub), beim 2. Minus erwartet er "unary -" (neg), beim 3. Minus hätte er gerne wieder einen Operanden (Value: also Zahl oder Variablen- oder Prozedurname).
Was passiert eigentlich, wenn wir bei einem Operator, der aus einem Wort besteht (z.B. and, or, not, ...) und deshalb genauso gut ein Variablenname sein könnte, einen Fehler machen?
- Da in Pure Basic mixed Operations - in der Zusammenfassung am Ende von Kapitel 9 Genaueres darüber - nicht erlaubt sind, wähle ich für PB folgendes Beispiel:
PB gibt die richtige Fehlermeldung aus, meint doch der Compiler korrekterweise, dass jetzt ein Operator kommen müsste, erkennt ein "Or" und findet heraus, dass diese "Variable" ein Pure-Basic-Schlüsselwort ist. Fehler!
Code: Alles auswählen
If X<2 And Or X>=3 : EndIf ^^^^--- PB denkt, dies wäre ein Variablenname
In Toy-C können wir das direkt in Source-Code eingeben:Unser Toy-C-Compiler glaubt in dieser Situation hingegen einfach, dass z.B. ein "or" ein Variablenname ist:Code: Alles auswählen
X<2 and or X>=3
Das können wir so lassen und damit leben oder auch nicht.Code: Alles auswählen
push Global X push Const 2 lt push Global OR <=== ValueFactor soll kommen, na dann: OR = Variable! FALSCH! and push Global X push Const 3 ge
9.6. Zusammenfassung
Wir haben nun einen mathematischen Parser, der folgende Spezifikationen aufweist:
- Klammerausdrücke ( ... )
- Unary Operationen: + - not
- Korrekte Präzedenzbehandlung unserer Wahl (siehe Präzedenztabelle) aller Operatoren
- korrekte Rechstassoziativität des Potenz-Operators ^
- mathematische Ausdrück erlauben gemischte Operationen (mixed Operations) aller Art
- einfachste Fehlererkennung
Vielleicht baue ich sie ein, vielleicht können das die Leser aber auch schon selbst mittlerweile, denn das ist flott erledigt.
Interessant ist, dass gemischte z.B. boolesche und nicht boolesche Operatoren, also das gleichzeitige Auftauchen von z.B. "and" oder "or" und auch z.B. "*" oder "-" uvm. in einem mathematischen Ausdruck problemlos möglich ist.
Pure Basic verbietet das, und ich glaube, dass man das sogar absichtlich mit Mehraufwand verbieten muss, denn an sich, wie man an unserem Parser sieht, ist das von Haus aus erlaubt:
Ein Beispiel:
Wenn wir folgendes Programm in Pure Basic starten, erhalten wir eine Fehlermeldung (zum Verständnis unbedingt starten und durchlesen):
Code: Alles auswählen
Debug 5+(3<2) ; Fehler! Das geht in PB nur innerhalb von If, While, Until und Bool()
;
; * Das müsste man in PB so umschreiben
;
Debug 5+Bool(2<3) ; ergibt 6, weil 2<3=#True (also 1)
Debug 5+Bool(3<2) ; ergibt 5, weil 3<2=#False (also 0)
;
; * Toy-C braucht Bool() nicht, da geht das einfach so
;
Code: Alles auswählen
5+(3<2) // wird kompiliert von Toy-C
Code: Alles auswählen
push Const 5
push Const 3
push Const 2
lt
add
Code: Alles auswählen
=======================================================================
Prozedur Operator Assoziativität Präzedenz
=======================================================================
ValueFactor() ( ... ) höchste
-----------------------------------------------------------------------
NegValueFactor() Negation L *-->
bzw. Unäres -|+
-----------------------------------------------------------------------
Exponentiation() ^ R <--*
-----------------------------------------------------------------------
MulTerm() * / % L *-->
-----------------------------------------------------------------------
AddExpression() + - L *-->
----------------------------------------------------------------------
Relation() < <= <> = >= > L *-->
-----------------------------------------------------------------------
LogNot() Unäres log. not R <--*
-----------------------------------------------------------------------
BoolAnd() and L *-->
-----------------------------------------------------------------------
BoolOrXor() or xor L *--> niedrigste
=======================================================================
9.7. Code des Compilers von Kapitel 9
- ---- weiter im Teil "Code" dieses Kapitels ----
Viel Spaß mit dem Compiler von Kapitel 9.
LG Puretom
*** ENDE KAPITEL 9: TEXT ***
DAS KANN MAN SCHON ERNSTER NEHMEN: BITTE UM BUGBERICHTE, FEEDBACK, LOB, ..., denn das war viel Arbeit
Re: [Tutorial] Compiler und Virtual Machine
*** KAPITEL 9: CODE ***
9.7. Code des Compilers von Kapitel 9
Abschließend präsentiere ich den Gesamtcode des Compilers von Kapitel 9, ich habe einige Doppelgleisigkeiten entfernt, wie z.B. die Default-Zweige in den Prozeduren des mathematischen Parsers, die nur zu Lernzwecken (wir haben die Prozeduren ja immer geklont) dort waren.
Außerdem habe ich ein wenig aufgeräumt.
Der Gesamtcode des Compilers von Kapitel 9:
Code: Alles auswählen
; XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
; X PARSER/COMPILER (Kapitel 9) X
; X mit eingebunden muss sein: X
; X 1) Module Scanner X
; X 2) Module Code X
; XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
; *******************************************************************************
; * Scanner *
; *******************************************************************************
DeclareModule Scanner
; -
; - Public Declarations ---------------------------------------------------------
; -
Global Look ; Look Ahead Character
Global Token ; Token-Typ als Zahl
Global Lexem.s ; Lexem = Token als String
; --- Start-Prozedur ---
Declare Start(file_name.s) ; file_name des Source-Files
; --- Look Ahead Character holen mit dieser Prozedur ---
Declare GetChar() ; holt nächsten Character von *Source_Code
; --- Token-Lexem-Paare holen mit diesen Prozeduren ---
Declare GetToken() ; holt nächstes Token-Lexem-Paar
Declare GetName() ; holt nächsten Name
Declare GetNum() ; holt nächste Num (Integer)
Declare GetString() ; holt nächsten String
Declare GetOther() ; holt nächstes Other-Zeichen
; --- Fehlermeldung ausgeben ---
Declare Error(error_message.s) ; zeigt Meldung und beendet Compiler
Declare Expected(expected_object.s) ; zeigt, was erwartet wurde
; und beendet Compiler
EndDeclareModule
Module Scanner
; -
; - Private Declarations --------------------------------------------------------
; -
Global Source_Position ; Zeichen-Position im *Source_Code
Global *Source_Code ; Source-Code Zeichen für Zeichen im Speicher
; --- Lade Source-File ---
Declare Load(file_name.s) ; lädt das ASCII-Source-File
; --- Is?() - Prozeduren ---
Declare IsName1(c) ; testet, ob Zeichen der Start eines Namens ist
Declare IsName(c) ; testet, ob Zeichen zu einem Namen gehört
Declare IsNum(c) ; testet, ob Zeichen zu einer Nummer gehört
Declare IsString(c) ; testet, ob Zeichen der Start eines Strings ist
Declare IsWhite(c) ; testet, ob Zeichen ein White-Character ist
; --- Skip - Prozeduren ---
Declare SkipWhite() ; überspringt jedes White-Zeichen
Declare SkipLineComment() ; überspringt ab Comment-Start bis Zeilenende
Declare SkipBlockComment() ; überspringt von Block-Start bis Block-Ende
; - Debug - Prozeduren (am Ende löschen) ----------------------------------------
; -
Procedure Debug_CharStream()
; um ersten Look in den Stream zu legen
GetChar()
; so lange, bis Look = 0-Byte
While ( Look<>0 )
Debug "'"+Chr(Look)+"' : "+Str(Look)
GetChar()
Wend
Debug Str(Look)+"-Byte beendet (außerhalb While-Schleife)"
EndProcedure
Procedure Debug_TokenLexemStream0()
; so lange, bis Token = 0-Byte
While ( Token<>0 )
Debug RSet(Str(Token),3," ")+" | '"+Chr(Token)+"' | "+Lexem
GetToken()
Wend
Debug "(außerhalb While-Schleife)"
EndProcedure
Procedure Debug_TokenLexemStream1()
; so lange, bis Token = 0-Byte
While ( Token<>0 )
If Token = #LF: Debug RSet(Str(Token),3," ")+" | 'LF' | "
ElseIf Token = #CR: Debug RSet(Str(Token),3," ")+" | 'CR' | "
Else
Debug RSet(Str(Token),3," ")+" | '"+Chr(Token)+"' | "+Lexem
EndIf
;
GetToken()
Wend
If Token=0:Debug "'0'-Token beendet (außerhalb While-Schleife)":EndIf
EndProcedure
; - Start - Prozedur ------------------------------------------------------------
; -
Procedure Start(file_name.s)
; laden des Source-Files
Load(file_name.s)
; auf 1. Zeichen stellen
Source_Position = 0
; das erste aktuelle Token-Lexem-Paar holen
GetChar() ; 1. Zeichen in Zeichen-Strom
SkipWhite() ; alle White bis zum 1. gültigen Look
GetToken() ; anhand dieses Look erstes Token-Lexem-Paar holen
; --> ab hier ist alles zum Parser-Start vorbereitet
; --> ein gültiges Token-Lexem-Paar liegt bereit
; --> der Parser kann übernehmen und weitermachen
;Debug_CharStream() ; nur zu Debug-Zwecken
;Debug_TokenLexemStream0() ; nur zu Debug-Zwecken
;Debug_TokenLexemStream1() ; nur zu Debug-Zwecken
EndProcedure
; - Lade Source-File ------------------------------------------------------------
; -
Procedure Load(file_name.s)
ReadFile(0,file_name)
size = Lof(0)
*Source_Code = AllocateMemory(size+1) ; damit auch sicher am Ende 0-Byte ist
ReadData(0, *Source_Code, size)
CloseFile(0)
EndProcedure
; - Is?() - Prozeduren ----------------------------------------------------------
; -
Procedure IsName1(c)
If ((c>='a' And c<='z') Or (c>='A' And c<='Z'))
ProcedureReturn #True
EndIf
EndProcedure
Procedure IsName(c)
If (IsNum(c) Or IsName1(c) Or c='_')
ProcedureReturn #True
EndIf
EndProcedure
Procedure IsNum(c)
If (c>='0' And c<='9')
ProcedureReturn #True
EndIf
EndProcedure
Procedure IsString(c)
If c='"'
ProcedureReturn #True
EndIf
EndProcedure
Procedure IsWhite(c)
If (c=' ' Or c=#TAB Or c='/' Or c=#LF Or c=#CR Or c=':'Or c=';')
ProcedureReturn #True
EndIf
; Anmerkung: / startet einen Zeilenkommentar mit // (wie in C)
; / startet einen Blockkommentar mit /* (wie in C)
EndProcedure
; - Get - Prozeduren ------------------------------------------------------------
; -
Procedure GetChar()
Look = PeekA(*Source_Code+Source_Position) ; aktuelles Zeichen nach Look
Source_Position+1 ; auf nächstes Zeichen stellen
EndProcedure
Procedure GetToken()
; --> in Look ist das 1. Zeichen dieses Token-Lexems
; Entscheide, welcher Token-Typ vorliegt und verzweige entsprechend
If IsNum (Look): GetNum()
ElseIf IsName1 (Look): GetName()
ElseIf IsString(Look): GetString()
Else : GetOther()
EndIf
; ueberspringe alle White Characters und Comments (zur Sicherheit)
SkipWhite()
; --> in Look ist jetzt das 1. Zeichen des nächsten Token-Lexems
EndProcedure
Procedure GetName()
; --> in Look ist das 1. Zeichen dieses Token-Lexems
; 1. Zeichen korrekt fuer Name?
If Not IsName1(Look):Expected("Name"):EndIf
; Token-Typ zuordnen
Token = 'x'
; Lexem mit Name füllen
Lexem = ""
Repeat
Lexem = Lexem + Chr(Look)
GetChar()
Until Not IsName(Look)
; Name-Identifier sind nicht Case sensitiv
Lexem = UCase(Lexem)
; ueberspringe alle White Characters und Comments
SkipWhite()
; --> in Look ist jetzt das 1. Zeichen des nächsten Token-Lexems
EndProcedure
Procedure GetNum()
; --> in Look ist jetzt das 1. Zeichen dieses Token-Lexems
; 1. Zeichen korrekt fuer Num?
If Not IsNum(Look):Expected("Integer"):EndIf
; Token-Typ zuordnen
Token = '#'
; Lexem mit Name füllen
Lexem = ""
Repeat
Lexem = Lexem + Chr(Look)
GetChar()
Until Not IsNum(Look)
; ueberspringe alle White Characters und Comments
SkipWhite()
; --> in Look ist jetzt das 1. Zeichen des nächsten Token-Lexems
EndProcedure
Procedure GetString()
; --> in Look ist jetzt das 1. Zeichen dieses Token-Lexems
; 1. Zeichen korrekt fuer String?
If Not IsString(Look):Expected("String"):EndIf
; Token-Typ zuordnen
Token = '$'
; (") String-Start-Zeichen überspringen
GetChar()
; Lexem mit Name füllen
Lexem = ""
Repeat
Lexem = Lexem + Chr(Look)
GetChar()
Until IsString(Look)
; String-Ende-Zeichen überspringen (")
GetChar()
; ueberspringe alle White Characters und Comments
SkipWhite()
; --> in Look ist jetzt das 1. Zeichen des nächsten Token-Lexems
EndProcedure
Procedure GetOther()
; --> in Look ist jetzt das 1. Zeichen dieses Token-Lexems
; Token-Typ zuordnen (=genau dieses Zeichen)
Token = Look
; Lexem füllen (=genau dieses Zeichen)
Lexem = Chr(Look)
; nächstes Look holen
GetChar()
; ueberspringe alle White Characters und Comments
SkipWhite()
; --> in Look ist jetzt das 1. Zeichen des nächsten Token-Lexems
EndProcedure
; - Skip - Prozeduren -----------------------------------------------------------
; -
Procedure SkipWhite()
; solange in Look ein White
While IsWhite(Look)
; Zeilenkommentar (// ... #LF/0-Byte)
If Look='/' And PeekA(*Source_Code+Source_Position)='/'
SkipLineComment()
; Blockkommentar (/* ... */)
ElseIf Look='/' And PeekA(*Source_Code+Source_Position)='*'
SkipBlockComment()
; einfaches (/) als nicht-White im Stream belassen
ElseIf Look='/'
ProcedureReturn
; sonstige White-Zeichen überspringen
Else
GetChar()
EndIf
Wend
EndProcedure
Procedure SkipLineComment()
; bis Zeilenende oder Ende des Source-Files (0-Byte)
While ( Look<>#LF And Look<>#CR And Look<>0 )
GetChar()
Wend
; --> Look steht auf #LF oder #CR oder 0-Byte
; --> v.a. beim 0-Byte ist wichtig, dass es als
; --> Token weitergegeben wird, was beim nächsten
; --> GetToken() auch passiert, weil Look ja
; --> auf dem 0-Byte oder #LF oder #CR steht
EndProcedure
Procedure SkipBlockComment()
; (/) überspringen
GetChar()
; solange bis (*/)
Repeat
; Zeichen holen, bei Ersteintritt (*) überspringen
GetChar()
; verschachtelte Block-Kommentare ermöglichen
If Look='/' And PeekA(*Source_Code+Source_Position)='*'
SkipBlockComment()
EndIf
; auf 0-Byte achten
If Look=0: ProcedureReturn: EndIf
Until Look='*' And PeekA(*Source_Code+Source_Position)='/'
; (*/) überspringen
GetChar()
GetChar()
; --> Look steht auf 1. Zeichen nach (*/)
EndProcedure
; - Error - Prozeduren -----------------------------------------------------------
; -
Procedure Error(error_message.s)
MessageRequester("Fehler",error_message)
End
EndProcedure
Procedure Expected(expected_object.s)
Error(expected_object+" wird erwartet.")
EndProcedure
EndModule
; *******************************************************************************
; * Code *
; *******************************************************************************
DeclareModule Code
; -
; - Public Declarations ---------------------------------------------------------
; -
Global LabelNr ; Nr. des nächsten Labels
; --- Start-Prozedur ---
Declare Start() ; Vor Code-Modul-Benutzung aufrufen
; --- Stack pushes & pops ---
Declare PushConst(const_value.s) ; pusht Konstante auf den Stack
Declare PushGlobal(var_name.s) ; pusht Wert einer globalen Variable
; --- Mathematischer Parser ---
Declare _Neg() ; mathematische Operationen
Declare _Exp()
Declare _Mul()
Declare _Div()
Declare _Mod()
Declare _Add()
Declare _Sub()
Declare _LowerThan()
Declare _LowerOrEqual()
Declare _GreaterThan()
Declare _GreaterOrEqual()
Declare _Equal()
Declare _NotEqual()
Declare _LogNot()
Declare _And()
Declare _Or()
Declare _Xor()
; --- Sprünge ---
Declare JumpIfFalse(label_text.s,label_nr) ; Sprung bei False (= 0)
; --- Label-Verwaltung ---
Declare Label(label_text.s,label_nr) ; gibt Label aus
Declare GetLabel() ; gibt aktuelle Labelnummer zurück
; --- Emit-Hilfsprozeduren ---
Declare EmitS(text.s) ; mit linken Leerzeichen (S=Space)
Declare Emit(text.s) ; ohne Leerzeichen
EndDeclareModule
Module Code
; -
; - Private Declarations --------------------------------------------------------
; -
; - Start-Prozedur --------------------------------------------------------------
; -
Procedure Start()
; Initialisationen
LabelNr=0
EndProcedure
; - Stack pushes & pops ---------------------------------------------------------
; -
Procedure PushConst(const_value.s)
EmitS("push Const "+const_value)
EndProcedure
Procedure PushGlobal(var_name.s)
EmitS("push Global "+var_name)
EndProcedure
; - Mathematischer Parser -------------------------------------------------------
; -
Procedure _Neg()
EmitS("neg") ; "-": Negation, unary +|-
EndProcedure
Procedure _Exp()
EmitS("exp") ; "%": Mod
EndProcedure
Procedure _Mul()
EmitS("mul") ; "*": Multiplikation
EndProcedure
Procedure _Div()
EmitS("div") ; "/": Division
EndProcedure
Procedure _Mod()
EmitS("mod") ; "%": Mod
EndProcedure
Procedure _Add()
EmitS("add") ; "+": Addition
EndProcedure
Procedure _Sub()
EmitS("sub") ; "-": Subtraktion
EndProcedure
Procedure _LowerThan()
EmitS("lt") ; "<": Lower than -> kleiner als
EndProcedure
Procedure _LowerOrEqual()
EmitS("le") ; "<=": Lower or equal -> kleiner oder gleich
EndProcedure
Procedure _GreaterThan()
EmitS("gt") ; ">": Greater Than -> größer als
EndProcedure
Procedure _GreaterOrEqual()
EmitS("ge") ; ">=": Greater Or equal -> größer oder gleich
EndProcedure
Procedure _Equal()
EmitS("eq") ; "=": Equal -> gleich
EndProcedure
Procedure _NotEqual()
EmitS("ne") ; "<>": Not equal -> nicht gleich, ungleich
EndProcedure
Procedure _LogNot()
EmitS("not") ; "not": unary log. Not
EndProcedure
Procedure _And()
EmitS("and") ; "and": log. And
EndProcedure
Procedure _Or()
EmitS("or") ; "or": log. Or
EndProcedure
Procedure _Xor()
EmitS("xor") ; "xor": log. Xor
EndProcedure
; - Sprünge ---------------------------------------------------------------------
; -
Procedure JumpIfFalse(label_text.s,label_nr)
EmitS("JF "+UCase(label_text+Str(label_nr)))
EndProcedure
; - Label-Verwaltung ------------------------------------------------------------
; -
Procedure Label(label_text.s,label_nr)
Emit("["+UCase(label_text+Str(label_nr))+"]")
EndProcedure
Procedure GetLabel()
LabelNr+1
ProcedureReturn LabelNr
EndProcedure
; - Emit-Hilfsprozeduren --------------------------------------------------------
; -
Procedure EmitS(text.s)
WriteString(1,Space(8)+text+#CRLF$)
EndProcedure
Procedure Emit(text.s)
WriteString(1,text+#CRLF$)
EndProcedure
EndModule
; *******************************************************************************
; * Parser (Compiler) *
; *******************************************************************************
DeclareModule Parser
; -
; - Public Declarations ---------------------------------------------------------
; -
; --- Start-Prozedur ---
Declare Compile(file_name.s) ; Compiliert ein Source-File und erzeugt eine
; Text-Datei in Toy-C-Assembler mit dem Namen
; "Source-File-Name.ta" .ta = Toy-Assembler
EndDeclareModule
Module Parser
; -
; - Private Declarations --------------------------------------------------------
; -
; --- Debug-Sachen ---
Global Debug_BlockEbene ; Debug-Zwecke, später löschen
; --- Programmstruktur ---
Declare Statement() ; erkennt eine Anweisung --> Do()-Prozedur
Declare Block() ; behandelt Block von Statements
; --- Mathematischer Parser ---
Declare ValueFactor() ; holt einen Operanden-Wert, ( ... )
Declare NegValueFactor() ; unary -|+
Declare Exponentiation() ; Potenz: Hoch-Operator: ^ (*rechts assoziativ*)
Declare MulTerm() ; bearbeitet Mul-Ops: *, /, %
Declare AddExpression() ; bearbeitet Add-Ops: +, -
Declare Relation() ; logische Vergleiche <, <=, <>, =, >=, >
Declare LogNot() ; unary logisches Not
Declare BoolAnd() ; boolesches And
Declare BoolOrXor() ; boolesches Or, Xor
Declare Expression() ; mathematischer Expression Parser 3+4*(4+3)
; --- Hilfsprozeduren ---
Declare SkipToken(token) ; prüft, ob erwartetes Token
; Nein? -> Fehler, Ja? -> GetToken()
; - Start, Statement, Block -----------------------------------------------------
; -
Procedure Compile(file_name.s)
; Open .ta-File
CreateFile(1,GetFilePart(file_name, #PB_FileSystem_NoExtension)+".ta")
; Inits
Debug_BlockEbene = 0
; Starte Code-Modul
Code::Start()
; Starte Scanner-Modul (1. Token-Lexem liegt danach im Stream)
Scanner::Start(file_name.s)
; so lange, bis Token = 0-Byte
While ( Scanner::Token<>0 )
Statement()
Wend
Debug "(außerhalb While-Schleife)"
; Close .ta-File
CloseFile(1)
EndProcedure
Procedure Statement()
; nur für Debugzwecke (später löschen)
Debug RSet(Str(Scanner::Token),3," ")+" | '"+Chr(Scanner::Token)+"' | "+Space(4*Debug_BlockEbene)+Scanner::Lexem
; nach Statements/Befehlen suchen
Select Scanner::Lexem
Case "{" : Block()
; ...
Default : Expression()
EndSelect
EndProcedure
Procedure Block()
; Debug-Zwecke, später löschen
Debug_BlockEbene+1
; ------------------------------------------------------
; --> in Token/Lexem ist jetzt ({)
; ueberlesen des ({)
Scanner::GetToken()
; solange kein (}) oder Source-Code-Ende
While ( Scanner::Token<>'}' And Scanner::Token<>0 )
Statement()
Wend
; ueberlesen des (})
; Kein (})? Fehler, Block nicht geschlossen mit (})
SkipToken('}')
; --> in Token/Lexem ist jetzt das Token/Lexem nach (})
; ------------------------------------------------------
; Debug-Zwecke, später löschen
Debug_BlockEbene-1
EndProcedure
; - Mathematischer Parser -------------------------------------------------------
; -
Procedure ValueFactor()
; --> Token/Lexem steht auf Value ODER
; --> auf unary + | unary -
; je Token-Art: Ausgabe des Values als ASM-Code
Select Scanner::Token
Case '#' : Code::PushConst(Scanner::Lexem)
Case 'x' : If Scanner::Lexem="AND" Or Scanner::Lexem="OR" Or ; Teste, ob Lexem ein reservierter Operator-Name
Scanner::Lexem="XOR" Or Scanner::Lexem="NOT"
Scanner::Error("Ein Variablen- oder Prozedurname darf nicht wie ein Operator heißen: "+Scanner::Lexem+".")
EndIf
;
Code::PushGlobal(Scanner::Lexem)
Case '(' : Scanner::GetToken() ; überspringe "("
Expression() ; ")" wird weiter unten übersprungen
Default : Scanner::Expected("Korrekter Operand (Konstante Zahl, Variablen- oder Prozedurname)")
EndSelect
; holt nächstes Token (=Operator)
; überspringe eventuell ")"
Scanner::GetToken()
; --> in Token/Lexem ist Token/Lexem nach Value
; --> wenn die Expression weitergeht, ist das ein Operator
; --> Abstieg zu NegValueFactor()
EndProcedure
Procedure NegValueFactor()
; --> Token/Lexem steht auf Value ODER
; --> auf unary + | unary -
; Ist ein 'unary -' vor ValueFactor?
If Scanner::Token='-'
Scanner::GetToken()
ValueFactor()
Code::_Neg()
; Ist ein 'unary +' vor ValueFactor?
ElseIf Scanner::Token='+'
Scanner::GetToken()
ValueFactor()
; 'normaler' ValueFactor
Else
; Aufstieg zu ValueFactor()
ValueFactor()
EndIf
; --> in Token/Lexem ist Token/Lexem nach Value
; --> wenn die Expression weitergeht, ist das ein Operator
; --> Abstieg zu Exponentiation()
EndProcedure
Procedure Exponentiation_LA() ; *** falsch: links_assoziativ ***
; kann entfernt werden, wenn nicht benötigt
; --> Token/Lexem steht auf Value
; Aufstieg zu NegValueFactor()
NegValueFactor()
; Power-Op in Token/Lexem?
While Scanner::Token='^'
; vor Aufstieg nächstes Token (=Value) holen
Scanner::GetToken()
; Aufstieg zu NegValueFactor()
; ====> FALSCH! NUR BEI SELBSTAUFRUF RICHTIG RECHTSASSOZIATIV !!! <====
; ====> siehe richtige Lösung weiter unten
NegValueFactor()
; Ausgabe des ASM-Codes des Operators
Code::_Exp()
Wend
; --> in Token/Lexem ist Token/Lexem nach dem Operator ODER
; --> in Token/Lexem ist Token/Lexem nach dem Value
; --> Abstieg zu MulTerm
EndProcedure
Procedure Exponentiation() ; *** richtig: rechts_assoziativ ***
; --> Token/Lexem steht auf Value
; Aufstieg zu NegValueFactor()
NegValueFactor()
; Power-Op in Token/Lexem?
If Scanner::Token='^'
; vor Aufstieg nächstes Token (=Value) holen
Scanner::GetToken()
; Rechtsassoziativ !!! --> Selbstaufruf
; ====> BEI SELBSTAUFRUF RICHTIG RECHTSASSOZIATIV !!!
Exponentiation()
; Ausgabe des ASM-Codes des Operators
Code::_Exp()
EndIf
; --> in Token/Lexem ist Token/Lexem nach dem Operator ODER
; --> in Token/Lexem ist Token/Lexem nach dem Value
; --> Abstieg zu MulTerm()
EndProcedure
Procedure MulTerm()
; --> Token/Lexem steht auf Value
; Aufstieg zu Exponentiation()
Exponentiation()
; Mul-Op in Token/Lexem?
While Scanner::Token='*' Or Scanner::Token='/' Or Scanner::Token='%'
; Operator merken
operator = Scanner::Token
; vor Aufstieg nächstes Token (=Value) holen
Scanner::GetToken()
; Aufstieg zu Exponentiation()
Exponentiation()
; Ausgabe des ASM-Codes des Operators
; mit gemerktem Operator
Select operator
Case '*' : Code::_Mul()
Case '/' : Code::_Div()
Case '%' : Code::_Mod()
EndSelect
Wend
; --> in Token/Lexem ist Token/Lexem nach dem Operator ODER
; --> in Token/Lexem ist Token/Lexem nach dem Value
; --> Abstieg zu AddExpression()
EndProcedure
Procedure AddExpression()
; --> Token/Lexem steht auf Value
; Aufstieg zu MulTerm()
MulTerm()
; Add-Op in Token/Lexem?
While Scanner::Token='+' Or Scanner::Token='-'
; Operator merken
operator = Scanner::Token
; vor Aufstieg nächstes Token (=Value) holen
Scanner::GetToken()
; Aufstieg zu MulTerm()
MulTerm()
; Ausgabe des ASM-Codes des Operators
; mit gemerktem Operator
Select operator
Case '+' : Code::_Add()
Case '-' : Code::_Sub()
EndSelect
Wend
; --> in Token/Lexem ist Token/Lexem nach dem Operator ODER
; --> in Token/Lexem ist Token/Lexem nach dem Value
; --> Abstieg zu Relation()
EndProcedure
Procedure Relation()
; --> Token/Lexem steht auf Value
; Aufstieg zu AddExpression()
AddExpression()
; Relational-Op in Token/Lexem?
While Scanner::Token='<' Or Scanner::Token='>' Or Scanner::Token='='
; Operator merken, mehrteilige Operatoren erkennen
If Scanner::Token='<' And Scanner::Look='='
operator='k' ; <k>leiner gleich <=
Scanner::GetToken()
ElseIf Scanner::Token='>' And Scanner::Look='='
operator='g' ; <g>rößer gleich >=
Scanner::GetToken()
ElseIf Scanner::Token='<' And Scanner::Look='>'
operator='u' ; <u>ngleich <>
Scanner::GetToken()
Else
operator = Scanner::Token
EndIf
; vor Aufstieg nächstes Token (=Value) holen
Scanner::GetToken()
; Aufstieg zu AddExpression()
AddExpression()
; Ausgabe des ASM-Codes des Operators
; mit gemerktem Operator
Select operator
Case '<' : Code::_LowerThan() ; <
Case 'k' : Code::_LowerOrEqual() ; <=
Case 'u' : Code::_NotEqual() ; <>
Case '=' : Code::_Equal() ; =
Case 'g' : Code::_GreaterOrEqual() ; >=
Case '>' : Code::_GreaterThan() ; >
EndSelect
Wend
; --> in Token/Lexem ist Token/Lexem nach dem Operator ODER
; --> in Token/Lexem ist Token/Lexem nach dem Value
; --> Abstieg zu LogNot()
EndProcedure
Procedure LogNot()
; --> Token/Lexem steht auf Value ODER
; --> auf unary not
; Ist ein 'unary not' vor ValueFactor?
If Scanner::Lexem = "NOT"
; vor Aufstieg nächstes Token holen
; d.i 'not' ODER ein Value
Scanner::GetToken()
; Selbstaufruf für 'not not not ...'
LogNot()
; Ausgabe des ASM-Codes des Operators
Code::_LogNot()
Else
; Aufstieg zu Relation()
Relation()
EndIf
; --> in Token/Lexem ist Token/Lexem nach Value
; --> wenn die Expression weitergeht, ist das ein Operator
; --> Abstieg zu BoolAnd()
EndProcedure
Procedure BoolAnd()
; --> Token/Lexem steht auf Value
; Aufstieg zu LogNot()
LogNot()
; BoolAnd-Op in Token/Lexem?
While Scanner::Lexem ="AND"
; vor Aufstieg nächstes Token (=Value) holen
Scanner::GetToken()
; Aufstieg zu LogNot()
LogNot()
; Ausgabe des ASM-Codes des Operators
Code::_And()
Wend
; --> in Token/Lexem ist Token/Lexem nach dem Operator ODER
; --> in Token/Lexem ist Token/Lexem nach dem Value
; --> Abstieg zu BoolOrXor()()
EndProcedure
Procedure BoolOrXor()
; --> Einsprung von Statement()
; --> Token/Lexem steht auf Value
; Aufstieg zu BoolAnd()
BoolAnd()
; BoolOrXor()Xor()-Op in Token/Lexem?
While Scanner::Lexem ="OR" Or Scanner::Lexem = "XOR"
; Operator merken
operator.s = Scanner::Lexem
; vor Aufstieg nächstes Token (=Value) holen
Scanner::GetToken()
; Aufstieg zu BoolAnd()
BoolAnd()
; Ausgabe des ASM-Codes des Operators
Select operator
Case "OR" : Code::_Or()
Case "XOR" : Code::_Xor()
EndSelect
Wend
; --> in Token/Lexem ist Token/Lexem nach dem Operator ODER
; --> in Token/Lexem ist Token/Lexem nach dem Value
; --> Ende der Expression (Rücksprung zu Statement() über Expression())
EndProcedure
Procedure Expression()
BoolOrXor()
EndProcedure
; - Hilfsprozeduren ------------------------------------------------------------
; -
Procedure SkipToken(token)
If Scanner::Token<>token
Scanner::Expected("'"+Chr(token)+"'")
Else
Scanner::GetToken()
EndIf
EndProcedure
EndModule
Parser::Compile("Source-Code.tc")
LG Puretom
*** ENDE KAPITEL 9: CODE ***
DAS KANN MAN SCHON ERNSTER NEHMEN: BITTE UM BUGBERICHTE, FEEDBACK, LOB, ..., denn das war viel Arbeit
Re: [Tutorial] Compiler und Virtual Machine
Ein nahezu vollständiger Matheparser steht zur Verfügung!
Bitte um Rückmeldungen!
LG Puretom
Re: [Tutorial] Compiler und Virtual Machine
ÄNDERUNGEN STEHE BEVOR!!!
NICHT VERGESSEN: VARIABLE NICHT DEKLARIER __ I N N E R H A L B ___ EINER EXPRESSION
*** ENDE KAPITEL 10: TEXT ***
Re: [Tutorial] Compiler und Virtual Machine
Re: [Tutorial] Compiler und Virtual Machine
Re: [Tutorial] Compiler und Virtual Machine
Danke für das Feedback!
@all
Ich werde ein wenig umbauen.
Vor allem möchte ich schon viel früher ein funktionierende kleine Skriptsprache Toy-C 0.8 (mit if und while) präsentieren können.
Ich habe nämlich selbst das Gefühl, dass schon zu viele Teile da sind, ohne dass man wirkliche Ergebnisse sieht.
Für die Interessierten:
Keine Angst, die jetzigen Teile sollen bleiben, aber NACH der ersten kleinen Sprache, damit man mal "Ergebnisse sieht". Die jetzigen Teile stellen dann die Erweiterung zu ToyC-0.9 oder was auch immer dar - sozusagen.
Ich hoffe, es interessiert sich noch irgendjemand dafür (SAGT MAL WAS!!!!)!
Ist viel Arbeit, macht aber Spaß, und vor allem ich lerne selbst viel dabei.
LG Puretom
P.S. In 3 Wochen habe ich Urlaub, da mache ich vielleicht wieder etwas mehr. Weiß noch nicht.