Oh ja! Ganz tolle Idee, einfach den Look beinhart austauschen an der Quelle und ein anderes Ei ins Nest legen, ein Kuckucksei ebenJa, 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 ebenJa, 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>=3Das 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.