Hallo Besucher, der Thread wurde 11k mal aufgerufen und enthält 60 Antworten

letzter Beitrag von BlondMammuth am

Vision: auf der klassischen Original-Hardwarebasis eines C64 von Grund auf neu geschaffene moderne System-Software in 16 KB ROM?

  • Du verlangtest jedoch einen bijektiven Code, d. h. eine 1:1 Entsprechung von Klartext und Tokenstrom, damit ein gegenseitiges Umwandeln möglich wird. Das ist aber nicht Kompilieren, sondern nur Tokenisieren.

    Auf das habe ich ganz vergessen. Ich stelle es noch einmal dar:


    Ich habe Source-Code. Der enthält Identifier, eine Struktur (in Blöcken), Kommentarzeilen, etc.


    Ich mache daraus Byte-Code. Dieser selbst enthält praktisch nichts mehr davon, allerdings werden diese Informationen nebenher gesammelt und festgehalten. Ich habe vorher gemeint, es gäbe dazu Elemente im Byte-Code, die man bei Bedarf einfach weg werfen kann. Ich bin mittlerweile am Zweifeln, ob die überhaupt im Byte-Code sein müssen oder sollen. Vielleicht ist es besser, sie ganz abgetrennt davon nebenher festzuhalten. Wichtig ist nur, dass zur Entwicklungs (und Experimentier-)Zeit diese Informationen noch verfügbar sind, um den Quelltext zu rekonstruieren.


    Der Bytecode, der daraus entsteht, soll bereits völlig ablauffähig sein, und annähernd in der erwarteten Geschwindigkeit eines reinen Compilats. Zumindest was Blöcke, Schleifen, lokale Variablen, Argumente etc. betrifft, soll es da keinen Unterschied mehr geben.


    Die 1:1-Relation (jetzt verstehe ich dein Verständnis des Wortes erst richtig) ergibt sich ausschließlich aus den temporär noch dabei gehaltenen Zusatzinformationen.


    Die eine große Ausnahme, die es sehr wohl noch gibt, ist die: Globale Objekte (Funktionen und Datenobjekte aller Art, soweit nicht lokal) werden so lange dynamisch gelinkt (also ihre Addresse einmal festgestellt), so lange wir noch im Interpreter-Modus sind (es ist zwar kein echter Interpreter, sondern dieselbe VM, aber ich nenne den Modus einmal so). Wird ein statisches Compilat draus, kann man deren Addressen auch noch statisch ersetzen.


    Ausnahme der Ausnahme: Für Sprachelemente, die tatsächlich nur dynamisch funktionieren können, wird das beibehalten.


    Natürlich kann man das Vorhandensein solcher Sprach-Elemente kritisch sehen und hinterfragen. Das ist dann eine Frage der Philosophie. Es gibt wohl Ansätze, auf sowas generell zu verzichten, und die haben sicher auch ihre guten Begründungen. Der Ansatz hier kann aber so einer nicht sein, weil er von Anfang an darauf beruht, interaktiv zu arbeiten, also dynamisch globale Objekte verwalten zu können und müssen.


    Wie weit "late binding" in dem Fall gehen kann und soll, ist eine eigene Frage. Es gibt gerade bei so einem winzigen System gute Gründe, das von vorne herein abzulehnen. Ich will mir die Möglichkeit nicht von Anfang an verbauen.


    Gerade habe ich auch drüber nachgedacht, ob es gescheiter wäre, jede einzelne Funktion als eigenes File auf Diskette zu halten, und bei Bedarf einfach neu einzulesen und zu compilieren. Allerdings hätte ich dann das Problem, dass man ein Floppy-Laufwerk zwingend voraussetzen muss, sonst wird die Warterei (per Datasette) zur Qual. Und man kann nicht einfach eine Funktion eintippen und ausprobieren. Aber genau das will ich ermöglichen.


    Vielleicht wäre als Alternative zu diesem 1:1-Konzept eine anderes Konzept machbar: Dass man den Source-Code aller Funktionen eigenständig im Speicher hält, und das Compilat ablaufen lässt. Allerdings wirds dann mit sprechenden Fehlermeldungen bei Laufzeitfehlern eher mau, und mit dem Debuggen ebenfalls.


    Trotzdem: Drüber nachdenken schadet gar nichts. Vielleicht ist das tatsächlich die bessere Möglichkeit, ganz ohne datentechnische Klimmzüge im Byte-Code.


    Faszinierendes Thema jedenfalls!

  • Schreiben hat was länger gedauert, hab die letzten Sachen von Mammuth noch nicht gelesen.

    Wozu brauche ich zwei verschiedene Dinge, Interpreter und Compiler, für ein und dieselbe Sache? Und wenn man dann später ohnehin einen Compiler anwerfen muß, um das Programm brauchbar schnell zu machen: Wozu benötigt man dann einen total komplizierten Tokenizer für die Programmentwicklung?

    Da sind 2 Anwendungen:
    -Einerseits eine verständliche Sprache zum "einfach loslegen", weitgehend "interpretiert". Alles nötige incl. Editor muss im ROM sein, schlechter als V2 sollte sie auch nicht sein.
    -Andererseits ist sowas wie Sweet16 schön: Kleiner als Assembler, möglichst schnell, soll Platz im ROM sparen, "compiliert" ist völlig i.O.
    Meine Hoffnung ist, dass eine Sprache/Interpreter beiden Zielen gerecht werden kann.


    Beide haben Zugriff auf den gleichen Satz an Tokens, werden praktisch aber doch verschiedene Teile daraus nutzen.
    Die Interpreter-Fassung dürfte wohl kaum irgendwelche Vorwärts-Sprünge erzeugen,
    die Compiler-Fassung wird andersrum kaum Tokens für Suchen nach oder temporäres Anlegen von Variablen verwenden.

    sind die Ähnlichkeiten zu z. B. CIL

    Wie sähe denn der "Bubble"-sort in CIL aus? Die ganze .Net-Welt ist ja riesig, vielleicht gibt es da auch ein Tool, das sowas lesbar darstellt, quasi wie ein Maschinensprache-Monitor?


    -----------------------------------------------------------------------------
    Ich zäume das Pferd wohl Gedanklich von der anderen Seite auf. Im Prinzip hab ich mich schon festgelegt, dass die Sprache Basic-Ähnlich wird und nicht sowas wie komplette Unterprogramme als Übergabeparameter erlauben wird.


    -Was kann man alles dem Editor zuschieben und schon bei der Eingabe oder zumindest beim Start erledigen?
    -Welche Daten werden zum Laufen gebraucht (compiliert), welche braucht nur der Editor?
    -Welche Daten werden für Prüfungen zur Laufzeit gebraucht?
    -Was gibt es für "Meta"-Daten? Sowas wie public/private, Blocklängen, Variablen-Namen...
    Hierbei hab ich vor allem vor den Augen, was für eine "compilierte" Fassung im ROM alles verzichtbar ist.


    -Kann ein externes Programm am PC aus dem "Compilat" wieder was für den Editor erstellen?
    Sehr nebensächlich, aber wäre schon cool, wenn man das Programm im ROM wieder editierbar machen kann.


    -Wunschliste: Welche "modernen" Sprachelemente wären schön, die jenseits von V2 liegen?
    -Unterprogramme mit Parametern und Rückgabe(n).
    -Strukturen im Sinne zusammengesetzter, eigener Variablentypen.
    -Lokale Variablen.


    -"Scope" an sich, geht ja über lokale Variablen hinaus. Zugriffe beschränken und Fehler meiden zu können ist ja schon schön.
    -Datentyp Variant, der beliebige andere Typen aufnehmen kann?
    -Cast? Vielleicht als Ersatz für int() oder Str$()?
    -Variabel dimensionierte Arrays?
    -Strings fester Länge (an fester Position?)
    -Callbacks?
    -Pointer für verkettete Listen? Zeiger überhaupt? Und überhaupt zur Laufzeit erzeugte Variablen...


    -Strukturiertes Programmieren Im Sinne von ohne Goto.
    -Objektorientierung? Zumindest Instanzen scheinen mir nützlich oder sogar nötig.
    -Multitasking?
    Das soll nicht alles umgesetzt werden! Fragen dazu sind eher: KÖNNTE die VM das? Woran scheitert es, was für Programme kann man dann nicht mehr schreiben? SIe muss auf jeden Fall gut genug sein, den "compilierten" Editor für sich laufen zu lassen.


    -Da sich das kaum alles umsetzen lässt: Kann man ähnlichen Zweck auch mit anderen Konzepten erreichen, die weniger Aufwand machen?
    -Scope: Evtl. werden übergeordnete Variablen außerhalb des aktuellen Blocks nur über Index oder zumindest Sonderkommandos ansprechbar sein.
    -Kann man auf Goto verzichten? Vielleicht muss es sogar ohne gehen (was ja sehr schön wäre)!
    -Parameter... ByRef oder ByValue? Oder nur über Index ansprechbar, Typkontrollen vielleicht über Extra-Befehle am Anfang des Unterprogramms?
    -Oder Parameter über Extra-Kommandos aus dem Bytestream holen, quasi wie es die Befehle in V2 heute machen?
    Hier geht es vor allem um die Frage, ob die Sprache trotz aller Beschränkungen noch verständlich umsetzbar ist oder ob das Konzept doch besser aufgeböhrt werden sollte.


    -Packt es die Garbage Collection?
    V2 hat eine einfache Liste von Strings+Arrays, dazu einen "Stack" nur für String-Inhalte. Aber falls es Instanzen von "Objekten" gibt, die ja auch wieder irgendwo gespeichert werden wollen, und die dann wieder Strings enthalten... Klingt das böse.


    -Scheint es mir halbwegs realistisch, dass ein Editor am C64 diese Daten beim Editieren trennen, zusammenfügen und nachträglich ändern kann?
    -Welche Einschränkungen und komischen Kompromisse kann man zur Not hinnehmen bzw. auf Zusatzprogramme schieben, weil der kleine C64-Editor es nicht packt?
    Basic V2 kam ja auch ohne ein Renumber daher, schon gar nicht mit Anpassung der Sprungziele.
    Evtl. könnte es sein, dass Ändern eines Variablentyps oder Entfernen einer Variablendefinition ein Problem wird.
    -----------------------------------------------------------------------------
    Ich hab da schon diverse Ideen, aber das ist noch recht frisch, ich weiß noch nicht, wie weit die mich tragen

  • Strukturen = Records? würde zu weit führen (Aufwand!)
    Lokale Variablen wäre schön, da das Basic-Korsett eh knapp mit namen ist (2 Zeichen lange Bezeichner) ...
    Variant = von mir auch vorgeschlagen ..
    variabel dim'ed Arrays = verleiten zu nachlässigem Programmierstil ..
    Pointer vielleicht nicht generell, aber Syntax "geschickt" erweitern , so dass z.b. ein Zeiger auf Datenstrukturen - Zeilen die DATA enthalten - gewonnen werden kann. Vielleicht die USR-Funktion mit einem Stub vorbelegt, der die 6502-Speicheradresse einer als Argument übergebenen Struktur zurückliefert. (z.b. eines String-Arrays).

    gut genug sein, den "compilierten" Editor für sich laufen zu lassen.

    Meinst du, den in der Sweet16 -Zwishensprache verfassten (codierten) Editor?

  • Ich gehe natürlich davon aus, dass dieser Offset zur Compile-Zeit berechnet wird. Wenn du meinst, ich hätte das nicht so vorgehabt, verstehe ich das Mißverständnis. Also doch, das habe ich ganz entschieden so vor.

    Okay, damit können wir also folgende Dinge festhalten:


    1.) Du benötigst im ROM:
    - einen Vollbildschirmeditor,
    - einen Tokenizer,
    - einen Compiler,
    - einen Bytecodeinterpreter für auf dem C64 kompilierten Entwicklungscode,
    - einen Bytecodeinterpreter für auf dem PC kompilierten optimierten Code.


    2.) Ein Editor arbeitet zeilenorientiert. Deine Sprache ist NICHT zeilenorientiert. Daher ist der Editor selber nicht in der Lage. während der Eingabe den Programmtext anders umzuwandeln als in der Form eines simplen Tokenizers, d. h. er übersetzt sequentiell einzelne Symbole in einen Bytewert. Da Ausdrücke, Blöcke usw. sich über mehrere Zeilen erstrecken und der Tokenizer keinen Überblick hat über die Gesamtstruktur einer Funktion, ist der Tokenizer in seiner Leistung sehr eingeschränkt. Was der Tokenizer NICHT kann:
    - Erstellen einer Bezeichnerliste und Zuordnung von Bezeichnern zu einem Eintrag in dieser Liste,
    - Umwandlung von Infix in Postfix,
    - Berechnung von Sprungadressen,
    - Berechnung von Variablenoffsets,
    - Typprüfung und damit Festlegen genauer Operationen (z. B. + als Integeraddition oder Stringkonkatenation).
    All dies kann nur in einem zweiten Schritt von einem Compiler erledigt werden.


    3.) Da Du darauf beharrst, daß die Eingabe des Editors und damit die Reihenfolge der Symbole von links nach rechts dem fortlaufenden Code entsprechen soll und der Editor somit Infix nicht in Postfix umwandeln kann, der Compiler wiederum aber den Programmtext nicht zerstören darf, kann die Optimierung nach Postfix nur von einem zweiten Compiler (z. B. auf dem PC) ausgeführt werden. Der dadurch entstandene Postfix-Bytecode ist aber nicht kompatibel zum Infix-Entwicklungscode. Du brauchst dafür einen zweiten Bytecodeinterpreter. Im Übrigen ist allein schon wegen der Infixdarstellung des Entwicklungscodes dieser definitiv nicht so schnell wie ein vollständig nach Postfix kompilierter Code.


    4.) Der Compiler ist KEIN inkrementeller Compiler. Um Sprungadressen und Offsets zu berechnen, MUSS der Compiler als kleinste Einheit mindestens eine Funktion am Stück übersetzen. Das kann man so machen, daß im Editor der Cursor vor dem Kompilieren auf den Beginn der Funktion gesetzt wird, die übersetzt werden soll. Danach wird der Compiler per Tastendruck aufgerufen. Das ist aber kein inkrementeller Compiler.



    Folgende Fragen bleiben ungeklärt:
    - Was ist der Unterschied zwischen einem Compiler, der eine Funktion übersetzt, und einem Compiler, der mehrere Funktionen übersetzt? Also wieviel Platz läßt sich mit einem 1-Funktion-Compiler einsparen gegenüber einem X-Funktion-Compiler?
    - Wozu braucht man einen Tokenizer, wenn die meisten Sprachsymbole ohnehin nur aus einem Zeichen bestehen und alle wichtigen Vorgänge (s. o.) sowieso erst vom Compiler vorgenommen werden können?
    - Wie werden Stringliterale behandelt? (Hinweis: In Deinem Bytecode liegen sie an der Stelle, wo sie im Editor stehen, müssen bei Ausführung also immer übersprungen werden. Bei einem optimierten Code werden sie ausgelagert in ein eigenes Datensegment.)
    - Wozu soll man einen Bytecode haben mit einem großen Haufen Zusatzinformationen, die irgendwie (wie überhaupt?) mit dem Bytecode verlinkt sind, wenn man genauso gut einen einfachen Programmtext haben kann und einen daraus erzeugten Bytecode? (Ich werde das Gefühl nicht los, daß Dein Ansatz sich von hinten durchs Knie schießt.) Anders gesagt: Glaubst Du wirklich, daß ein Compiler, der eine Funktion von einem echten Quelltext nach Bytecode übersetzt signifikant langsamer wäre als ein Compiler, der einen irgendwie vorgefertigten, aber immer noch völlig unfertigen Bytecode um wesentliche Informationen ergänzen muß? Tip: Das Hangeln durch den Bytecode ist wesentlich aufwendiger als das Lesen der Zeichen aus dem Quelltext.)
    - Wie unterscheidet Dein Compiler die verschiedenen Variablen i in folgender Struktur

    Code
    1. [
    2. i: int8 := 1;
    3. [
    4. i: int8 := 2;
    5. ]
    6. ]
    7. [
    8. j: int8 := 0;
    9. i: int8 := 3;
    10. ]

    - Wie deklarierst Du Dein Hauptprogramm (und damit den Einstieg ins Programm)?
    - Wie deklarierst Du globale Variablen?
    - Wie unterscheidest Du call-by-reference von call-by-value sowohl syntaktisch als auch im Bytecode?
    - Wie kannst Du eigene Datentypen deklarieren der Form

    Code
    1. TYPE
    2. t_datensatz = RECORD
    3. name : STRING;
    4. strasse : STRING;
    5. hausnummer : INTEGER
    6. END;

    Und wie kannst Du im Code einer Variablen diesen Typ zuordnen? Der Bytecode kennt zwar Befehle zur Erzeugung von Standardtypen wie int8 oder int16, aber was ist mit eigenen Datentypen? Noch ein Bytecode mit einem Link auf irgendeine Liste irgendwo im Speicher?
    - Gegeben sei eine Graphik-Bibiothek, die ein Rechteck definiert:

    Code
    1. TYPE
    2. t_rechteck = RECORD
    3. links : INTEGER;
    4. oben : INTEGER;
    5. rechts : INTEGER;
    6. unten : INTEGER;
    7. END;

    Wie wird dieser selbstdefinierte Datentyp in einem Programm angewendet? Woher kennt der Compiler bei der Übersetzung einer Funktion diesen Datentypen (Namen und Struktur)?
    - Was passiert, wenn in einer Bibiothek eine globale Variable geändert wird, so daß sie mehr oder weniger Platz einnimmt als vorher? Mit anderen Worten: Die Bibliothek (oder sonstwie extern definierte Funktion) paßt nicht mehr zum sonstigen Programmcode. Woran erkennt der Interpreter diesen Konflikt? (Der C64 kennt kein Dateidatum, schon gar keine Uhrzeit.) Soll jedesmal beim Laden eines Programms dieses mit all seinen Komponenten sicherheitshalber neu übersetzt werden?
    - (Wo wir schon mal dabei sind:) Woher kennt der Compiler überhaupt global definierte Variablen aus einer externen Funktion?
    Bitte sage jetzt nicht, daß die irgendwie im Speicher stehen. Solange Du noch nicht mal im Ansatz irgendein Dateiformat hast, daß diese ganzen Informationen zusätzlich zum Bytecode und den anderweitigen Informationen (Wo waren nochmal die Kommentare?) repräsentiert, macht es keinen Sinn zu sagen: Die Informationen gibt es irgendwie irgendwo.
    - Also nicht vergessen: Wie sieht das Dateiformat aus? Wie sieht der Systemteil aus, der dieses Dateiformat lädt und daraus eine Bezeichnerliste macht, einen Bytecode zur Ausführung, einen Zusatzinfobereich für den Editor usw.?
    - Wie soll das alles in 16kb reinpassen?



    -Was kann man alles dem Editor zuschieben und schon bei der Eingabe oder zumindest beim Start erledigen?

    Nicht viel. Da der Texteditor zeilenweise arbeitet (s. o.) kann er maximal längere Schlüsselwörter in Tokens umwandeln und mehrere Leerzeichen nacheinander RLE-komprimieren.

    -Welche Daten werden zum Laufen gebraucht (compiliert), welche braucht nur der Editor?

    Der Editor braucht nur den Text.
    Zur Programmausführung sind Sachen nötig wie
    - Datentyp einer Variable
    - Größe der Variable
    - Ort im Speicher mit aktuellem Wert der Variable
    - Sprungziele von Sprüngen
    - Zeiger auf Programm (PC), Stapel (SP) und Stackframe (FP), gegebenenfalls auch globale Daten (GP)
    - Zeiger auf Evaluationsstack bzw. Registersatz (implizit)

    -Welche Daten werden für Prüfungen zur Laufzeit gebraucht?

    Bei einem Interpreter s. o. Ansonsten ist es abhängig vom Kompilat und ob die Sprache eine starke, statische Typisierung verlangt. Starke, statische Typisierung hat den Vorteil, daß viele Überprüfungen während der Übersetzung vorgenommen werden und der Code nachher sehr kompakt ist. Dynamische Typisierung erfordert auch zur Laufzeit eine fortwährende Überprüfung. Desweiteren geht der Zugriff auf die Werte nur indirekt über Zeiger und macht eine Garbage Collection erforderlich. Außerdem sind In der Regel Operatoren syntaktisch überladen. "A = B" kann je nach Kontext ein Vergleich von Zeichen, Zahlen, Zeichenketten oder sonst was sein. Bei starker Typisierung kann der Compiler entscheiden, um welche Operation es sich tatsächlich handelt, bei dynamisch nicht. Dort muß er wie ein Basic-Interpreter stets erneut beide Datentypen überprüfen, ob sie gleich sind, und wenn nicht, entweder versuchen, den einen so umzuwandeln, daß ein Vergleich möglich wird, oder einen Programmabbruch veranlassen. Die strenge Typisierung braucht für den Vergleich einen Bytecode, der von ein paar 6502-Befehle ausgeführt werden kann. Bei der dynamischen Variante braucht es einen ganzen Haufen veschiedener Unterroutinen (s. Basic-Interpreter). Außerdem muß zusätzlich zu jeder Variable gemerkt werden, welchen Datentyp sie gerade hat, und dieser Datentyp muß dann irgendwie repräsentiert werden. (Lustig bei Arrays von Structs). Das alles kostet wieder jede Menge Speicher, der dem eigentlichen Programm fehlt. Kurz gesagt: dynamisch = langsam und speicherintensiv.

    -Was gibt es für "Meta"-Daten? Sowas wie public/private, Blocklängen, Variablen-Namen...

    Solche Metadaten läßt man am besten im Quelltext. Der Bytecode sollte davon völlig befreit sein, um einen maximalen Befehlsdurchsatz zu erreichen.

    -Kann ein externes Programm am PC aus dem "Compilat" wieder was für den Editor erstellen?

    In der Regel nicht. Blockanweisungen wie WHILE, REPEAT und FOR werden vom Compiler übersetzt in direkte Sprünge (GOTO). Diese lassen sich nicht ohne große Mühe als Blocktruktur restaurieren.

    -Multitasking

    Bitte nicht. (s. u.)

    -Scheint es mir halbwegs realistisch, dass ein Editor am C64 diese Daten beim Editieren trennen, zusammenfügen und nachträglich ändern kann?

    Meiner persönlichen Meinung nach nicht. Es dürfte um einiges einfacher sein, den Editor als einfachen Bildschirmeditor zu gestalten und alles andere dem Compiler zu überlassen. Dieser Editor kann auch nicht als Bytecode vorliegen, da er ansonsten zu langsam wäre, um z. B. einigermaßen zügig seitenweise durch den Text zu scrollen. Da im Editor auch kaum 16-Bit-Werte gebraucht werden, würde es auch keinen Sinn machen, hierfür eine virtuelle 16-Bit-Maschine zu verwenden. Da wäre Assembler schneller und unter Umständen sogar kürzer.

    KÖNNTE die VM das? Woran scheitert es, was für Programme kann man dann nicht mehr schreiben?

    Beispiel Multitasking: Wenn man eine Art Multitasking erlauben wollte, müßte man im Interpreter einen Zähler mitschleppen, der pro Befehl dekrementiert wird, damit nach einer Anzahl von Befehlen der Task gewechselt wird. Alternativ könnte man ein Flag im Interrupt setzen, daß abgefragt wird. Bei einem Taskwechsel muß der komplette Zustand des Evaluationsstacks bzw. des Registersatzes im Speicher gesichert und derjenige des anderen Tasks restauriert werden. Das alleine verbraucht schon wahnsinnig viel Zeit. Will man es richtig machen, bräuchte man auch noch eine Liste der aktiven und der wartenden Tasks und dann noch eine Prioritätenliste, d. h. welche Tasks Vorrang vor anderen haben usw. Ich wüßte keinen Grund, warum man auch nur eines davon auf dem C64 umsetzen sollte. Sollte ein Programm tatsächlich so etwas wie Nebenläufigkeit brauchen, wäre es besser, diese im Programm selbst zu implementieren und nicht in der VM.

    Sweet16 -Zwischensprache

    Zur Zeit eignet sich der SWEET16 nicht als Zielsprache für einen Compiler, da Befehle für Grundoperationen wie Multiplizieren oder Dividieren nicht definiert sind. Außerdem ist die Adressierung von Variablen etwas umständlich und erfordert mehr Platz bzw. Befehle als z. B. beim p-Code von UCSD-Pascal. Auch bei einer erweiterten Definition des Befehlssatzes bleibt zudem das Problem bestehen, daß man es hier mit einer Registermaschine zu tun hat. Registerallokation ist beim Übersetzen aber noch mal ein Übel für sich. Bei einer Stackmaschine umgeht man dies von vornherein. (Genauer gesagt: Man ignoriert es einfach.)

  • Meinst du, den in der Sweet16 -Zwishensprache verfassten (codierten) Editor?

    Nein, der Editor soll in sich selber geschrieben sein. Die Zwischensprache umfasst also nicht nur Assembler-ähnliche, simple Befehle, sondern bringt schon alles mit, was man für echte Variablen usw. braucht.

    Der Editor braucht jedenfalls einigen Überblick über das Programm.
    Imho sollten im Voraus alle Variablen bekannt sein, damit er die auch tokenisieren kann., und er darf die Blockstruktur nicht kaputtmachen.
    Compiler muss nicht,
    ein Bytecode-Interpreter für beides.





    Ich hab hier mal was entworfen:



    Module:
    1 Programm setzt sich aus X Modulen zusammen.
    Jedes Modul hat seinen eigenen Speicher, keine direkten Zugriffe auf andere Module.
    Zugriffe auf andere Module nur über Extra-Befehle.


    1 Modul setzt sich zusammen aus:
    1 Teil Variablen
    1 Teil Arrays(?)
    1 Teil Programm


    Variablen-Teil:
    1 Teil zusätzliche LZ-Daten (Länge des Bereichs, Anzahl verschiedener Typen, Flags wie z.B. Schreibschutz des Speichers, ohne Instanz...)
    1 Teil eigentlicher Speicher, kann auch vorbelegt bzw. reserviert sein.
    1 Teil Editor-Daten (Namen, Kommentare)


    Max. 256 Variablen in der compilierten Fassung
    Vorbestimmt 24 Variablen jedes Typs in der Editor-Fassung als reine Editor-Beschränkung.
    Oder wie viele auch immer schön auf den Schirm passen.
    Nachträgliches hinzufügen, entfernen oder Typ ändern dürfte dem Editor sonst viel Aufwand machen oder den Anwender zu sehr beschränken.


    Aufbau des eigentlichen Speichers:
    -Variablen sind nach Größe/Typ sortiert.
    -Instanzen sind ein kompletter eigener Satz aller Variablen. Ändern der Base-Adresse (und der weiteren Vektoren für die längeren Variablentypen) reicht zum Umschalten.


    Anlegen von Variablen beim Start des Programms:
    -Vorbelegter Speicher vorhanden?
    --Schreibgeschützt? Dann beim Modulstart eine Kopie davon als Instanz anlegen. CLR erzeugt ebenfalls Kopie.
    --Ohne Schreibschutz? Dann einfach verwenden, selber Schuld, wenn Init-Werte verloren gehen. CLR initialisiert mit $00
    -Kein reservierter Speicher?
    --Aus den zusätzlichen LZ-Daten eine Instanz anlegen, CLR mit $00.


    Array-Teil:
    Wegen der anderen Struktur abgetrennt. Sind in V2 ja auch ein Sonderfall
    TBD.


    Programm-Teil:
    1 Teil Unterprogramme
    1 Teil Code


    -Setzt sich zusammen aus Blöcken.
    -Blöcke können geschachtelt sein, aber auch parallel liegen.
    -Alle Schleifen sind Blöcke, aber auch hinter THEN oder ELSE finden sich Blöcke.
    -Unterprogramme sind Blöcke.
    -Am Blockanfang wird auch die Länge des Blockes gespeichert.
    -Startbefehle der Blöcke werden beim Ablauf des Programms "entdeckt" und auf dem Stapel gespeichert. Typisch Interpreter.
    -Sprünge sind nur zum Blockanfang (LOOP) oder zum Blockende (EXIT) möglich.
    -Da die Länge beim Blockstart gespeichert ist, kann das Blockende für EXIT auf dem Stapel gefunden werden.
    -Blockende-Befehle dürfte es in den Varianten LOOP und EXIT geben. Evtl. noch ein LOOP mit Ausdruck, der auf TRUE/FALSE getestet wird.


    -Unterprogramme sind Blöcke mit Sonderfunktion.
    Im Gegensatz zu den anderen Blöcken werden sie wie Variablen gespeichert und bekommen auch deren Editor-Daten.


    Der Sort-"Benchmark"
    CONST N = 8;
    VAR a : ARRAY [1 .. N] OF INTEGER;
    PROCEDURE bubblesort;
    VAR i, j, temp : INTEGER;
    BEGIN
    FOR i := 1 TO N - 1 DO
    FOR j := N DOWNTO i DO
    IF a[i] > a[j] THEN
    temp := a[i];
    a[i] := a[j];
    a[j] := temp
    END
    END
    END
    END bubblesort;



    Da der Teil für Arrays noch TBD ist kritzel ich jetzt einfach mal was hin, was dem V2 ähnlich sehen dürfte.
    Das Drumrum für Module lasse ich auch weg, ist auch noch TBD.



    Variablenteil:

    Code
    1. -Länge : $000c = 5(eigentlicher Speicher) + 7 (Overhead dieser beschreibenden Struktur)
    2. -Anzahl Byte(1) : 3
    3. -Anzahl Integer(2) : 1
    4. -Anzahl Strings(3) : 0
    5. -Anzahl Floats(5) : 0
    6. -(Und was es sonst noch an Datentypen geben mag, nach Größe sortiert!)
    7. -Flags : 1
    8. (Speicher vorbelegt, aber beschreibbar)
    9. (Der Speicher für die Variablen ist mit im Programm und darf bei der Arbeit beschrieben werden)



    Der eigentliche Speicher:
    8,0,0,$0000


    Editor-Daten:

    Code
    1. Länge (dieses Datenblocks) : $xxxx
    2. "N ; die Variablennamen und Kommentare, passend zum Speicher sortiert."
    3. "I ; Der Editor kann die auslesen"
    4. "J"
    5. "Temp"


    Array-Teil:

    TBD, jetzt einfach mal was dahergeschrieben, was nach V2 aussieht.

    Code
    1. -Länge : $0005 = 0(Array-Inhalt) + 7 (Dieser Overhead)
    2. -Anzahl : 1
    3. -Dimensionen : 1
    4. -Typ (Int) : 1
    5. -Größe Dim 1 : 8
    6. -Flags : 0


    (Kein vorbelegter Speicher (Array-Speicher wird erst zur Laufzeit belegt))


    Der eigentliche Speicher:
    Entfällt, wie in V2 wird das Array erst beim Start des Moduls erzeugt, da es keine Vorbelegung gibt (TBD).
    Editor-Daten:

    Code
    1. "A ; Keine variable Dimension!"

    Programm-Teil eines Moduls:

    Code
    1. Länge : $xxxx
    2. Anzahl Unterprogramme : 1
    3. Liste deren Adressen : $0801

    Weitere Daten, Parameter o.Ä:
    TBD


    Editor-Daten:

    Code
    1. Länge : $xxxx
    2. "Bubblesort"


    Mnemonic-Code, quasi im Monitor:
    ; Die Mnemonics haben je nach Parameter schon verschiedene Token.
    ; Hängt letztlich von Nützlichkeit und Platz ab, welche Varianten eines Mnemonics eigene Token haben.
    ; Ebenso, ob nicht schon Variablentypen mit in die Token kommen.
    ; Zur Not gibt es immer eine Variante mit "Ausdruck auswerten"
    ;
    ; Für die Lesbarkeit hier:
    ; % als Präfix für Variablen,
    ; # als Präfix für Konstanten,
    ; ( leiten Ausdrücke ein,
    ; ) beenden sie
    ;
    ; In Ausdrücken mögen diverse Präfixe auch im Bytestrom stehen,
    ; meist weiss aber schon der Befehl selber, welche Parameter kommen.
    ; In der Regel werden Variablennamen kommen, das % findet sich also eigentlich nirgends.
    ; (TBD: Rechenzeichen und Variablen-Nummern dürfen nicht kollidieren)
    ; Ansprechen von Array-Variablen fragt direkt nach den Indices
    ;


    Die Längen hab ich jetzt nicht abgezählt, werden jedenfalls 2 Bytes Platz brauchen.
    Das Beispiel hatte sehr einfache Parameter, die imho aber auch sehr oft vorkommen und daher Sonder-Tokens verdienen.
    Mit komplizierteren Formeln wären mehr Varianten mit Ausdruck-Auswertung gekommen.
    Kann sein, dass a[j] in seiner Form als Ausdruck auch ein Byte mehr braucht, aber ist erstmal egal.



    Der Editor könnte das so darstellen:

    Und falls die ganzen Editor-Daten verloren sind:


    Offensichtliche Probleme (bisher):
    -Sonderfälle der Parameter (wie hier im IF) mögen die Ausführung beschleunigen und den Code kürzen, dürften aber sehr kompliziert im Editor zu erkennen sein.
    -Dieser Bytecode ist noch sehr HighLevel. Man sieht regelrecht, dass dort sehr oft Unterprogramme wie "hole Adresse zu Var", "Berechne Array-Index" usw. gebraucht werden - was ja eigentlich ein Fall für Bytecode wäre. Womit sich die Katze in den Schwanz beisst.
    -Die ganzen Strukturen, Einfügungen usw, die der Editor erzeugt, sollen in dieser Sprache selber ausgedrückt werden. Da sehe ich noch nicht die nötigen Befehle und Strukturen. Ein Array Byte(38911) kann es ja nicht sein...
    -Offensichtlich muss noch reichlich viel zur Laufzeit gerechnet, nachgeschlagen und geschoben werden.


    -Lokale Variablen?
    -Rekursionen?
    -Überhaupt Zeiger und Stacks?
    -Arrays variabler Länge?
    -Aufruf von Funktionen in anderen Modulen über Namen zur Laufzeit?
    -Zugriff auf Variablen in anderen Modulen?
    -Reichen dafür vielleicht eine Hand voll von Standard-Unterprogrammen a la get/set?
    -Gibt es doch noch Stellen, wo Variablen und Adressen erst zur Laufzeit aufgelöst werden sollten?
    -Speicherung der Instanzen im Detail.


    -Der Editor wird nach der Eingabe einer Zeile eine Menge Zeiger und Längen anpassen müssen.
    -Da scheint noch einiges zu fehlen, um den Editor dafür halbwegs anständig in sich selbst zu programmieren.
    -Der Editor wird keine ganz freie Eingabe erlauben können. Einfach mal wild 10 Blockende-Befehle einbauen geht nicht, weil mit jeder Blockänderung auch Zeiger angepasst werden müssen. Daher muss die Struktur jederzeit OK sein.

  • Es gibt nur einen Bytecode-Interpreter. Es gibt einen .. Compiler, wie es sich abzeichnet, denn einen Tokenizer brauchts wirklich nur, wenn überhaupt, zur Kompression des Quelltextes. Es braucht keinen Vollbildschirm-Editor. Z.B. ist es denkbar, schlicht und einfach Zeilennummern zu verwenden wie in BASIC; und den Source-Code einer Funktion dann jeweils (tokenized oder nicht) in einem eigenen Bereich zu speichern, bzw. bei Bedarf wieder in dieser BASIC-artigen Zeilen-Form zur Verfügung zu stellen. Ich habe mich nie auf einen Vollbildschirm-Editor versteift, bloß gemeint, dass eine Funktion sich über mehrere Zeilen erstrecken kann.

    Wenn man davon ausgeht, dass die Sprache einigermassen kompakt bleibt, braucht er das nicht. All das sind Überlegungen, die mehr oder weniger sinnvoll sind. Du hast mit Erfolg argumentiert, dass eine zu enge 1:1-Beziehung kaum möglich ist. Das wäre das FORTH-Modell, da ist es möglich, aber wenn ich das einigermassen "konventioneller" und lesbarer gestalten will, ist es sinnvoll, davon abzugehen, und tatsächlich den Source-Code jeweils im Speicher zu halten.

    All dies kann nur in einem zweiten Schritt von einem Compiler erledigt werden.

    Natürlich. Zustimmung.

    Da Du darauf beharrst, daß die Eingabe des Editors und damit die Reihenfolge der Symbole von links nach rechts dem fortlaufenden Code entsprechen soll

    Nein, darauf beharre ich nicht. Das war ein Denkansatz, aber der ist zur Diskussion gestanden. Ich denke (eben auch dank deiner Beiträge), dass er sich nicht bewähren wird.

    Der Compiler ist KEIN inkrementeller Compiler. Um Sprungadressen und Offsets zu berechnen, MUSS der Compiler als kleinste Einheit mindestens eine Funktion am Stück übersetzen. Das kann man so machen, daß im Editor der Cursor vor dem Kompilieren auf den Beginn der Funktion gesetzt wird, die übersetzt werden soll. Danach wird der Compiler per Tastendruck aufgerufen. Das ist aber kein inkrementeller Compiler.

    Nein, ist er nicht. Er müsste so arbeiten, dass alle Interaktion von unterschiedlichen Compilaten (Funktionen) vor dem Ablauf aufgelöst werden müssen, oder (teurer) während des Ablaufs. Lieber vor dem Ablauf.

    - Was ist der Unterschied zwischen einem Compiler, der eine Funktion übersetzt, und einem Compiler, der mehrere Funktionen übersetzt? Also wieviel Platz läßt sich mit einem 1-Funktion-Compiler einsparen gegenüber einem X-Funktion-Compiler?

    Das ist nicht der Sinn. Der Sinn besteht in der möglichen Interaktivität. Wenn man auf dem C64 das ganze Programm zugleich übersetzt, wird man dabei alt. Übersetzt man je eine Funktion, ist das erträglich.

    - Wozu braucht man einen Tokenizer, wenn die meisten Sprachsymbole ohnehin nur aus einem Zeichen bestehen und alle wichtigen Vorgänge (s. o.) sowieso erst vom Compiler vorgenommen werden können?

    Das ist eine Überlegung wert. Ich bin da noch nicht schlüssig. Siehe weiter oben.

    - Wie werden Stringliterale behandelt? (Hinweis: In Deinem Bytecode liegen sie an der Stelle, wo sie im Editor stehen, müssen bei Ausführung also immer übersprungen werden. Bei einem optimierten Code werden sie ausgelagert in ein eigenes Datensegment.)

    Stringliterale stehen bei mir sicher nicht "literal" im Code. Die sind, wenn, dann in einem eigenen Segment. Diese Aufteilung gibts aber noch nicht.

    - Wozu soll man einen Bytecode haben mit einem großen Haufen Zusatzinformationen, die irgendwie (wie überhaupt?) mit dem Bytecode verlinkt sind, wenn man genauso gut einen einfachen Programmtext haben kann und einen daraus erzeugten Bytecode?

    Wie schon gesagt: Diesbezüglich hast du mich mittlerweile weitgehend überzeugt. Zustimmung!

    (Ich werde das Gefühl nicht los, daß Dein Ansatz sich von hinten durchs Knie schießt.)

    Tut er nicht, er entwickelt sich einfach weiter, und dank deiner ziemlich fundierten Kritik sogar recht schnell und zielgenau. ;-)

    Wie unterscheidet Dein Compiler die verschiedenen Variablen i in folgender Struktur

    Bei der Code-Generierung weiß er, welche Variable in welchem Block welchem Stack-Offset entspricht, und wenn eine die andere überdeckt, nimmt er die innerste.

    - Wie deklarierst Du Dein Hauptprogramm (und damit den Einstieg ins Programm)?

    Ich würde sagen, per Aufruf der jeweiligen Funktion von der Kommando-Zeile. Man könnte aber natürlich eine System-Variable einführen, die bei "run" jeweils eine Eingabe ausführt. Oder .. oder .. oder. Da gibt es mehrere Möglichkeiten.

    - Wie deklarierst Du globale Variablen?

    Das Modell, das ich angedacht habe, ist ein uniformes Modell von Variablen, die ungefähr (aber nicht im Detail) dem entspricht, was der BASIC-Interpreter jetzt schon tut. Ich wäre allerdings dafür, einen zusätzlichen Compile-(oder Link-)Vorgang zu erlauben, der alles, was da ist, statisch verlinkt, und so den Overhead beseitigt.


    - Wie unterscheidest Du call-by-reference von call-by-value sowohl syntaktisch als auch im Bytecode?

    Wie in C: per Pointer.

    - Wie kannst Du eigene Datentypen deklarieren der Form

    Das war der Sinn der "Tupel"-Schreibweise. Das gehört allerdings noch besser durchdacht und ausgearbeitet. Allerdings existiert eine Ähnlichkeit von Parameter-Listen zu solchen Typen, und darauf soll das Konzept aufbauen.

    Und wie kannst Du im Code einer Variablen diesen Typ zuordnen?

    Meistens gar nicht. Das muss in dem Fall bereits vor dem Ablauf verlinkt sein, wo die Information statisch vorliegt. Tut sie das nicht, muss das über Tabellen laufen. Allerdings ist das noch geistige Baustelle.

    - Was passiert, wenn in einer Bibiothek eine globale Variable geändert wird, so daß sie mehr oder weniger Platz einnimmt als vorher?

    Dann hat sich ihr Typ geändert. Sofern es sich um ein Array oder ein Struct handelt, wird zunächst dynamisches Binden dieser Info notwendig sein, solange man daran herum entwickelt. Wenn man alles, was da ist, zufrieden in einem Schwung speichern will, ändert man daran nichts mehr, daher kann man auch die Information statisch linken.

    - (Wo wir schon mal dabei sind:) Woher kennt der Compiler überhaupt global definierte Variablen aus einer externen Funktion?

    Während der Entwicklung: dynamisch. Danach: statisch.

    Bitte sage jetzt nicht, daß die irgendwie im Speicher stehen. Solange Du noch nicht mal im Ansatz irgendein Dateiformat hast, daß diese ganzen Informationen zusätzlich zum Bytecode und den anderweitigen Informationen (Wo waren nochmal die Kommentare?) repräsentiert, macht es keinen Sinn zu sagen: Die Informationen gibt es irgendwie irgendwo.

    Dazu braucht es ein einheitliches Format für globale Definitionen, wie erwähnt, das es natürlich noch nicht in dem Detaillierungsgrad gibt.


    Wo waren nochmal die Kommentare? Gut, in der früheren Vision abseits des Codes, mit der Änderung, den Source Code separat intakt zu halten, ist das obsolet.


    - Also nicht vergessen: Wie sieht das Dateiformat aus? Wie sieht der Systemteil aus, der dieses Dateiformat lädt und daraus eine Bezeichnerliste macht, einen Bytecode zur Ausführung, einen Zusatzinfobereich für den Editor usw.?
    - Wie soll das alles in 16kb reinpassen?

    Ja, das alles will erarbeitet werden. ;-)


    Die File-Formate, sofern es zwei völlig verschiedene Formate für Source Code und für Byte Code gibt, werden wohl machbar sein.


    Wie passt das in 16kb rein? Naja, wie passts jetzt rein? Jetzt hat man ja auch einen Tokenizer, einen Interpreter (ein Compiler wird nicht grundsätzlich viel komplexer werden als ein Interpreter), und das Lesen und Schreiben der entsprechenden Formate auch. Die virtuelle Maschine fehlt dabei natürlich noch, die sollte allerdings erstens im Vergleich zum BASIC-Interpreter eher simpler sein sollte, und zweitens sollte es möglich sein, etliche Teile des OS eben in kürzerem Byte-Code zu stricken, um das auszugleichen.


    Gut, sollte .. sollte .. sollte. Sollte es nicht gehen, gehts halt nicht. ;-)

  • Es braucht keinen Vollbildschirm-Editor. Z.B. ist es denkbar, schlicht und einfach Zeilennummern zu verwenden wie in BASIC

    Oh Graus. Warum willst Du einen Zeileneditor, wenn ein Vollbildschirmeditor wenig Platz verbraucht und auch viel einfacher zu bedienen ist? Hinzukommt, daß der Zeileneditor pro Zeile 4 Bytes mehr benötigt (Zeilennummer und Link auf nächste Zeile) als ein normaler Texteditor. (Damals[tm] hatte ich einen Editor mit eingebautem Assembler in ca. 5 kb. Davon brauchte der Editor ca. 3kb, allerdings war da schon der Tokenizer zum Umwandeln der Assembler-Befehlstoken enthalten. Natürlich war der Editor total primitiv, aber immer noch besser als ein Zeileneditor. Brrrr...)

    Ich habe mich nie auf einen Vollbildschirm-Editor versteift

    Ich schon. :D


    Das Modell, das ich angedacht habe, ist ein uniformes Modell von Variablen, die ungefähr (aber nicht im Detail) dem entspricht, was der BASIC-Interpreter jetzt schon tut. Ich wäre allerdings dafür, einen zusätzlichen Compile-(oder Link-)Vorgang zu erlauben, der alles, was da ist, statisch verlinkt, und so den Overhead beseitigt.

    Das habe ich nicht verstanden. Aber vermutlich war meine Frage schlecht gestellt. Es ging mir zunächst einfach darum zu wissen, wo im Programmtext globale Variablen deklariert werden und wie das aussieht, also rein syntaktisch. Wenn man z. B. eine Grafikbibliothek definiert, die ein Array verwaltet, in dem konstant ein Zeichensatz zum Malen abgelegt ist, dann muß dieser Bereich ja auch fest reserviert werden. Daraus ergibt sich, daß man irgendwie die Möglichkeit braucht, wiederholt (auch für weitere Bibliotheken) im Programm globale Variablen zu definieren. Erst die nächste Überlegung gilt dann den Problemen, die entstehen, wenn man getrennte Bibliotheken lädt, die alle jeweils ihren eigenen globalen Speicher mit sich bringen (s. Hoogos Ausführungen).


    Ein (längeres) Hochsprachenprogramm gliedert sich in mehrere Bereiche: 1.) Bibliothek, 2.) Klasse, 3.) Funktion/Unterroutine, 4.) Block. Der Compiler übersetzt nun jeweils eine Funktion. Diese Funktion kann zu einer Klasse gehören (z. B. eine Methode) oder zu einer Bibliothek. Auf jeden Fall macht es Sinn, anhand dieser Grobstruktur den gesamten Programmtext in einzelne Module zu spalten, also einzelne Quelltextdateien, die jeweils eine Bibliothek oder Klasse enthalten. Jede dieser Textdateien wird dann getrennt zu einer Objektdatei kompiliert, und solche Objektdateien meinte ich, als ich von "extern" sprach.
    Diese Objektdateien müssen nun miteinander verlinkt werden, sei es statisch oder dynamisch. (Nebenbei: Für das statische Linken auf den PC würde ich vorschlagen, den ganzen Programmtext inklusive aller verwendeten Module in einem Rutsch zu kompilieren. Dadurch spart man sich Objektdateien und den Linker auf dem PC. Einzelne Objektdateien mitsamt Linker sind tatsächlich mangels Beschränkung des Compilers nur auf dem C64 nötig.)
    Für das Zusammenbinden von verschiedenen Modulen muß man aber nicht nur überlegen, wie solche Module a) als Objektdateien verlinkt werden, sondern auch b) wie sie gegenseitig ihre Deklarationen (API) austauschen. In C benutzt man für API-Deklarationen eines Moduls (z. B. Namen der Funktionen mit Anzahl und Typ der Parameter) bekanntlich die ".h"-Datei, die der Compiler zwingend braucht, um eine passende Objektdatei zu erzeugen. Auf welche Art und Weise kannst Du in Deiner Sprache APIs beschreiben?


    Und beruhend auf der Aufspaltung eines Programms auf mehrere Module nochmal die Frage nach Veränderungen dieser Module bzw. Objektdateien: Auf dem PC verwendet man ein MAKE-File, das dafür sorgt, daß die Textdateien, die sich auf veränderte Module beziehen, automatisch neuübersetzt werden. Gibt es eine Möglichkeit diese Abhängigkeiten abzubilden? Wie soll erkannt werden, daß eine Objektdatei sich verändert hat, damit andere Dateien neuübersetzt werden?

  • Oh Graus. Warum willst Du einen Zeileneditor, wenn ein Vollbildschirmeditor wenig Platz verbraucht und auch viel einfacher zu bedienen ist?

    Ich habe mich mit der Frage nicht intensiv beschäftigt. Wenn du davon ausgehst, dass es so ist, dann bin ich ganz bei dir, es so zu machen. Aber dann ist das ja eher ein Argument dafür, dass das Endergebnis weniger Platz braucht als der derzeitige BASIC-Zeileneditor.

    (Nebenbei: Für das statische Linken auf den PC würde ich vorschlagen, den ganzen Programmtext inklusive aller verwendeten Module in einem Rutsch zu kompilieren. Dadurch spart man sich Objektdateien und den Linker auf dem PC. Einzelne Objektdateien mitsamt Linker sind tatsächlich mangels Beschränkung des Compilers nur auf dem C64 nötig.)

    Das hielte ich für recht vernünftig. Allerdings hätte ich diese Möglichkeit natürlich auch gerne native auf dem C64, und um die Frage "was bin ich für ein Perverser?" zu beantworten, von Datasette bzw. in Grenzen onboard. ;-)

    Für das Zusammenbinden von verschiedenen Modulen muß man aber nicht nur überlegen, wie solche Module a) als Objektdateien verlinkt werden, sondern auch b) wie sie gegenseitig ihre Deklarationen (API) austauschen.

    Jetzt eine Frage an dich: Ich bin davon ausgegangen, dass eine strikte Hierarchie besteht, also wenn A B benutzt, nicht B zugleich A benutzt (ausser es werden von A nach B Funktionszeiger übergeben). Würdest du das so sehen? Wenn das nämlich geht, kann man theoretisch eine LIB durch statisches Linken erzeugen, die nur mehr ein paar IDs exportiert, und über diese IDs von einem dynamischen Entwicklungs-Programm zugreifen. Geht das in Ordnung (hat man es fertig entwickelt), kann man das Ganze wieder statisch linken, und erhält so einen größeren Monolithen, den man wieder als Basis für dynamische Entwicklung verwenden kann, usw.


    Das löst noch nicht die Frage, wann und wie diese APIs überprüft werden. Derzeit hätte ich im Kopf, dass das jedesmal dynamisch geschieht, solange man dynamisch verlinkt (nicht grade schnell). Das ist auch noch Baustelle! Vielleicht wäre eine Idee die, dass man beim interaktiven Arbeiten immer einen on top-Zustand hat, in dem alles, was man vorher festgelegt hat, fix ist, und das, was man grade macht, dazupassen muss. Das heißt, sobald man etwas verwendet, werden die APIs (oder der Typ der Funktion) automatisch readonly.


    Das könnte man aufweichen, in dem man mit Floppy arbeitet, daher die Quelltexte in beliebiger Reihenfolge wieder laden kann.

    Wie soll erkannt werden, daß eine Objektdatei sich verändert hat, damit andere Dateien neuübersetzt werden?

    Brauchts im Grunde nicht, solange man die Teile noch dynamisch verlinkt (also on board auf dem C64). Sollte man die alle zusammenbinden, muss man alles neu verlinken (wahrscheinlich compilieren auch, das ist aber jetzt noch ein Nachdenkvorgang). Das heißt, einen make-Formalismus habe ich nicht vorgesehen. Den könnte man theoretisch dazu basteln, wenn man von Diskette arbeitet und gleich alles durch compiliert, bzw. das auf einem PC tut.

  • Zugriffe auf andere Module nur über Extra-Befehle.

    Die aus der OOP bekannten get-set-Routinen haben allerdings den Nachteil, daß sie sehr langsam sind. Gerade bei einem Rechner mit 1Mhz muß man sehr gut überlegen, ob diese Vorgehensweise auch in der Praxis effizient genug ist.

    Schreibschutz des Speichers

    Verzeihung, was verstehst Du unter "Schreibschutz des Speichers"?

    Startbefehle der Blöcke werden beim Ablauf des Programms "entdeckt" und auf dem Stapel gespeichert. Typisch Interpreter.

    Welchen Stapel meinst Du hier?

    Der Editor wird nach der Eingabe einer Zeile eine Menge Zeiger und Längen anpassen müssen.

    1.) Dies macht die Eingabe sehr langsam.
    2.) Was passiert, wenn man z. B. den Blockanfang eines neuen Blocks eingegeben hat, aber noch nicht das Blockende, da dies erst ein paar Zeilen tiefer kommt. Was sollte daraufhin passieren? Fehlermeldung? Wie unterscheidet man zwischen einem echten Fehler, der passiert, weil etwas vergessen wurde, und einem Programm, das erst noch im Entstehen begriffen ist?

    dürfte dem Editor sonst viel Aufwand machen

    Wie schon bei BlondMammuths Vorschlag würde ich auch hier davon abraten, den Eingabeeditor mit zu viel Aufgaben zu überlasten. Ein Editor, der all den genannten Bedingungen genügen soll, würde bereits den Rahmen der 64kb sprengen. Besser wäre es meiner Ansicht nach, den Programmtext einer Sprache als reine Textdatei zu editieren und später die Übersetzung von einem Compiler vornehmen zu lassen. Das bedeutet viel weniger Aufwand als ständig bereits während der Eingabe Überprüfungen vornehmen zu müssen, zumal, wie Du selbst schreibst, es auch noch Beschränkungen durch den Editor hervorrufen würde. Prinzipiell sollte eine Sprache nicht in direkter Abhängigkeit stehen zu der Art ihrer Eingabe.


    und um die Frage "was bin ich für ein Perverser?" zu beantworten, von Datasette bzw. in Grenzen onboard.

    Ja, wahrscheinlich während Deine Frau dabei zu Deinen Füßen Schweinefleisch ist (s. Woody Allen). Masochist! :sm::D

    Ich bin davon ausgegangen, dass eine strikte Hierarchie besteht, also wenn A B benutzt, nicht B zugleich A benutzt (ausser es werden von A nach B Funktionszeiger übergeben). Würdest du das so sehen?

    Bedaure, aber meiner persönlichen Erfahrung nach ist dies nicht möglich. Es gibt zig Beispiele, in denen ein Modul A sich auf ein Modul B bezieht und gleichzeitig B auf A. Kleines Beispiel: Ein C64-Emulator verfügt über zwei Module: A) Speicher, B) VIC. Das Speichermodul leitet Zugriffe auf die Adressen $d000.. weiter an das VIC-Modul. Das VIC-Modul braucht zur Darstellung des Bildschirminhalts (direkte) Speicherzugriffe auf den emulierten RAM-Speicher vom Speichermodul. Rückbezügliche Verbindungen dieser Art haben mir in C schon manchmal die schön sauber getrennten Headerdateien durcheinander gebracht und das Prinzip einer möglichst auf 'public' reduzierten Schnittstelle zunichte gemacht. Funktionieren kann sowas nur, wenn man alle Module eines Programms an einem Stück kompiliert und dafür einen 2-Pass-Übersetzer verwendet. Bei allen anderen muß man z. B. die Beschreibung des Interface von der Implementation komplett abspalten (mit Hilfe von 'forward'-Deklarationen) und dem Code voranstellen.

    Brauchts im Grunde nicht, solange man die Teile noch dynamisch verlinkt.

    Braucht es wohl. Was soll passieren, wenn es in einem Modul eine Variable oder Funktion früher mal gab, diese aber dann entfernt wurde? Wohin wird dann das andere Modul linken? Was passiert, wenn in einem Modul eine Funktion mitten drin ergänzt oder weggenommen wurde? Wie sollen diese ganzen Funktionen, Variablen usw. eigentlich eindeutig gekennzeichnet werden, so daß ein Linken überhaupt möglich ist? Das setzt logischerweise voraus, daß in jeder Objektdatei alle exportierten Elemente (Kann man die überhaupt syntaktisch so kennzeichnen?) in einer Liste mit genauer Beschreibung (Name, Typ usw.) mitgeschleppt werden müssen. Auf das Dateiformat und den dazugehörigen Linker bin ich gespannt. Nebenbei bemerkt: Stell Dir "dynamisch Linken" nicht so einfach vor. Allein die Verwaltung, z. B. Einrichten, der Zeiger ist sehr aufwendig, und die Programmausführung wird dadurch drastisch gesenkt.

  • Ja, wahrscheinlich während Deine Frau dabei zu Deinen Füßen Schweinefleisch ist (s. Woody Allen). Masochist!

    :rauch: Da hat mich einer ganz schön durchschaut! :roll2:

    Bedaure, aber meiner persönlichen Erfahrung nach ist dies nicht möglich.

    Gut, bei solchen Abhängigkeiten würde ich schlichtweg drauf verzichten, dazu ist das System schlicht und einfach zu klein. Es tut zwar in der Seele weh, die Abstraktionen nicht sauber trennen zu können, aber .. naja, siehe Woody Allen! ;-)


    Was man vielleicht versuchen könnte, wäre dieses: Eine Schnitstellendefinition vom Compilat unabhängig importieren und testen zu können. Aber bei Gegenseitigen Abhängigkeiten

    Was soll passieren, wenn es in einem Modul eine Variable oder Funktion früher mal gab, diese aber dann entfernt wurde?

    Wenn ich voraussetze, dass ich das nur mit halb geordneten Modulen tun kann (also das Abhängigkeitsverhältnis grundsätzlich gerichtet ist), dann habe ich ein bereits feststehendes, gelinktes Modul im Speicher, das ich nicht mehr ändern kann, und ein dynamisches, an dem ich grade entwickle. Ich kann zwar das alte noch einmal aufschnüren, ändern, und wieder compilieren, aber wenn ich es dann, im API verändert, lade, und das neue Zeug dazulade, krachts beim Startversuch.


    Aber ich finde, man sollte die gegenseitig abhängigen Module trotzdem in Hinterkopf behalten! Da hab ich wieder was zu kauen! :gluck:kruecke:


  • Die aus der OOP bekannten get-set-Routinen haben allerdings den Nachteil, daß sie sehr langsam sind. Gerade bei einem Rechner mit 1Mhz muß man sehr gut überlegen, ob diese Vorgehensweise auch in der Praxis effizient genug ist.

    Das ist die eine Sorge.
    Die andere Sorge: Diese Module sollen nicht nur eine Art NameSpace sein, sondern auch nachgeladen werden können. Spätestens hier wäre ein Late Binding über Namen sehr nützlich. Alles über Index anzusprechen ist beim Verschieben von Unterprogrammen nicht sehr schön, und eine Prüfung auf die Grenzen zur Laufzeit würde auch nötig.
    Mir schwebt da auf die Schnelle Variablen vom Typ "Adresse" vor. Beim Start des aufrufenden Programms müssen die dann per Befehl gefüllt werden.
    Kann sein, dass dieser Typ eh gebraucht wird, um Module mit verschiedenen Kopien eines Variablensatzes laufen zu lassen.


    Dieser äußere Rahmen ist NICHT ansatzweise fertig durchdacht!!
    So langsam scheint mir das alles für 16KB reichlich fett zu werden, grad wenn man bedenkt, dass V2 nicht mal ein Merge hat.




    Verzeihung, was verstehst Du unter "Schreibschutz des Speichers"?


    a) Ein Modul bringt einen vorbelegten Variablenspeicher mit.
    oder
    b) Ein Modul kommt nur mit dem Header der Typen daher.


    Falls a):
    c) Die Startwerte dürfen einfach verändert werden
    oder
    d) Die sollen für Später erhalten bleiben. Wäre ja blöd, wenn man erst Mühe in das Eingeben der Startwerte steckt und die beim ersten Testlauf verloren gehen.


    Wenn die erhalten bleiben sollen, dann sind die Schreibgeschützt, und vor dem Start des Moduls wird ein Kontext mit einer Kopie angelegt.
    Nebenbei erschlägt das den Sonderfall, dass der Editor mit all seinen Variablen im ROM steht.
    (Ich gehe davon aus, dass die meisten Variablen initialisiert werden und viele manuellen LET mehr Platz fressen).




    Welchen Stapel meinst Du hier?


    Ach, da bin ich nicht so streng :D Ich betrachte das Ganze ja entgegen der Richtung von Mammuth, also mehr so vom Groben ins Feine.
    Ich sag mal "Der Stack, auf dem Rücksprünge landen".




    1.) Dies macht die Eingabe sehr langsam.2.) Was passiert, wenn man z. B. den Blockanfang eines neuen Blocks eingegeben hat, aber noch nicht das Blockende, da dies erst ein paar Zeilen tiefer kommt. Was sollte daraufhin passieren? Fehlermeldung? Wie unterscheidet man zwischen einem echten Fehler, der passiert, weil etwas vergessen wurde, und einem Programm, das erst noch im Entstehen begriffen ist?


    Ich sehe die ganze Zeit 1 Zeile = 1 Befehl.
    Der Editor kennt alte und neue Zeile und kann daher sagen, ob "irgendwas" zu Blockstart oder Blockende wurde oder umgekehrt.
    Blockende ist verboten, das kann man nicht eingeben! Im Grunde muss man diese Zeilen nur betreten, falls man davor/dahinter eine Einfügen will.
    Blockstart fügt auch gleich das passende Blockende ein.
    Blockstart entfernen entfernt alles bis zum passenden Blockende.




    Wie schon bei BlondMammuths Vorschlag würde ich auch hier davon abraten, den Eingabeeditor mit zu viel Aufgaben zu überlasten. Ein Editor, der all den genannten Bedingungen genügen soll, würde bereits den Rahmen der 64kb sprengen. Besser wäre es meiner Ansicht nach, den Programmtext einer Sprache als reine Textdatei zu editieren und später die Übersetzung von einem Compiler vornehmen zu lassen. Das bedeutet viel weniger Aufwand als ständig bereits während der Eingabe Überprüfungen vornehmen zu müssen, zumal, wie Du selbst schreibst, es auch noch Beschränkungen durch den Editor hervorrufen würde. Prinzipiell sollte eine Sprache nicht in direkter Abhängigkeit stehen zu der Art ihrer Eingabe.


    Im Prinzip gerne, aber irgendeinen Tod muss man ja sterben.
    Mammuth und mit schwebt halt vor, möglichst viel aus der Laufzeit rauszuhalten, durchaus auch auf Kosten des Komforts. Mir fallen jedenfalls immer mehr Haken und Ösen auf.




    Bedaure, aber meiner persönlichen Erfahrung nach ist dies nicht möglich. Es gibt zig Beispiele, in denen ein Modul A sich auf ein Modul B bezieht und gleichzeitig B auf A. Kleines Beispiel: Ein C64-Emulator verfügt über zwei Module: A) Speicher, B) VIC. Das Speichermodul leitet Zugriffe auf die Adressen $d000.. weiter an das VIC-Modul. Das VIC-Modul braucht zur Darstellung des Bildschirminhalts (direkte) Speicherzugriffe auf den emulierten RAM-Speicher vom Speichermodul.


    ...Oder beide greifen auf einen 3. zu.
    Ich bin nicht sicher, ob das immer möglich ist, aber hier ginge es.

  • Übrigens habe ich begonnen, einen Basic-Prototyp zu entwerfen. Ob ich jemals damit fertig werde, weiß ich noch nicht, also nagelt mich bitte nicht drauf fest! :lauh3: Falls ich aber in die Nähe komme, werde ich das Resultat zu gegebener Zeit veröffentlichen. :alt:

  • Darf man fragen, ob Du Dich an die von mir in den Raum geworfene Größenbeschränkung von 16 KB zusammen mit dem Kernal (oder 8KB für den Interpreter allein) gebunden fühlst?


    Diese Information benötige ich um dem (olympischen) Wettbewerbsgedanken huldigen zu können :)

  • Selbstverständlich, oh Stephan, fühle ich mich diesbezüglich gebunden! :-)


    Obs was nützt weiß ich nicht, denn die erste Version entsteht in reinem BASIC und soll nur demonstrieren, wie ich mir den Funktionsumfang vorstelle. Ob die mit 16k Code auskommt, und wie weit das relevant ist, kann ich wirklich nicht voraussagen.

  • BlondMammuth:
    Um noch einen Liter Wasser in das Glas Wein zu gießen...
    Ich weiß, daß Du Dich mit Namensräumen erst zu einem späteren Zeitpunkt auseinandersetzen willst. Trotzdem aber schon mal vorab folgende Überlegung:
    Normalerweise teilt man Programme der Übersicht halber gern ein in verschiedene Klassen/Bibliotheken/Units/Module und spricht Elemente dieser Bereiche von außen an durch Kombination von Bereich und Element, z. B. "grafik.clearscreen".
    Frage: Wenn Du Funktionen/Prozeduren/Methoden einzeln übersetzen lassen willst, woher weiß der Compiler in welcher Bibliothek/Klasse/Unit... er sich gerade befindet?

    die erste Version entsteht in reinem BASIC

    Bitte nicht falsch verstehen, aber warum entwickelst Du das in V2-BASIC und nicht in C, Java, Python etc auf dem PC? Wäre letzteres nicht ein wenig schneller und einfacher? Es geht ja nicht um die Codegröße, sondern allein ums Entwickeln des Algorithmus. Ich bin mir nicht sicher, ob V2-BASIC dafür wirklich die beste Plattform ist. :/


    Größenbeschränkung von 16 KB zusammen mit dem Kernal

    Persönlich glaube ich nicht daran, daß so etwas möglich ist und würde eher für ein offenes System plädieren, dessen Teile von Diskette geladen werden können ähnlich GEOS.
    Was man vielleicht anstelle von BASIC ins 8kb ROM ($a000-$bfff) einbauen könnte, wäre ein Vollbildschirmeditor mit Assembler und ein Bytecodeinterpreter für Hochsprachenprogramme. Den Compiler dafür dürfte man aber kaum in 16kb quetschen können.

  • Was meinst Du mit "look and feel"? Den 40-Zeichen-Textbildschirm in blauen Farben? Wenn es unbedingt sein muß, läßt sich sowas sehr einfach nachbasteln...

    Jedenfalls ist es eine völlig emotionale Entscheidung, die zu rationalisieren, wie du wieder einmal richtig herausarbeitest, alles andere als einfach, wenn überhaupt möglich ist. ;-)

  • Dass er in reinem Basic V2 entwickeln will, hätte ja als Vorbild die Sprachen von Niklaus Wirth, von denen etliche als Königsdisziplin sozusagen rekursiv "in sich selbst geschrieben sind".


    Der Oberon-Compiler soll bspw. in Oberon verfasst sein .....


    So könnte User "BlondMammoth" ja auch - mit Basic - verfahren wollen!?