Cognition: Eine neue Antisyntax-Sprache, die Metaprogrammierung neu definiert
(ret2pop.nullring.xyz)Einführung in die Cognition-Programmiersprache
Problemstellung
- Lisp-Programmierer:innen behaupten, dass sie mit S-Expression-Code und einem funktionalen Makrosystem Metaprogrammierung und verallgemeinerte Systeme bauen können.
- Aber Lisp hat ein fundamentales Problem.
- Da Metaprogrammierung und Programmierung nicht dasselbe sind, besitzt Lisp immer eine strikte Syntax (bestimmte Zeichen für Klammern oder Look-ahead).
- Die linke Klammer teilt Lisp mit, dass es weiterlesen muss, bis die rechte Klammer erreicht ist.
- Dadurch werden linke und rechte Klammern in der Sprache veränderbar festgelegt (konzeptionell nicht grundsätzlich, aber in manchen Implementierungen nicht möglich).
- Besonders wichtig ist, dass die Reihenfolge, in der diese Tokens unterschieden werden, ohne String-Verarbeitung nicht nachträglich geändert werden kann.
- Auch andere Sprachen haben unterschiedliche Verfahren, anhand bestimmter Tokens festzulegen, was als Nächstes gelesen werden soll.
- Dieser Prozess heißt Syntax.
- Cognition geht anders vor und verwendet Antisyntax mit vollständiger Postfix-Notation.
- Das ist ähnlich wie verknüpfte Programmiersprachen, doch diese haben ebenfalls zwei große Probleme:
- Einführung von linken/rechten Klammern (eigentlich Präfixnotation)
- Das Anführungszeichen für Zeichenketten
- Das ist für allgemeine Sprachen ungeeignet.
- Auch in C-Syntax-Implementierungen von Lisp tauchten dieselben Probleme auf (Übermaß an Escape-Zeichen, Erfordernis eines Leerzeichens zur Trennung von Token-Anfang/-Ende).
- Racket hat zwar ein Makrosystem, ist aber zur Laufzeit nicht dynamisch und nutzt Präprozessoren.
- Das ist ähnlich wie verknüpfte Programmiersprachen, doch diese haben ebenfalls zwei große Probleme:
Cognition-Einführung
- Ein Projekt, an dem ich mehrere Monate gemeinsam mit Matthew Hinton gearbeitet habe.
- Ziel ist es, mit vollständiger Postfix-Notation eines der am meisten verallgemeinerbaren Syntaxsysteme zu bauen.
- Das kann Hintergrundwissen zu Grammatik/Tokenisierung/Parsing erfordern, dennoch wird versucht, es verständlich zu erklären.
- Repository: https://github.com/metacrank/cognition
Baremetal Cognition
- Baremetal Cognition ist ähnlich wie Brainfuck, erlaubt aber eine robuste Metaprogrammierung.
- Bootstrap-Codebeispiel:
ldfgldftgldfdtgldf dfiff1 crank f
- Leerzeichen und Zeilenumbrüche sind wichtig.
- In der zweiten Zeile steht ein Leerzeichen nach
df. - In der dritten Zeile befindet sich ein Leerzeichen.
- In der zweiten Zeile steht ein Leerzeichen nach
- Dadurch werden zwei neue Konzepte eingeführt: Delimiter und Ignore.
Tokenisierung
- Ein Delimiter ermöglicht dem Tokenizer die Unterscheidung von Token-Anfang und -Ende.
- Die Liste des Single-Character-Tokenizers ist öffentlich, sodass sie innerhalb von Cognition bearbeitet und gelesen werden kann.
- Ein ignored character wird vom Tokenizer in der ersten Stufe jeder Read-Eval-Print-Schleife vollständig ignoriert.
- Das heißt, beim Start der Tokenerfassung werden die als ignored Zeichen im Satz übersprungen.
- Standardmäßig ist jedes Zeichen ein Delimiter und es gibt keine ignored Zeichen.
- Mit den Delimiter- und Ignore-Listen kann bei gegebenen Zeichen zwischen Blacklist/Whitelist umgeschaltet werden (für Kürze und Praktikabilität).
Falias
- Falias ist eine Liste von Wörtern, die ausgeführt wird, wenn sie auf den Stack kommt.
- Jeder Falias führt den Stack-Top aus (entspricht dem
evalin Stem). fist der Standard-Falias. Er wird nicht auf den Stack gelegt und führt den Stack-Topdaus.dkonvertiert die Delimiter-Liste auf den Stringwert des Worts (also werden nur die Zeichenlnicht als Delimiter ausgeschlossen).- In der Standardumgebung wird kein Wort ausgeführt, außer den speziellen Falias.
Delimiter-Hinweise
- Es gibt eine interessante Regel für Delimiter:
- Wenn in der Tokenisierungs-Schleife ein Zeichen nicht ignored ist, bleibt es als Teil des aktuellen Tokens und es wird weitergesammelt.
- Das ist im Gegensatz zu einem Singlet (womit das Zeichen sich selbst im Token enthält und durch Überspringen die Tokenerfassung beendet).
- Auch der Blacklist-Status lässt sich setzen.
- Delimiter-, Singlet- und Ignore-Listen können blacklisted oder whitelisted werden.
- Standardmäßig gibt es keine blacklisted Delimiter, whitelisted Singlets und whitelisted ignored characters.
- Alle anderen Zeichen werden als Teil des aktuellen Tokens gesammelt, bis die Tokenisierungs-Schleife durch Delimiter- oder Singlet-Regeln gestoppt wird.
Bootstrap-Code fortgesetzt
ldf
lwird zu einem Nicht-Delimiter gemacht.
gldftgldfdtgldf dfiff1 crank f
-
dist ein Delimiter, deshalb wirdglauf den Stack gelegt, und der Faliasfwird aufgerufen, wodurchglzu einem Nicht-Delimiter wird. -
tglwird auf den Stack gelegt und durchdfzu einem Nicht-Delimiter gemacht. -
dtglwird auf den Stack gelegt und durch\ndfwird der Zeilenumbruch (\n) zum einzigen Nicht-Delimiter (der tatsächliche Zeilenumbruch ist im Code enthalten). -
Laut Delimiter-Regeln werden das Leerzeichen und
\nauf den Stack gelegt (beinhaltet das Leerzeichen in der dritten Zeile). -
Es wird noch ein weiteres
\ \n-Word tokenisiert. -
Der aktuelle Stack ist wie folgt (von unten nach oben): 3. dtgl 2. [Leerzeichen]\n
- [Leerzeichen]\n
-
dfmacht\ \nzu einem Nicht-Delimiter. -
ifmacht\ \nzu einem ignored Character (wird beim Tokenisierungsstart ignoriert). -
fführtdtglaus, wobei derdflag-Umschalter gesetzt wird, der die Unterscheidung zwischen Blacklist und Whitelist für Delimiter speichert. -
Nun werden alle Nicht-Delimiter zu Delimitern und alle Delimiter zu Nicht-Delimitern.
-
Schließlich werden Leerzeichen und Zeilenumbruch zu Token-Delimitern und beim Tokenstart ignoriert.
-
Als Nächstes wird
1tokenisiert und auf den Stack gelegt; danach wird das Wortcranktokenisiert und durchfausgeführt (1-Token gilt hier als Zahl, aber in Cognition ist alles ein Word). -
Bootstrapping-Sequenz abgeschlossen! Was
crankmacht, wird im nächsten Abschnitt erklärt.
Bootstrapping-Zusammenfassung
- Cognition kann die Tokenisierung programmatisch dynamisch ändern.
- In anderen Sprachen nicht möglich.
- Für Fremdsprachen kann innerhalb von Cognition der Tokenizer programmiert und die gewünschte Tokenisierung erreicht werden.
- Durch Postfix-Notation und ohne Look-ahead ist das möglich.
- Es ist nicht nötig, mehr als ein Token zu parsen, bevor ein Ausdruck ausgewertet wird.
- Mit Falias kann ein Wort ausgeführt werden, ohne Prefix-Wörter oder Built-ins zu verwenden.
Crank
- Das metacrank-System erlaubt es, wie Token, die auf den Stack gelegt werden, standardmäßig ausgeführt werden.
- Das Wort
cranknimmt eine Zahl als Argument und führt den Stack-Top aus, sobald auf den Stack n Wörter gelegt wurden. - Beispielcode (bei gesetztem
crank 1):
5 crank 2
crank 2 crank
1 crank unglue swap quote prepose def
- In der Umgebung mit
crank 1ist die Verwendung vonfbeim Token-Auswerten deaktivierbar.- Pro tokenisiertem Token wird eins ausgewertet.
- Da eine Syntax über Leerzeichen und Zeilenumbruch programmiert wurde, lässt sich der Code intuitiv lesen.
- Der Code beginnt mit dem Versuch,
5auszuwerten (da es kein Built-in ist, wertet es sich selbst aus). crankwird so eingestellt, dass es ausgeführt wird, sobald 5 Wörter auf den Stack gelegt wurden.2crank,2,crank,1werden alle auf den Stack gelegt (weilcrankmitcrank 5als Built-in konfiguriert ist und deshalb nicht ausgeführt wird): 4. 2crank 3. 2 2. crank- 1
crankist das fünfte Wort und wird ausgeführt (crank 1ist gesetzt).unglueist ein Built-in, das den Wert des obersten Wortes im Stack nimmt, wie durch1genutzt.- Es nimmt also den Funktionszeiger für das mit
crankverbundene Built-in.
- Es nimmt also den Funktionszeiger für das mit
- Der Stack ist jetzt:
3. 2crank
2. 2
- [CLIB]
- CLIB verweist auf den Funktionszeiger des Built-ins
crank.
swapausführen: 3. 2crank 2. [CLIB]- 2
quote(Built-in, das den Stack-Top quotiert) ausführen: 3. 2crank 2. [CLIB]- [2]
prepose(ähnlichcomposein Stem, aber vorn abgelegt und so genannt VMACRO) ausführen: 2. 2crank- ([2] [CLIB])
defaufrufen- definiert das Wort
2crank, das2auf den Stack legt und den Funktionszeiger auf das Built-incrankaufruft.
- definiert das Wort
- Was VMACRO ist sowie der Unterschied zwischen Cognition-Stack und Stem-Stack muss noch erklärt werden.
Unterschiede zu Stem
- Auf dem Stem-Stack können Wörter direkt auf den Stack gelegt werden.
- In Cognition wird ein Wort nicht ausgewertet, sondern in einen Container gelegt und auf den Stack gelegt.
- In Stem arbeitet
composemit einem Word (oder einem Container mit einem einzelnen Word) und einem anderen Container. - Das macht die Cognition-API konsistenter.
- In Stem arbeitet
- Auch Wörter wie
cdnutzen dieses Konzept.
Makros
- Ein weiterer Unterschied zwischen Stem-Quote und Cognition-Containern.
- Bei der Makroauswertung wird alles innerhalb des Makros ausgewertet und
crankignoriert. - Wenn ein Wort gebunden wird, bei der Auswertung des Wortes
1 Kommentare
Hacker News-Kommentare
Ein paar zentrale Punkte lassen sich wie folgt zusammenfassen: