Posts by M. J.

    Es war wahrscheinlich ein Quest-Spiel.

    Was genau verstehst Du unter einem "Quest-Spiel"? Meinst Du damit ein Spiel wie "King's Quest", "Space Quest" etc von Sierra Online? Die wurden (bislang) nicht für den C64 umgesetzt. Wenn Du jedoch schreibst "so wie Giana Sisters" klingt für mich das eher wie ein Actionspiel oder Jump'n'Run als ein Adventure oder Rollenspiel.

    Bei Haus mit Kellergewölbe und Stuhl muß ich an den ersten Level von Please login to see this link. denken, bei "Geschoßebene" und "sittzende Frau" sowie "der Spieler kam von links, die Frau war rechts" jedoch auch an den dritten Level von Please login to see this link. (Screenshot Please login to see this link.). Wird's wohl beides nicht sein, aber vielleicht fällt Dir noch ein Hinweis ein.

    [Teil 2]

    Die Routine bei $0d70 verändert nicht die Koordinate des Aliens, sondern setzt nur den Wert für Status B (vermutlich die Shapenummer) anhand des Bewegungsindikators. Außerdem wird getestet, ob der neue, folgende Indikator in der Bewegungsliste 0 ist. Falls ja, bedeutet dies, dass das Ende der Liste erreicht wurde, woraufhin der Listenzeiger zurückgesetzt wird auf einen Startwert, der in M4394 und M4395 gespeichert ist.

    Zur Berechnung, welches Shape angezeigt werden soll, werden die X- und Y-Werte herangezogen. Zusätzlich wird der Bewegungsindikator als Index genommen in eine Tabelle mit jeweils drei Elementen.

    Code
                           LD      L,A            ;    Multipliziere A mit 3
                           RLCA
                           ADD     A,L            ;    A := A * 2 + A
    
                           ADD     $A0            ;    $a0 = Lowbyte von Tabellenadresse $16a0
                           LD      L,A                 
                           LD      H,T1600 >> 8   ;    Index * 3 + $16a0 ==> HL = Zeiger auf Tabelleneintrag

    Bitte beachte, dass die ersten drei Werte in der Tabelle ($ff, $ff, $ff) wieder Dummywerte sind.

    Der erste Wert der Tabelle wird in die untersten drei Bits des Status A übertragen. Kann sein, dass hier sowas angegeben wird wie die Bewegungsgeschwindigkeit.

    Der zweite Wert gibt an, auf welche Weise das Shape berechnet werden soll (anhand von X- oder Y-Koordinate...) mit Hilfe des dritten Wertes (Shapekachel-Basiswert).

    Eine grobe Portierung sähe in etwa so aus:

    Die Bewegungslisten stehen ab T1000 im Speicher. Du kannst sie ja mal in Dein Programm einbauen und ein Alien danach bewegen lassen um zu schauen, ob es sich dabei um diese Loops handelt. Ansonsten wäre halt noch sinnvoll nachzugucken, wann und unter welchen Bedingungen die Bewegungslisten gesetzt und gestartet werden.


    Nebenbei: Könnte es sein, dass sich in der Routine AlienDataController bei $a50 ein Bug befindet? Die Routine verwendet zwei Zeiger in BC und DE. Der erste zeigt auf die Alien data structure bei $4b70, der zweite auf die Alien data structure (screen ram) bei $4bb0. Beide haben 16 Einträge * 4 Bytes und belegen den Speicher bis $4bef ($4bb0 + $40 - 1). Die Routine jedoch erhöht den Zeiger auf das nächste Element (+4) solange, bis das Lowbyte des Zeigers (auf $4bb0...) überläuft und auf 0 (entsprechend $4b00) zeigt. Das bedeutet, dass vier Einträge zuviel bearbeitet werden, deren Inhalte bei $4bb0..$4bbf und $4bf0..$4bff liegen sollen. Anstelle eines "AND A" als Vergleich hätte in der Routine ein "CP $f0" stehen müssen, oder sehe ich das falsch?

    [Teil 1]

    Okaayyyy.... Mal sehen...

    Also die Routine bei $d1c holt sich zuerst einen Zeiger auf eine Bewegungsliste und dann aus der Bewegungsliste ein Bewegungsindikator. Dieser gibt an, in welche Richtung sich das Alien bewegen soll. Hierzu wird die Tabelle T1700 benötigt. Die ersten beiden Werte ($ff, $ff) sind Dummywerte, denn ein Bewegungsindikator mit dem Wert 0 bedeutet "Ende der Liste". Der erste richtige Eintrag (Indikator = 1) bedeutet folglich

    Code
                .db    $01    ;    fliege um ein Pixel nach rechts
                .db    $00    ;    y bleibt konstant

    Der nächste

    Code
                .db    $ff    ;    fliege um ein Pixel nach links
                .db    $00    ;    y bleibt konstant

    Die folgenden Tabellenwerte geben weitere Bewegungsgeschwindigkeiten in diverse Richtungen an.

    Die Bewegungsrichtungen für X und Y werden nun geholt und zu den X- und Y-Werten des Aliens hinzuaddiert. Dabei wird getestet, ob die Koordinate einen Kachelanfang erreicht hat. Hierzu wird der Wert einfach mit 7 maskiert (entsprechend Wert MOD 8). Eine 0 als Ergebnis bedeutet Kachelanfang.

    Wird das Alien in Y-Richtung bewegt, findet der Test mit dem Y-Wert statt. Wird das Alien nicht in Y-Richtung bewegt, wird der Test mit dem X-Wert vorgenommen.

    Wurde ein Kachelanfang erreicht, wird der Zeiger auf die Bewegungsliste um eins erhöht. Das Alien fliegt somit pro Bewegungsindikator (ein Eintrag in der Bewegungsliste) immer von einer Kachel zu nächsten Kachel.

    Zu Beginn wird der Bewegungszeiger auf die Tabelle T1000 gesetzt mit den Werten:

    Code
    T1000:
                           .DB $01, $01, $01, $01, $02, $02, $02, $02
                           .DB $02, $02, $02, $02, $01, $01, $01, $01
                           .DB $00

    Wie oben beschrieben bedeutet der Wert 1 eine Bewegung um ein Pixel nach rechts bis zum Kachelanfang der nächsten Kachel. Der Wert 2 bedeutet eine Bewegung nach links bis zum Kachelanfang der vorherigen Kachel. Der Wert 0 kennzeichnet das Ende der Liste.

    Das kleine Bewegungsprogramm bei T1000 macht daher nichts anderes, als das Alien im Schritttempo 1 um vier Kacheln nach rechts zu bewegen, dann um acht Kacheln nach links und anschließend wieder um vier Kacheln nach rechts. Ich gehe davon aus, daß es sich hierbei um die Flugbewegung in der Formation handelt.

    Bei der Portierung nach 6502-Code würde man die Tabelle T1700 aufspalten in X- und Y-Werte. Eine 6502-Übersetzung könnte in etwa so aussehen:

    Bin dabei, mir den Code anzusehen.

    Eine kleine Rückfrage: Die Koordinaten der Aliens geben Pixelkoordinaten wieder und keine Tilekoordinaten, richtig? Ein Tile ist jeweils 8 Pixel breit und hoch? Ich frage, weil sich im Programm ab L0d4f (vermutlich) zwei Abfragen befinden, ob der X- oder Y-Wert einer Kachelgrenze entspricht (AND $7). Falls ja, wird der Zeiger um eins erhöht (INC (HL)).

    Hier mal der Source code.

    Danke.

    Kannst du dich noch erinnern?

    Aber selbstverständlich. ^^ Die Daten habe ich alle noch.

    Die Implementierung der 'Closed loops movement pattern' der Aliens im Arcade game Phoenix.

    Kannst Du vielleicht sagen, an welcher Stelle ungefähr im Code die Routine zu finden ist? Und was genau bräuchtest Du (Flußdiagramm, Pseudocode, 6502-Code...)?

    Zur Zeit habe ich den Sourcecode (welcher nur das 8080 Subset verwendet) in Z80 Syntax) passend für den uz80as.

    Wie lang ist der Code? Nomalerweise wäre eine Codeanalyse und dann ein direktes Neuschreiben in 6502-Assembler die beste und schnellste Methode. 8080-Code ist an sich nicht schwer zu lesen (mit Sourcecode und Kommentaren noch leichter), hat aber einige Besonderheiten (neben den erwähnten Registern auch z. B. Befehle wie RET Z), aufgrund derer sich die Programmstruktur nicht gut 1:1 auf den 6502 abbilden läßt. Man kommt daher ohnehin nicht darum herum, den verwendeten Algorithmus und den Programmaufbau zu verstehen, wenn man eine gute Portierung vornehmen möchte.

    Von einer automatischen Übersetzung würde ich abraten, da zum einen der erzeugte Code zigmal länger als das Original und damit sehr schlecht lesbar wird (schlechter als den Originalcode zu lesen), zum anderen aber auch sehr schnell Fehler bei der Übersetzung auftreten können, sofern der Übersetzer die Befehle nur einzeln übersetzt und nicht den Zustand der Flaggen bei Ausführung des Programms berücksichtigt. Bekanntlich werden die Flaggen von 8080-Befehlen anders verwendet als beim 6502. Dazu gehören u. a. die Subtraktion und die Ladebefehle. So kann z. B. beim 8080 ein Programm vor der Abfrage eines Flaggenzustands in aller Ruhe einen Wert in ein Register laden, beim 6502 jedoch nicht, da bei diesem das Laden das N- und Z-Flag verändert. Wird sowas nicht berücksichtigt, ist der erzeugte Code höchstwahrscheinlich falsch. Um alle Fehler in Zusammenhang mit den Flaggen zu verhindern, müßten die Flaggen des 8080 (auch P und H) exakt nachgebildet werden mit Hilfe von eigenen Variablen und 6502-Befehlen, die diese dann einzeln setzen oder löschen. Dann hat man jedoch nichts anderes als einen unrolled-8080-Interpreter und einen Code, der ruckzuck die 64 kb-Grenze des 6502 sprengt.

    Wenn also der Originalcode nicht allzulang ist (< 8 kb), würde ich eine händische Übersetzung empfehlen. Bei längerem Code müßte man halt schauen, wie kompliziert dieser angelegt ist.

    "Elite" hat "simple" Vektorgrafik. Aus heutiger Sicht sicherlich "mies". Gilt auch für andere 3d-Spiele wie "Mercenary" oder "Cholo". Bei Adventures kann man über Grafikgeschmack auch schön streiten. Die Bilderkonvertierungen auf den C64 von "Transsylvania" und Co waren damals schon nicht sonderlich gelungen, obwohl noch immer besser als die kleinen HiRes-Bilder von Level-9 oder Scott Adams. Und wie sieht es aus mit den PETSCII-Grafiken von "Zauberschloss" und anderen Basic-Adventures? Hmm... Und gelten die Grafiken von "Ultima" oder "Wasteland" eigentlich als "gut"?

    (verspätete Antwort... ;( )

    Viele Programme sind aber in Basic entstanden, weil die Leute keine Alternativen hatten (sich keine anderen Sprachen leisten wollten, sie damals noch nicht nicht gelernt hatten, keinen Overhead wollten wegen geringer Komplexität etc) und die haben tatsächlich in solchen Fällen z.B. die Integer-Arithmetik "gemeint" (wobei es auch andere solche Fälle "gemeinter" Semantik geben mag), und haben Basic V2 mit seinen diesbezüglichen Mangelerscheinungen eigentlich nur in Kauf genommen. Wenn die einen Compiler kriegen, mit dem ihre Programme schnell compiliert werden können, und dann tatsächlich in solchen Fällen auch viel schneller werden, und genau das tun, was die damals "gemeint" haben, ist das kein Problem, sondern eine Lösung. Es kommt halt immer auf das Motiv an.

    Da hast Du völlig Recht. Es dürfte zahlreiche Basic-Programme geben, die von den Fließkommazahlen keinen Gebrauch gemacht haben. So kenne ich da einen komischen Typen (seinen Namen will ich nicht nennen, aber er schreibt hier im Forum gelegentlich so merkwürdige Posts), der hat damals[tm] ein Adventure in Basic geschrieben, und ich kann Dir versichern, daß ich im ganzen Programm garantiert keinen einzigen Fließkommawert verwendet habe. Zur Probe hatte ich das Adventure mal mit dem Austrocompiler übersetzt, was gefühlt Jahre brauchte, und das Ergebnis war tatsächlich ein wenig schneller, wenn auch nicht so viel wie erhofft. Grund u. a.: weiterhin langsame und unnötige Fließkommaberechnungen. Von daher kann ich nur zustimmen, daß es sicherlich Bedarf gab nach z. B. reinen Integer-Compilern.

    Ich habe daher auch kein Problem mit diversen spezialisierten Compilern. Ich habe ein Problem damit, daß so getan wird, als würden sie Basic V2 übersetzen. Meines Erachtens wäre es sinnvoller gewesen, gleich offen ein Integer-Basic anzubieten so wie das erste Basic von Wozniak auf dem AppleII. Da wäre bereits die Interpreter-Ausführung während der Entwicklung schneller gewesen. Dazu kommt dann ein Compiler, der komplett auf Integer-Arithmetik aufbaut und entsprechend schnellen Code erzeugt. Und am besten gibt es noch ein paar Strukturelemente und Grafikbefehle gratis dazu. Das wäre mal eine Möglichkeit gewesen, in Basic auch kleine Actionspiele auf dem C64 zu programmieren. Und vielleicht hat es sowas auf dem C64 auch tatsächlich gegeben (darüber weiß ich zu wenig), aber die mir bekannten Compiler bauen alle auf dem Basic V2-Rom auf und tun so, als wären alle diese Basic-Varianten irgendwie Basic V2, was nicht stimmt.

    Es ist leider so, wie Du schreibst: Die Leute hatten zu Basic V2 und Assembler auf dem C64 keine echte Alternative. "Pascal auf dem C64" fand ich z. B. ganz gut (hatte mir sogar das Buch gekauft). doch der Editor war m. M. n. zu kompliziert geraten und konnte nur kurze Textdateien editieren, was die Motivation, mal in Pascal ein Projekt zu entwickeln, arg dämpfte. Die anderen Pascal-Compiler kannte ich nicht. Was ich aber bisher von Peter so gesehen habe, war auch nicht sehr ermunternd. Da wäre ein echtes alternatives Basic sinnvoll gewesen, hat es aber anscheinend nicht gegeben, was ich im Nachhinein dann schade finde. (Im C64-Wiki finde ich nur eine Reihe von Basic V2-Erweiterungen, aber keinen vollständigen Ersatz, bin aber kein Basic-Experte. Falls jemand mehr weiß, bitte um Mitteilung.)

    die ganzen theoretischen Erklärungen haben auch andere Themen wie z.B. Entscheidbarkeit ((Wann) kann man mit einer Grammatik entscheiden, ob ein gegebenes Programm der Grammatik entspricht? Welche Grammatiken sind überhaupt entscheidbar? Und so weiter), wofür man das entsprechende theoretische Handwerkszeug braucht (und die zitierten "Formeln" nunmal die ersten Schritte davon sind)

    Du sagst es: andere Themen. Das Thema des Buches ist aber "Compilerbau" und nicht "Grammatiken".

    Stell Dir vor, Du kaufst ein Buch zum Thema "Porträtmalen", und die ersten hundert Seiten beschäftigen sich allein mit netten Theorien zu solchen Sachen wie Please login to see this link., Please login to see this link., Farbempfinden und Please login to see this link.. Letzteres auch gerne mit diversen mathematischen Umrechnungsverfahren. Alles Sachen, die irgendwie mit dem Malen zu tun haben, aber für das Porträtmalen nicht wirklich relevant sind. Würdest Du Dich da nicht verschaukelt vorkommen?

    Ich habe hier noch ein weiteres Compilerbuch als PDF vorliegen: "Basics of Compiler Design". Die ersten 100 von 300 Seiten widmen sich einzig der lexikalischen und der syntaktischen Analyse. Das Kapitel "machine code generation" hat dagegen mal gerade 10 Seiten. Es ist dieses Ungleichgewicht, was so fürchterlich nervt, besonders wenn man es in einer Vorlesung erlebt hat, die sich die ganze Zeit nur um den langweiligen trivialen Grammatikteil dreht, aber nie zum Wesentlichen vorstößt. Die ganzen Theorien zur Grammatik sind für einen üblichen Compiler einer üblichen Programmiersprache irrelevant. Da stehen ganz andere Themen im Vordergrund:

    - Genereller Aufbau des Compilers (Frontend/Backend?)

    - Gestaltung des Zwischencodes

    - Gestaltung der Objekttabelle

    - Identifikation von Bezeichnern

    - Analyse eines Ausdrucks, Operatorenpriorität, constant folding, implizite Casts...

    - Registerallokation

    - Register spilling (insbesondere bei Funktionsaufrufen)

    - Stackbehandlung

    - Wann wird welche Optiminierung durchgeführt?

    - Bei 1-Pass-Compilern: Patchen von Sprungadressen bei Vorwärtssprüngen

    - Abbilden von Zwischencode auf die Zielsprache

    usw. usf.

    Ja, Informatik heißt nicht Programmieren, sondern beschäftigt sich (frei nach Wirth) mit "Algorithmen und Datenstrukturen". Aber gerade davon gibt es beim Compiler ausreichend genug, die einer ausgiebigen Behandlung bedürfen. Es gibt hingegen keinen einzigen Grund, Papier und Zeit mit unwichtigen Theorien zu Grammatiken zu verschwenden.

    Eine gar nicht allzu schlechte (wenn auch sehr verkürzte) Einführung zum Thema Compiler (in dem Fall zu M-Code) hat die 64'er übrigens hier: Please login to see this link.

    Das ist eine gute Zusammenfassung von einigen Sachen, die hier bereits erwähnt wurden. Auf S. 39 unten rechts gibt es außerdem ein Beispiel für genau die Formel, die oobdoo in dem Buch gefunden hat. Zumindest sollte das Beispiel da stehen, aber irgendwie finde ich nur die erste Regel in der Backus-Naur-Form:

    1. <Satz> ::= <Subjekt> <Prädikat>

    entsprechend

    1. <S> ::= <A> <B>

    im Buch, aber die anderen beiden scheinen zu fehlen...? ?(

    Darf ich vorsichtig widersprechen?

    Nein, auf keinen Fall! :picard::versohl: (:D )

    Wenn klar ist, dass das Ergebnis wieder ein Integer ist

    Hier zitiere ich mal einen Berufeneren als mich (aus Post Please login to see this link.):

    Und ich nehme weiterhin an, dass du davon ausgehst, man könne, sofern nur Integerzahlen an einer Berechnung beteiligt sind, auch immer mit Integer-Arithmetik rechnen. [...] Und das ist eben falsch. Der Interpreter wandelt alle Integer in Floats, rechnet und wandelt dann das Ergebnis zurück. Soll ein Compiler in allen Situationen zum Interpreter identische Ergebnisse liefern, dann muss er das in vielen Fällen auch tun. Einfach weil die Zwischenergebnisse größer oder kleiner als der 16-Bit-Integerbereich sein können und weil während der Rechnung nicht ganzzahlige Zwischenergebnisse vorkommen können und der Compiler zur Compilierzeit oft nicht entscheiden kann, ob das der Fall sein wird oder nicht. Klar, kann man alles ignorieren und so tun, als gäbe es das nicht. Aber dann hat man irgendwas compiliert und nicht mehr Commodore BASIC.

    Das kommt halt drauf an, ob besagte Compilate dann stets völlig gleiche Resultate erzielen wie die interpretierten Programme, mit anderen Worten: ob sie semantisch äquivalent sind.

    Genau das ist mein Argument: Viele "Basic"-Compiler erlauben Compileranweisungen oder andere Ergänzungen, wodurch das Kompilat sich extrem stark von einem herkömmlichen Commodore Basic-Code unterscheidet. Die verwendeten "Basic"-Dialekte sind eben nicht semantisch äquivalent zum Commodore Basic. Sie sind weder Fisch noch Fleisch, weder Basic noch eine brauchbare Hochsprache wie Pascal. Vielmehr heftet man diesen Mischmaschsprachen das Etikett "Basic" an, um den massiven Einfluß anderer Hochsprachen zu verschleiern, obgleich weder die Deklaration von Variablen mit ihrer Typisierung noch die Strukturelemente einer annehmenden oder abweisenden Schleife mit festem Sprungziel (dieses ebenso bei FOR-Schleifen) irgendwie Basic entsprechen.

    Letztendlich komme ich immer wieder zur gleichen Schlußfolgerung: Wer eine strukturierte Programmiersprache haben möchte mit diversen Datentypen und angepaßten Operatoren, sollte eine Sprache nehmen, die diese Elemente bereits von Natur aus mitbringt. Wer hingegen einen Compiler haben will, der tatsächlich semantisch äquivalenten Code zu Commodore Basic ausgibt, sollte den MOSpeed von EgonOlsen71 verwenden.

    Ähm? Die Diagramme sind doch die Grammatiken.

    Ja, aber anschaulich dargestellt, wohingegen die von oobdoo erwähnten Formeln halt das typische mathematische Geblubber sind, das wichtig und bedeutsam wirken soll., aber im Grunde genommen trivial ist. Man kann die ganze Seite aus dem Buch rausreißen und würde beim praktischen Schreiben eines Compilers nichts vermissen.

    Statische Arrays bringen also einen (etwas) einfacheren Compiler, (etwas) einfacherer Runtime und ein (etwas) schnelleren Code (da sich das Kompilat ein paar Lookups und Checks zur Dimensionierung des Arrays sparen kann), aber letztendlich macht's in Sachen Komplexität des Compilers den Braten nicht wirklich fett.

    Umgekehrt wird ein Schuh draus. Statische Arrays sind, sofern man Optimierungen wie die Adreßberechnung vornimmt, komplexer. Mein Punkt war aber, daß Basic nicht vollständig kompiliert werden kann, sondern immer wieder Sprünge in Unterprogramme der Runtime-Bibliothek vornehmen muß, auch für Aktionen, die in anderen Hochsprachen keine Runtime benötigen. Das macht den Code nicht nur etwas langsamer, sondern reichlich langsamer, allein schon dadurch, daß die Parameter einer arraySet-Prozedur beim 6502 nicht in den 3 Registern übergeben werden können. Stattdessen muß man auf umständliche Methoden wie Übergabe in der Zeropage oder Übergabe als Konstanten im Code nach dem JSR-Befehl zurückgreifen, die auch noch den Code ziemlich aufblähen. Bei einem P-Code wäre es nicht ganz so schlimm, da die 3 Parameter im günstigen Fall nur 3 Bytes plus einem Byte für den Aufruf von arraySet brauchen. (Sofern die Elementgröße in dem Aufruf bereits enthalten ist. Ansonsten wäre noch ein Byte dafür nötig).


    BTT Pascal-Compiler auf dem C64:

    Ich habe mir mal das Profi-Pascal System angesehen. Es fällt auf, daß viele Fehlermeldungen mit den Fehlermeldungen des UCSD-Pascal-Compilers übereinstimmen. Auch der Bytecode-Interpreter funktioniert ähnlich wie der von ApplePascal, was die vergleichbare Ausführungszeit erklärt. Da außerdem Profi-Pascal ein eigenes Dateiformat verwendet, fragt man sich, inwieweit hier Verbindungen bestehen. Leider kenne ich die Version 1.3 von ApplePascal nicht (nur bis 1.2), so daß ich dies nicht beurteilen kann.

    diese Formel

    Genau das ist das Problem. Diesen ganzen Kram braucht man gar nicht, um einen Compiler zu schreiben. Es ist reines Theoriegelaber in bezug auf Grammatiken. Was Du wirklich brauchst, sind die von 1570 genannten Diagramme, die veranschaulichen, welche Elemente der Programmiersprache aufeinander folgen müssen bzw. dürfen.

    Wenn z. B. die Anweisung "FOR" im Text auftaucht, geht aus dem Diagram für "statement" hervor, daß danach ein "variable identifier" kommen muß gefolgt wiederum von ":=", einer "expression" usw. Stell Dir einfach vor, daß der Compiler beim Übersetzen diesen Diagrammen Stück für Stück folgt. Er weiß dadurch immer, was für ein Element jetzt als Nächstes kommen muß, und kommt kein passendes, gibt er eine Fehlermeldung aus.

    Dieser ganze Vorgang ist leicht als Programm umzusetzen, indem man für jedes Diagramm eine eigene Prozedur schreibt. Es gibt also eine Prozedur "statement" und auch "expression" sowie "factor" oder "variable" usw.

    Allerdings mit einem Unterschied: In dem von 1570 verlinkten Diagrammen sieht man oben ein Diagramm für "identifier" und "unsigned integer" und ein paar andere mehr. Das Parsen von Zahlen oder allgemein Konstanten sowie von Bezeichnern und fest eingebauten Schlüsselwörtern würde man in ein eigenes Unterprogramm auslagern. Dieses hat die Aufgabe, aus einem Text ein abgeschlossenes Element (ein sogenanntes "Token") zu holen. Ein Token ist z. B. das Schlüsselwort "AND" oder "BEGIN" oder "PROCEDURE". Ein Token ist auch eine Zahl wie "1234" oder ein String wie 'Nur ein String'. Oder ein Operator aus einem oder mehreren Sonderzeichen wie "+", "-", "<<". Dazu kommen noch andere Elemente wie Klammern, Semikolon etc.

    Die Routine weist nun jedem Token eine feste ID (= z. B. eine Nummer) zu, so daß der Compiler später einfach nur die ID überprüfen und keine eigenen Stringvergleiche (P..R..O..C..E..D..U..R..E... ah, Wort "PROCEDURE" gefunden...) durchführen muß.

    Als Letztes werden auch alle vom Programmierer benutzten Bezeichner gesammelt und ihnen ebenfalls eine eigene Bezeichnernummer zugewiesen.

    Diese Unterroutine holt also aus dem Sourcecode ein Token, und wenn es sich um einen Bezeichner handelt, gibt sie zusätzlich die Bezeichnernummer zurück. Wenn es sich um eine Konstante handelt (Zahl, Zeichen etc), wird auch noch der Wert der Konstanten zurückgegeben. Auf diese Weise kann man sich das Leben ein wenig einfacher machen.

    Besonders in Hinblick auf Retrorechner hilft diese Unterroutine auch dabei, den Compiler zu beschleunigen und den Programmcode des Compilers zu minimieren, denn diese Unterroutine läßt sich gut in der Assemblersprache des Computers schreiben (Z80 und selbst 6502), wohingegen der Rest des Compilers auch auf P-Code basieren kann.

    Danke, sieht interessant aus. Werde ich mir mal genauer anschauen.

    Bevor Du das anschaust, klick Dich mal zur Please login to see this link. durch und lies am Ende folgende Aussage:

    Quote

    So far, I’ve been implementing a compiler and writing posts as I go. This system worked really well for a while, but now it’s starting to work less well; I realized that some decisions I made in earlier stages made this stage harder to complete, so I had to go back and change them. I think I’m likely to run into more problems like that in later posts. So I’m going to take a break, finish building the compiler (whatever I decide “finished” means), and then come back and write the rest of this series. I probably won’t post another update for six months. So basically…I’m going to keep posting at about the same rate I have been.

    Da die letzte Veröffentlichung schon eine ganze Weile her ist, kann man davon ausgehen, daß der Kurs nicht weiterfortgeführt und auch nicht an die erwähnten neuen Erkenntnisse angepaßt wird. Er ist daher mit Vorsicht zu genießen und liefert auch nicht die Lösungen für die wirklich schweren Probleme.

    das Buch von Wirth

    Das Buch "Compiler Construction" von Wirth in der überarbeiteten Auflage von 2005 kann man sich in der englischen Ausgabe kostenlos im Internet herunterladen. Es behandelt als reales Beispiel die Übersetzung einer (umfangreichen) Untermenge der Sprache Oberon in Befehle für einen RISC-Prozessor. Der Compiler-Sourcecode dazu ist ebenfalls frei erhältlich. Diese zusammen böten prinzipiell ein gutes Anschauungsmaterial, wenn Wirth nicht die Eigenschaft hätte, seinen Code nur äußerst spartanisch zu kommentieren, kryptische Variablennamen zu verwenden und mehrere Befehle in eine Zeile zu quetschen, um nachher sagen zu können: Schaut her, ich habe den kürzesten und kompaktesten Compiler... Mit anderen Worten: Wirth beweist, daß man auch in seiner Sprache schlecht lesbaren Code schreiben kann. :/

    Mein "Problem" mit den meisten Büchern zum Thema (und auch Vorlesungen an der Uni damals) ist, dass sie sich in Grammatiken, Parser und Lexer ergehen und die Codegenerierung oft nur als "Ja, gibt es auch noch...ist aber trivial, das kann jeder Depp" abtun.

    Dies entspricht leider auch meiner Erfahrung. Die genannten Bücher habe ich mir damals[tm] ebenfalls angesehen und auch eine Vorlesung zum Thema besucht, die aber nicht über den Lexer/Scanner hinausgekommen ist. Dabei ist es genau umgekehrt: Das vollständige, reine Parsen eines Programms mit Test auf syntaktische Korrektheit (= Frontend) ist trivial. Die Codeerzeugung hingegen äußerst kompliziert, und man wird das Gefühl nicht los, daß die verantwortlichen Lehrenden sich gerne darum herumdrücken. Mein Fazit am Ende war: Vergiß die Literatur und mach es selber frei nach dem Motto "Learning by doing". Letztlich ist ein Compiler auch nur ein Programm, d. h. man geht in der Entwicklung genau so vor wie bei anderen Programmen auch. Zuallererst muß man wissen, was man überhaupt will. Aus "Ich möchte irgendwie ein Spiel schreiben mit irgendeiner Figur, die irgendwas macht." wird ja auch nie ein fertiges Spiel. Falls Du, ogd, also Interesse daran hast, einen eigenen Compiler zu schreiben, solltest Du zunächst die folgenden Fragen beanworten:

    1.) Auf welchem Computer soll der fertige Compiler laufen? PC oder C64? Dies bestimmt von vornherein den Umfang des Compilers (z. B. mögliche Codeoptimierungen) und die Vorgehensweise: direkte Übersetzung ohne Zwischencode (sehr wenig Speicherverbrauch) <=> Zwischencode für Prozeduren/Funktionen (geringfügiger Speicherverbrauch, teilweise Optimierungen möglich) <=> Zwischencode für das ganze Programm (immenser Speicherverbrauch, auf Retrorechnern nicht zu erreichen).

    2.) Wie sieht die Quellsprache aus? Ist sie Pascalartig? Herzlichen Glückwunsch. Einen Compiler für eine solche Sprache zu erstellen, ist einigermaßen realistisch, Handelt es sich um Basic? Dann kommt man schnell an die Grenzen der Kompilierbarkeit, denn Basic verwendet zu viele Elemente, die erst während der Laufzeit entschieden werden können, und eignet sich daher von vornherein nicht gut als Sprache zum Kompilieren.

    Die Wahl der Quellsprache bestimmt auch, wie kompliziert das Parsen ausfällt: Kann ein Programmtext in einem Durchlauf geparst werden oder braucht man mehr als einen Durchgang? Hierbei muß man unterscheiden in zusätzliche Durchgänge, um Programmadressen (z. B. für Sprünge) zu ermitteln. Dies kannmit Hilfe des Zwischencodes durchgeführt werden. Oder handelt es sich um Deklarationen, bei denen die Verwendung eines Bezeichners vor der Deklaration stattfinden kann? Dieser Fall ist schwer zu behandeln. Beispiel:

    Code
    10    GOSUB 1000
    100   a(50) = 0
    ...
    1000 DIM a(40)

    Der Compiler müßte in Zeile 100 einen Fehler ausspucken, da auf ein Element 50 zugegriffen werden soll, jedoch im späteren Programmcode nur ein Feld mit 41 Elementen erzeugt wird.

    Besonders lustig wird es in Basic bei folgendem Code:

    Damit sich solche Scherze nicht auswirken auf "normalen" Basiccode, in dem ein Array nur einmal dimensioniert wird mit einer Konstanten, muß man im Compiler einige Verrenkungen vornehmen.

    3.) Wie lautet die Zielsprache? Wird in P-Code kompiliert? Das wäre relativ einfach. Assembler hingegen ist recht schwierig, weil z. B. die 8 Bit-Prozessoren nicht über die Adressierungsarten verfügen, die man für ein Hochsprachenprogramm benötigt.

    4.) Soll Zwischencode verwendet werden und falls ja, wie sieht dieser aus? Üblicherweise nimmt man entweder einen Code für eine Stackmaschine, die dann bereits dem auszuwerfenden P-Code nahekommt, oder man verwendet eine 3-Adreß-Codierung (aufwendig). Letztere erlaubt einige Optimierungen am Code, ist aber schwerer in der Handhabung. Stackmaschinen-Befehle lassen sich hingegen auch relativ leicht auf eine Registermaschine übertragen (vorausgesetzt man unterscheidet zwischen Evaluationsstack und Datenstack), erzeugen aber keinen optimalen Code.

    Solltest Du am Anfang noch keine Ahnung haben, wie der Compiler intern aufgebaut sein soll (Unterscheidung Frontend - Backend? Welcher Zwischencode? etc), folge einfach den Ratschlägen von EgonOlsen71:

    1.) Schreib ein Programm in der Programmiersprache Deiner Wahl auf dem PC, welches einen Programmtext in Deiner Quellsprache akzeptiert.

    2.) Schreib darauf aufbauend einen Interpreter für Deine Sprache. Dabei lernt man mit Variablenadressen umzugehen, Bezeichnerdeklarationen umzusetzen, Stackallokationen durchzuführen usw. (Hinweis: Es ist relativ leicht, einen Interpreter für Pascal zu schreiben, wenn man zusätzlich ein Flag definiert, welches festhält, ob ein Code ausgeführt werden darf. Zwar wird hierbei auch nicht auszuführender Code wiederholt geparst (= übersprungen), doch war selbst damals[tm] auf meinem Amiga1200 die Ausführungsgeschwindigkeit eines alten Pascal-Programms aus Schulzeiten schneller als original auf dem AppleII.)

    3.) Schreib einen Codegenerator auf Basis des Interpreters. Vom Interpreter zum Compiler ist es nur ein kleiner Schritt.

    4.) Schreib Deinen Compiler neu in der Quellsprache und übersetze ihn mit sich selber. Wenn das dadurch entstandene Kompilat sich nicht vom Vorgänger unterscheidet, darfst Du Dir gratulieren. Du hast einen Please login to see this link. geschrieben.

    Kurz gesagt: Viel Spaß! ^^

    Dem UCSD die source von TP vorsetzen hat leider nicht geklappt.. BAD SOURCE FILE ERROR

    Da ist guter Rat teuer. Da es sich wahrscheinlich trotz des Namens nicht um echtes UCSD-Pascal handelt, ist völlig offen, welches Textformat der Compiler erwartet. Es könnte sich um irgendwas mit Basic-Zeilen handeln genauso wie eine Textdatei. Nun könnte man versuchen festzustellen, nur um sicherzugehen, ob eine UCSD-Textdatei kompiliert werden kann, habe aber - wie gesagt - keine große Hoffnung. Angesichts der Tatsache, daß kein Editor beiliegt, würde ich eher darauf tippen, daß das Textformat auf Basic-Zeilen beruht. Aber dann bräuchte man ein System, wodurch bei der Eingabe verhindert wird, daß der Pascaltext in Basic-Token umgewandelt wird, was im Endeffekt bedeutet, daß jede Zeile mit einem REM beginnen müßte... Erscheint also auch nicht ganz stimmig. :/

    Letztendlich braucht er die Klammern um den Zahlenvergleich in der inneren until Bedingung.

    Die Klammern dort sind nach der Pascal-Sprachdefinition aufgrund der Auswertungspriorität auch notwendig, da hier bei "a > sz" ein Ausdruck rekursiv aufgerufen wird. Bei NOT hingegen sollten die Klammern weggelassen werden können. Genaueres zur Syntax findet sich z. B. in "Florian Matthes: Pascal auf dem C64" von Markt&Technik, Anhang A: Syntax-Diagramme Pascal 1.4, S. 198 unter "Faktor". Anhand der vier Diagramme "Faktor", "Term", "Einfacher Ausdruck" und "Ausdruck" läßt sich außerdem die Priorität in der Auswertung ablesen. Analog zu diesen vier Diagrammem würde normalerweise ein Compiler vier Parserroutinen verwenden, um einen Ausdruck zu übersetzen.

    Was ich in dieser Hinsicht sehr schade und bedauernswert finde, ist, daß die Sourcecodes der Compiler (bis auf den von Florian Matthes) wohl für immer verloren gegangen sind. Es wäre interessant zu sehen, inwieweit sie sich unterscheiden oder aufeinander aufbauen. Der Compiler von Florian Matthes z. B. beruht wohl auf dem quelloffenen Compiler von Wirth aus den 70er Jahren wie auch der UCSD-Compiler. Es fragt sich daher, ob die anderen von Grund auf Neuentwicklungen sind oder ebenfalls aus einer gemeinsamen Quelle heraus entstanden sind.


    Nebenbei: Ich habe mal das Pascalprogramm auf einem AppleII mit ApplePascal (= UCSD-Pascal) übersetzen und ausführen lassen. Ergebnis: ca. 34-35 Sekunden, wobei allerdings die Ausgabe auf die 80 Zeichenkarte sehr langsam ist. Man müßte mal einen Bytecodeinterpreter ähnlich dem von ApplePascal für den C64 schreiben um zu schauen, wie schnell das Programm dort ausgeführt wird. Die Wurzelroutinen von Tale-X konnte ich leider nicht einbauen, da ApplePascal - wie oben erwähnt - keinen Rechtsshift-Operator kennt. :(

    Hatte ich schon Assembler erwähnt.. da muss ich auch nochmal ran.

    Das wäre meine persönliche Empfehlung. Wenn Du Interesse daran hast, für den C64 zu programmieren, ist Assembler die Sprache der Wahl. (Gilt auch genauso für den AppleII oder den Atari800 als auch für die Z80-Rechner CPC, ZX Spectrum usw.) Wenn Du Dir das Leben erleichtern willst, greif dabei ruhig auf Crossassembler zurück wie ACME oder das C64Studio, d. h. Du schreibst Dein Programm auf dem PC, läßt es mit einem modernen Assembler auf dem PC übersetzen und führst es dann mittels VICE oder einem anderen Emulator auf dem PC aus. Damit erzielst Du am schnellsten Lernerfolge. Das Editieren und Assemblieren auf dem C64 selbst ist beim Erlernen nur hinderlich und verschwendet unnütz viel Zeit.

    Tale-X: Vielen Dank fürs Austesten. Das Ergebnis überrascht ein wenig, da der Algorithmus ohne Division eigentlich schneller sein sollte. (Würde man beide Algorithmen in Assembler umsetzen, wäre dies auch definitiv der Fall.) Ich vermute jedoch, daß es daran scheitert, daß die Ausführung des p-Codes in der Schleife zu langsam ist. Bislang habe ich auch noch keinen Bytecode-Interpreter gesehen, der an die Geschwindigkeit von ApplePascal herankommt. Leider kann ich den Algorithmus unter ApplePascal nicht ausprobieren, da es dort den Operator >> nicht gibt.

    Peter: Ich habe mal mittels Vice in den Compiler reingeschaut. Danach habe ich große Zweifel daran, daß es sich wirklich um einen UCSD-Compiler handelt. Vielleicht basiert der Compiler auf der ersten Version des UCSD-Compilers, die damals in den 70ern frei verteilt wurde. Aber der Bytecode-Interpreter funktioniert definitiv anders als der von ApplePascal (langsamer). Außerdem finden sich merkwürdigerweise deutsche Strings im Code, und bei $36eb findet sich sogar der Text "Austro - Comp". Sieht wirklich nicht nach UCSD aus.

    Daß zu dem Compiler ein Editor fehlt, fiel mir auch auf. Das UCSD-System besteht ja aus mehreren Teilen, u. a. einem Texteditor, einem Linker (fehlt hier auch komplett), einem Assembler, einer Textdatei, welche die Strings für die Fehlermeldungen enthält (gibt es hier auch nicht), und noch einiges mehr. Über das Textformat, das der C64-Compiler akzeptiert, kann nur spekuliert werden. Ich gehe mal davon aus, daß es sich nicht um das gleiche handelt, das von ApplePascal verwendet wird. Übrigens: Die anderen Dateien auf der Diskette (, sofern welche vorhanden sein sollten,) gehören nicht zum Compiler. Vielleicht wäre eine Möglichkeit, einen Text mit dem Turbeo-Pascal-Editor zu erstellen, den Compiler damit zu füttern und zu schauen, was passiert.

    Also ein Stück weit bin ich ja echt froh, dass die Basic-Compiler in den 80ern aber auch ALLE mit einem anderen Mindset geschrieben wurden. :) Und es ist echt niemand dran gestorben!

    Der Punkt ist, daß diese Compiler dann keine Compiler des Commodore Basic waren. Damit z. B. ein Prozentzeichen eine Integerverarbeitung bewirkt, muß die Sprachdefinition geändert werden, was einen neuen Basic-Dialekt produziert, der wieder einmal nicht kompatibel ist zu den vielen anderen. Leider ist der Name "Basic" nicht geschützt, und so kann jeder eine Sprache definieren, in der irgendwie die Befehle PRINT und GOTO vorkommen, und das Ganze dann "Basic" nennen, auch wenn es mit dem Ursprungsbasic oder dem Standardbasic auf dem Gerät (Commdore Basic V2 oder Locomotive Basic etc) nichts zu tun hat. Man kann natürlich das Sprachwirrwarr begrüßen, in dem jeder sein eigenes Süppchen kocht. Für einen Programmierer jedoch ist solch ein Zustand alles andere als angenehm, besonders wenn das Kompilat dann auch noch jeweils andere (Rechen)Ergebnisse liefert.

    Kurz: Der Compiler von EgonOlsen71 kompiliert das Standard Basic des C64. Andere Compiler betreiben in dieser Hinsicht Etikettenschwindel, denn ihr Basic ist ein erfundener Dialekt.