Jeden Tag ein gutes Wort: perform und andere tolle Sachen
Weiter auf meinem Weg zur Weltherrschaft zu Forth einer forthähnlichen Sprache auf dem Hive. An sich funktionieren ja schon einige Sache bei m: Der Interpreter läuft, ausführbare Worte können compiliert und Quelltexte können in einem Screeneditor bearbeitet werden. Dennoch erinnerte ich mich an einige Leichen im Keller: Die Worte „token“ und „m“ enthielten noch als erste Fassung eine recht unübersichtliche Struktur. Nicht das diese Worte nicht funktionierten, aber irgendwie war die Realisierung nicht wirklich handlich und elegant.
Wir erinnern uns: Da m ein verteiltes System ist, enthält der Core in Regnatix keinen Parser für eine Eingabezeile und auch keine Worte für eine formatierte Ausgabe. Schließlich habe wir ja ein massiv paralleles System, soll sich doch Bellatrix um diesen Krempel kümmern, in Regnatix haben wir dafür keinen Platz! Und Bella kann sich auch wirklich viel besser um die Interaktionen mit dem Benutzer kümmern, da ja alle Ressourcen (Tastatur und Bildschirm) an diesen Chip angeschlossen sind.
So kennt der m-Core in Regnatix nicht mal das Konzept einer Befehlszeile sondern einzig und allein einzelne Befehlstoken, mit welchen er von Bella gefüttert wird wie ein hungriger Wolf. Um dieses Tokeninterface möglichst geradlinig und einfach zu gestalten, sind die Eingaben schon bezüglich ihrer Funktion markiert. Diese Markierung wird durch Farben zum Ausdruck gebracht. Bei der Eingabe kann also die Farbe festgelegt werden und damit auch die subtilen Funktionen der Eingabe. Bisher gab es folgende Farbmarkierungen:
schwarz Wort wird ausgeführt rot Wort wird neu erzeugt grün Wort wird compiliert blau Zahl cyan Zahl wird compiliert (Number Literal) gelb String wird compiliert (String Literal) grau Kommentar (werden nicht zum Core gesendet)
Der m-Core empfängt also Token für Token und bearbeitet diese entsprechend der farblichen Markierung. Genau diese Funktion erfüllt das Wort „token“: Es empfängt (momentan von Bellatrix) ein Token und führt dessen Funktion aus. Realisiert wurde das durch eine unübersichtliche IF-THEN Struktur:
token ( -- ) colortag dup gc#m_ctag0 = if interpret then dup gc#m_ctag1 = if create then dup gc#m_ctag3 = if then dup gc#m_ctag4 = if number then gc#m_ctag5 = if string then ;
Das ist ziemlich umständlich. Da der Zeichencode der Colortags in einem zusammenhängenden Bereich liegt und damit ein Index 0..n berechnet werden kann, lässt sich die Funktion von token besser und eleganter durch eine Sprungtabelle realisieren. Dafür benötigen wir aber das Wort „perform“:
perform ( index adr -- )
Auf dem Stack werden zwei Werte übergeben: Die Anfangsadresse einer Tabelle mit Sprungzielen und ein Index. Perform ist also ein indizierter indirekter Unterprogrammaufruf. Nach der Implementierung konnte obiges Wort nun eleganter geschrieben werden:
function interpret create compile nop number string ;data token colortag> gc#ctag0 - function perform ; m begin token again ;
Das Wort function ist dabei ein Array aus Zeigern (kommt noch weiter unten im Text), welches die Adresse der Daten auf dem Stack übergibt, wodurch dann zusammen mit dem Farbindex perform die entsprechende Funktion ausführt. Sehr übersichtlich und dank der Definition von perform in Assembler auch verdammt schnell.
Nun gut, dass also zum Wort des Tages. Allerdings hat sich im m-Core seit dem letzten Artikel noch viel mehr getan. Hier nun im Schnelldurchlauf, was sonst noch geschah:
Colortags und die Rückkehr zu UPN: UPN (Umgekehrt Polnische Notation) ist so ziemlich das nerdigste an Forth. Dennoch gibt es Situationen, welche diese Regeln durchbrechen. So wird oft in einem Wort das nächste Token aus dem Eingabepuffer geparst. Ein Beispiel in m ist das Wort zum öffnen einer Datei:
open datei.blk
Auf der einen Seite ergibt dieses Verhalten Probleme im Interpreter, da der m-Core nur einen Stringpuffer für ein einziges Token hat. Wir erinnern uns: der Core bekommt wie ein hungriger Köter einzelne Token aus der Eingabe wie Fleischbrocken vorgeworfen. Das Konstrukt
token datei.blk str .str > .str OK
gibt also nicht den erhofften String „datei.blk“ mit dem Befehl .str aus, sonder es erscheint im Terminal der String des Befehls .str selbst. (Die Ausgaben des Core erscheinen immer zwischen roten Pfeil und dem OK-Prompt.) Wie man erkennen kann, sind irgendwie die Mechanismen aus einem klassischen Forth in meinem verteilten System nicht verwendbar. Der Core hat keinen uneingeschränkten Zugriff auf den Eingabepuffer mit allen Token, was gerade im interpretierenden Modus Probleme bereitet.
Davon abgesehen, sind diese Mechanismen auch unlogisch oder wenigstens nicht ganz konsequent. Strings sind wie Zahlen Daten. Numerische Date landen als Wert in einem gesonderten Puffer – dem Datenstack – und können dort in einfacher und übersichtlicher Weise verarbeitet werden. Strings aber werden teilweise infix durch das System geschleust. Das soll bei m anders werden. Aus dem Konstrukt
open datei.blk
soll entsprechend dem UPN-Konzept
datei.blk open
werden. Gleiches gilt im Gegensatz zu einem klassischen Forth auch für forget! Also nicht
forget name
sondern
name forget
Erst die Daten, dann der Operator, wie sich das für gutes UPN gehört! Korrekt wäre entsprechend den numerischen Daten die Verwendung eines Stacks für Strings, aber so viel Speicher möchte ich momentan in m nicht für die Stringverarbeitung verwenden. Vielleicht wird das aber noch ein Thema für eine Erweiterung, welche einen größeren Stringstack im eRAM verwaltet. Aber vorerst muss im hRAM ein von den Token getrennter Puffer für Strings her. Für beide Puffer – Token und Strings – gibt es nun je ein Datenwort:
tok ( -- cstr) - Puffer für Token str ( -- cstr) - Puffer für Strings
Entsprechend wurden auch die Colortags für Strings und Stringliterale erweitert, aber davon gleich mehr, denn es gibt noch eine weitere grundlegende Neuerung.
Variablen und Daten: Mittlerweile bin ich recht froh, m nicht als mForth sondern meistens nur als forthähnliches System bezeichnet zu haben, denn erfahrene Forthler würden mir wahrscheinlich einige Lösungen um die Ohren hauen. Aber da m nur forthähnlich ist, kann ich gelassen mit den Schultern zucken und raunend „forthähnlich“ in den Ring werfen – ein absoluter Todesstern unter den Argumenten! Oder einfach ausgedrückt: auch Variablen werden in m anders als in klassischen Systemen definiert. 😉
Aber erstmal wieder ein wenig Beinarbeit. Grundlegend sehe ich zwei Formen von Wortdefinitionen:
create - Definition von ausführbaren Wörtern data - Definition von Datenwörtern
Verwendet man ein create-Wort, so wird eine ausführbare Folge von Forthcode compiliert. Klar was beim Aufruf eines solchen Wortes passiert: es wird – oh Wunder – einfach ausgeführt. Ein Datenwort aber, legt im Gegensatz dazu, einzig die Adresse der Daten auf den Stack, um sie zur Verarbeitung zur Verfügung zu stellen. Soweit ist das ähnlich dem Konzept von Variablen in Forth. Um es einfach zu halten, ist es bei m aber völlig egal, was für Daten sich in dem Wort befinden – geliefert wird immer ein Zeiger auf die Daten, mehr nicht. Wie diese Daten verarbeitet werden, ist Sache des Programmierers. Ebenso der Inhalt des Datenwortes. So kann das Wort eine long Variable, ein Array aus Longs, ein Array von Zeigern (perform!) oder auch komplexere Strukturen enthalten.
a long ;data 32 Bit Variable b word ;data 16 Bit Variable c long long long ;data Long-Array mit drei Elementen jmptab dup swap drop ;data Sprungtabelle mit drei Funktionszeigern string1 32 allot ;data 32 Zeichen Stringvariable daten 64 allot ;data 64 Byte irgendwas
Wie man erkennen kann, sind Daten in m durch die Farbe Magenta definiert. Schreibt man ein Magenta Wort, so wird im Wörterbuch der Header eines Datenwortes mit dem entsprechenden Namen angelegt. Mit ;data wird dieses Wort im Wörterbuch sichtbar und ist damit auch im Interpreter nutzbar. Nicht zu verwechseln mit ; welches ein exit compiliert, Fehlerprüfungen durchführt (Stackausgleich) und dann erst das Wort im Wörterbuch aktiviert!
Momentan sind also folgende Farben/Funktionen verwendbar:
schwarz Wort wird ausgeführt rot Ausführbares Wort neu erstellen grün Wort wird compiliert blau Zahl --> Stack cyan Zahl wird compiliert (Number Literal) gelb String --> Stringpuffer orange String wird compiliert (String Literal) magenta Datenword neu erstellen grau Kommentar (werden nicht zum Core gesendet)
Wie schon angedeutet, gibt es damit in m kein Infix-Parsing mehr, alle Eingaben sind prinzipiell Postfix, was ich persönlich konsequenter finde. Strings können so je nach Farbe uneingeschränkt compiliert oder interaktiv genutzt werden:
hallo Hallo Welt! .str ; > OK hallo > Hallo Welt! OK Hallo Welt! .str > Hallo Welt! OK
Keine Ahnung ob das jetzt gut ist, aber momentan gefällt es mir: Es ist kompakt, logisch und war einfach zu implementieren.
Nächste Haltestelle: I/O-System und Compilierung von Quelltexten aus einer Datei!