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!