Die Textengine

Die Textengine ist ein in Entwicklung befindliches, experimentelles Programmiersystem im Rahmen meiner Forschung über den Text. Experimentiert wird hier mit dem Einsatz einer Text-Schicht als Basis für das Zusammenspiel und die Integration von verschiedenen Komponenten.

Man kann sich die Textengine wie eine Art Programmiersprache vorstellen. Nur geht es hier nicht darum, sich auf eine bestimmte Sprache festzulegen, sondern darum, Ausdrücke in verschiedenen Sprachen und Notationen auf ein und dieselbe universelle Datenstruktur zurückzuführen, nämlich auf „Text”.

Assoziazionen, die einem spontan in den Kopf kommen: Lisp, Rebol, domain-specific languages, code generation, aspect-oriented programming, intentional programming. Das ist alles richtig, nur zu kompliziert und eng gefasst. Mit dem schlichten Begriff des Textes als symbolischen Ausdrucks —so meine These— ergeben sich automatisch all diese Möglichkeiten und viele mehr.

Das Vorhaben beschreibe ich in diesem Artikel: The Text Engine

Notizen

21.08.17 Nach der Verbesserung der Equivalenzauflösung ist es zu Tage getreten, dass es zweierlei Abfragen gibt. Manchmal will man alle Texteinheiten besuchen, die eine Bedingung erfüllen, unabhängig davon, ob es Wiederholungen gibt. Zum Beispiel beim Durchlaufen des Strings „aba” will man drei Einheiten vom Typ char erhalten, wovon die erste und die dritte dieselbe ist. Manchmal braucht man aber nur die unterschiedlichen Einheiten, die eine Bedingung erfüllen, so im mit der Textengine verteilten Beispiel query, wenn man Familienmitglieder erfasst und dann nach allen weiblichen Personen fragt. Da will man jede Person nur einmal haben, egal wie oft diese in verschiedenen Einheiten eine Rolle spielt. Nach etwas Überlegung scheint mir, dass die Equivalenzen richtig definiert und eingesetzt und beide Fragestellungen sinnvoll sind, so habe ich die Cursors mit einer Option DISTINCT ausgestattet.

18.08.17 Den Webserver habe ich in Betrieb genommen, d.h. die Textengine arbeitet ab sofort dauerhaft zum ersten Mal an einer zwar bescheidenen, aber realen Aufgabe. Eine Konfigurationsdatei sieht etwa so aus: ~domain =de { ~domain =text-engine; ~domain =philosophisches-lesen { ~redirect "http://www.philosophisches-lesen.de"; ~domain =www { ~gone =philosophiewissenschaft.html; ~gone =korpus { ~gone =kant { ~gone =krv { ~gone =textgrundlage.html; ... Wenn eine Domain einfach zu bedienen ist, wie oben text-engine.de, reicht es, sie einmal zu nennen (damit sie nicht als unbekannt abgewiesen wird). Man kann für bestimmte Domains eine Weiterleitung einrichten, so werden hier alle Aufrufe auf http://philosophisches-lesen.de/<URL> mit einem 301 nach http://www.philosophisches-lesen.de/<URL> weitergeleitet. Ein Abruf von http://www.philosophisches-lesen.de/korpus/kant/krv wird mit einem 410 Gone quittiert. Interessant ist hier das Spiel mit den Parserkonfigurationen während der Verarbeitung einer Anfrage. Der Parser für die Host-Header ist als little-endian und mit dem Punkt als Separator, der Parser für die URI hingegen als big-endian mit dem Strich als Separator konfiguriert. Damit kann jede Anfrage sehr leicht verarbeitet werden, indem man die Textengine veranlasst, Host und URI als Namen zu parsen und nach dem Namen zu suchen. Diese Verwendung der Parserkonfigurationen kommt mir sehr natürlich und mächtig vor. Ich habe mir als Idee notiert, in Zukunft die Konfiguration jeweils im Namensraum zu verankern, so dass der Namensparser automatisch jeweils je nach Kontext die passende Konfiguration auswählt.

18.08.17 Es ist immer eine Genugtuung, wenn aus dem „was hast du jetzt wieder angestellt?” ein mit Überraschung und gar Bewunderung ausgesprochenes „stimmt, hast du Recht!” wird. Der Kleine hatte es mit seinem beschränkten Wissen besser als einer selbst mit all dem gefühlten Wissen erfasst. Ich hielt es zunächst für einen Bug: =Asian elephant ~elephant { ~veneration object.religion #Hinduism; }; ~veneration object #Asian elephant { ~religion #Hinduism; =Hinduism ~world religion; }; Wie kann der Hinduismus ein Kind des asiatischen Elefanten sein? Dieser Unsinn kommt aus der verkehrten Gleichsetzung des Verehrungsobjekts mit der Elefantenspezies. Der Elefant hat laut obiger Definition als Verehrungsobjekt den Hinduismus als Kind. Das System tut das richtige, nur die Definition ist falsch. Die logisch richtige Definition lautet: ~=veneration object { ~=species:species; ~=religion :world religion; }; ~veneration object { ~species #Asian elephant; ~religion #Hinduism; }; Und diese verhält sich erwartungsgemäß: =Asian elephant ~elephant; =Hinduism ~world religion; ~veneration object { ~species #Asian elephant; ~religion #Hinduism; };

17.08.17 Die Zeit der ersten realen Anwendung der Textengine ist bereits angekommen. Schön, ich hätte dies in dieser frühen Phase der Entwicklung nicht erwartet. Ich habe meinen persönlichen Webserver mit Konfigurationsdateien ausgestattet. Diese steuern, welche Domains bekannt sind, das Setzen von Weiterleitungen, und dergleichen, was bisher programmatisch in C gemacht wurde. ~domain =cat { ~domain =hervada-sala { ~domain =francesc { ~domain =www { ~redirect "http://francesc.hervada-sala.cat"; }; }; }; }; Wie es mit der Realität halt ist, eine Reihe von Anpassungen waren nötig, von vorhersehbaren Namenskolisionen mit Konstanten wie OK oder Funktionen wie set, die ich nun zähneknirschend mit dem Präfix te_ versehen habe, bis krasse Programmfehler, mit denen ich mich noch zu beschäftigen habe. Der Aufwand ist jedoch völlig gerechtfertigt, nicht für den Webserver, sondern für die Textengine, die auf jeden Fall verstärkt daraus ausgehen wird.

12.08.17 Neben der nie enden wollenden Arbeit am Bugfixing habe ich seit gestern auch die Veröffentlichung vorbereitet: den Quellcode verpackt und mit einer Lizenz (GPL) versehen, einige Beispiele beigefügt. Bei der beispielhaften Nutzung der Bibliothek libtextengine.a fällt mir gleich auf, wie wenig reif sie dafür ist. Man müsste an den Schnittstellen feilen, sie erfordern viel Hintergrundwissen und sind zu kompliziert für einen Einsteiger, abgesehen von der nicht existierenden Dokumentation. Doch daran werde ich nicht arbeiten können, die Textengine ist primär nicht für die programmatische Nutzung ausgelegt, sondern für den Einsatz der Shell, und in diese Richtung ist noch sehr viel Arbeit zu leisten.

09.08.17 Bei der Zuweisung des Namens session für jede Sitzung bin ich auf das Problem gestoßen, dass es nicht möglich ist, mehrere Einheiten unter ein und dieselbe übergeordnete Einheit (bzw. SELF) zu definieren, die denselben Bezeichner haben. Denn sonst hätte der entsprechende Namensraum doppelte Bezeichner. Um es umsetzen zu können, habe ich zwei Optionen erwägt: virtuelle und lokale Namen. Ein virtueller Name wäre eins, der nur innerhalb der Sitzung sichtbar ist, in der er definiert wurde. Dafür hätte man das System erweitern müssen. Vielleicht kommt das in Zukunft, aber ich werde die Komplexität scheuen und es hinausschieben, so lange man darauf verzichten kann. Ein lokaler Name ist eins, der nur für dessen Folgeeinheiten sichtbar ist. Es hat sich ergeben, dass lokale Namen mit den aktuellen Mitteln umsetzbar sind, nämlich so: ^~ { ~= session; {...A...}; }; ^~ { ~= session; {...B...}; }; Hier haben wir zwei Bereiche A und B, in denen ein und derselbe Name session auf verschiedene Einheiten verweist, ohne dass dieser Name außerhalb von diesen Bereichen zugänglich ist. So lassen sich also generell lokale Namen definieren, indem man eine selbstbezogene, namenlose Einheit und in ihr die benannte Einheit definiert.

09.08.17 Die reine Symbolsprache ist bislang per Design für die Referenzen auf Namen angewiesen. Die einzigen impliziten Referenzen sind für übergeordnete Einheiten (innerhalb von geschweiften Klammern) und Selbstreferenzen (mit leeren Namen, d.h. mit Angabe eines Referenzgenus ohne Referenzziel). Ich habe nun jede Sitzung mit dem Namen session versehen. Anlass war, in Ausdrücken wie folgendem einen eindeutigen Namen bauen zu können: ~= type; ~= function { ~= type : type; ~= arg : session.type; }; Ohne den Kennzeichner session würde der Typ von arg stattdessen der untergeordnete function.type sein. Man hätte hier sicherlich auf den generellen Namen verzichten und bei Bedarf jeweils eine Lösung finden können, zB nur zum Zwecke der Disambiguierung eine übergeordnete Einheit zu definieren oder die Erfassungsreihenfolge der Einheiten umzukehren. Mir erscheint jedoch, dass es besser ist, wenn das System eine allgemein verfügbare Möglichkeit der Disambiguierung bietet. Die Sitzungen zu benennen, hatte mir schon lange vorgeschwebt und ich bin mir auch ziemlich sicher, dass der Name in Zukunft auch für andere Zwecke benutzt werden wird. Ich lasse also — nicht im Modul session, mit dem sich nach wie vor eine leere Sitzung erstellen lässt, sondern im Modul system, das eine gewisse Funktionalität mit sich bringt — eine standardmäßige Sitzung erstellen, die eine mit dem (via config.h änderbaren) Namen session versehen ist und als Kontext für die während der Sitzung zu erfassenden Einheiten gilt.

07.08.17 Ich tue es ungern, die Symbolsprache mit weiteren Funktionen zu versehen, denn sie sollte so klein wie möglich bleiben. Bisher war sie auf set, enter und leave beschränkt. Jetzt habe ich sie mit einer goto-Funktion ausgestattet. Ohne diese Funktion lassen sich in Prinzip exakt dieselben Texte bauen, nur erfordert dies, entweder jede Einheit an genau einer Stelle vollständig zu definieren (was unpraktisch ist, wenn man anderswo definierte Einheiten ergänzen will), oder aber man muss auf geschweifte Klammern verzichten und jedesmal die übergeordnete Einheit explizit angeben (was weitschweifig ist und sich im sonstigen Erscheinungsbild der Symbolsprache nicht gut einfügt). Ich habe eine goto-Funktion eingeführt, die die Sitzung an einer gegebenen Einheit positioniert, so dass sich diese durch enter-Ergänzen (und auch programmatisch via session->lastid abfragen) lässt. Die Syntax der Symbolsprache habe ich nur so geändert, dass jetzt eine Einheit mit einer Symbolreferenz als alleinige Referenz zugelassen und als Goto interpretiert wird. Diese Art Einheiten waren bisher ungültig, denn eine Rolle muss immer sonst angegeben werden. Diese syntaktische Unaufälligkeit der Goto-Funktion gefällt mir in Prinzip, auch wenn die mangelnde Offensichtlichkeit einen negativen Lerneffekt hat. Weitere Zeichen hinzuzufügen, habe ich erwägt aber der Einfachheit halber abgewiesen.

04.08.17 Wie es so beim Programmieren ist, bin ich unbemerkt und eigentlich ungewollt in die Untersuchung des Performanceproblems eingestiegen. Die Auflösung der Equivalenzen für jedes Zeichen, wenn es durchlaufen wird, hat sich als Achillesferse der jetzigen Implementierung herausgestellt. Wie ich an einer verteilten Textengine träume, in der Textanbieter die eigenen Inhalte selbst implementieren! Da würden solche Engpässe gar nicht auftreten. Doch für diese Version der Textengine fasste ich den Entschluß, ein monolytisches System zu bauen, um überhaupt erst einmal etwas auf die Beine zu stellen, und dabei muss ich bleiben. So habe ich notgedrungen vorläufig die Auflösung der Equivalenzen für Einheiten vom Typ system.char kurzerhand unterdrückt, oder, genauer ausgedrückt, habe ich sie via Konfigurationsparameter unterdrückbar gemacht. Das hat überhaupt keine Auswirkung auf das Ergebnis, weil solche Einheiten keine untergeordneten Einheiten haben, aber eine drastische Auswirkung auf die Performance. Das Thema ist nicht abgehakt, ich muss es beobachten und über eventuelle Alternativen überlegen.

02.08.17 Es war sehr opportun, jetzt eine solide Betaphase anzugehen. Es ist zwar teilweise hart, aber die Probleme, die ich jetzt im Keim löse, werde ich später nicht mehr in komplizierteren Zusammenhängen antreffen. Beim durchlaufen von Cursors hatte ich zwar die symbolischen Equivalenzen beim matching berücksichtigt, jedoch nicht beim Durchlaufen der untergeordneten Einheiten. Ich war mir während der Erstentwicklung nicht ganz im Klaren darüber, wann und wie die symbolischen Equivalenzen aufzulösen sind. Ich habe mich jetzt dafür entschieden, sie erstmal immer aufzulösen (es sei denn, man kommt in einen Teufelskreis). Die Erfahrung wird mich ggf. eines besseren Belehren. Die Umsetzung war alles andere als leicht. Endlich scheint das jetzt richtig zu funktionieren. Die Tests stellen mich aber nicht zufrieden: die Performance des Systems ist für größere Datenmengen im Eimer. Vielleicht nicht sofort — denn andere Fehler sind mir auch schon inzwischen aufgefallen, die konzeptionelle Probleme und daher wichtiger zu lösen sind — aber irgendwann einmal werde ich mich damit beschäftigen müssen.

31.07.17 Ich bin am ausführlich Testen, es läuft gut. Ich habe mehrere reale Fälle aus dem Universaltext-Interpreter in Symbolsprache umgewandelt und sie durch die Shell verarbeiten lassen. Die Umwandlung war übrigens ganz leicht, indem ich ein etwa 100-zeiliges Modul für den Universaltext-Interpreter geschrieben habe, das die Funktion tsymbol für die symbolsprachliche Ausgabe bereitstellt. Somit brauche ich nur die alten Projekte mit dem Interpreter zu laden, save out.sym do tsymbol aufzurufen und voilà da steht die Quelldatei bereit für die neue Shell — wobei, zugegeben, die erstellten Dateien mal einige wenige händische Eingriffe nötig haben. Sämtliche Fälle ließen sich ohne Fehler verarbeiten, insofern sieht es mit der Textengine momentan sehr gut aus. Doch die Performance hat mir zunächst einen Schrecken verpasst. Ich habe zwei Fälle mit jeweils ca. halbe Milion Texteinheiten, die 9 Minuten für die Verarbeitung gebraucht haben. Zum Glück konnte ich schnell herausfinden, dass dies an der Funktion getnext lag, die das Textkorpus durchläuft: Die nächste Texteinheit wurde sequenziell ermittelt. Habe Verweise auf die nächste Texteinheit in der Textstruktur gesetzt und getnext überarbeitet und die Ausführung läuft nun in jeweils unter 3 Sekunden. Schön!

29.07.17 Die Shell ist da, ein aufregender Augenblick! An sich leicht und schnell zu implementieren — zumindest in einer Basisversion — und doch, welchen Spaß sie bringt, und auch welchen Nutzen, jetzt schon um spontan Kleinigkeiten zu testen! [francesc@ew15 dbg]$ tesh - Text Engine Shell v0.3 alpha; http://text-engine.de (c) 2017 Francesc Hervada-Sala te> ~= Gruß : string; te> ~Gruß "Hallihallo!"; te> ^D ^~ { ~=Gruß :string; ~Gruß { ~char #chars.H; ~char #chars.a; ~char #chars.l; ~char #chars.l; ~char #chars.i; ~char #chars.h; ~char #chars.a; ~char #chars.l; ~char #chars.l; ~char #chars.o; ~char #chars.!; }; }; [francesc@ew15 dbg]$

26.07.17 Das in der letzten Notiz Erwähnte gilt im Allgemeinen, im konkreten Fall der Strings sollten jedoch solche Konstrukte vermieden werden, denn sie entsprechen nicht der beabsichtigten Semantik. Man muss das System so ausbauen, dass eine zusätzliche Konsistenzregel eingehalten wird, nämlich: Jede Instanz von einem char muss zu genau einer Instanz unter system.chars symbolisch verlinkt sein. Das gilt auch für die Zeichen unter system.chars: sie sind jeweils mit sich selbst equivalent und dürfen zu keinem anderen geschwister-Zeichen equivalent sein.

26.07.17 Es ist ein auf Anhieb komisch anmutendes Verhalten. ~=blah :string { ~char #blah; }; Man erwartet spontan, dass dies einen Integritätsfehler auslöst, weil man meint, dass ein string allein aus char und nicht aus string bestehen kann. Doch das wird akzeptiert und folgendermaßen erfasst: [13] =blah ^5 ~SELF :string #16 [16] ^blah ~char :char #blah Der Satz meint: Die Texteinheit, die die Rolle char spielt, ist symbolisch equivalent zu einer (anderen) Texteinheit, die den Typ string hat. Das ist in Prinzip zulässig. Wenn man keinen Typ explizit angibt, wird die Rolle als Typ angenommen. Ein INTEGRITY-Fehler wird hingegen damit ausgelöst: ~=blah :string { ~char :string #blah; }; Dieses spontan befremdliche Verhalten entsteht daher, dass es erwünscht ist, symbolische Equivalenz zwischen Einheiten von verschiedenen Typen zu haben. Würde man eine Integritätsregel einführen, die nur Links von Einheiten desselben (oder verwandten) Typs zulässt, so wären solche Sätze nicht mehr möglich. Doch die mehrfache Typisierung ist ein unentbehrliches Merkmal. Ich lasse es erst einmal so und beobachte, wie sich das mit der Zeit auf die reale Nutzung auswirkt.

23.07.17 Bevor ich die Transformationen angehe, will ich das bisher implementierte sichern. Ich habe über die Roadmap des Projekts nachgedacht und mir die folgende Herangehensweise vorgenommen. Eine erste Betaversion 0.3 wird in der Lage sein, Symbolsprache zu parsen, damit Text zu erfassen, zu durchlaufen, und dessen Integrität zu gewährleisten. Alles nötige ist im ersten Anlauf bereits implementiert, es muss noch ausführlich getestet und ausgereift werden. Der nächste Schritt wird eine Betaversion 0.6 sein, die ausführbare Texteinheiten (sogenannte Text-Transformationen) unterstützt. Die Betaversion 0.9 wird darüber hinaus eine Skriptsprache für das Beschreiben von Code bereitstellen. Darauf wird eine vermutlich längere Phase der Ausreifung und Erweiterung zu einer produktiv einsetzbaren Version 1.0 führen.

23.07.17 Formatieren ist die Umkehrfunktion zu Parsen. Während man beim Parsen ausgehend von einer Zeichenkette Texteinheiten erfasst, erhält man durch Formatieren aus Texteinheiten eine Zeichenkette. Wenn man einen Ausdruck A parst und die erfasste Einheit wiederum formatiert, so erhält man einen Ausdruck B mit demselben semantischen Gehalt. Nur sind A und B nicht unbedingt dieselbe Zeichenkette. Jetzt ist es mir aufgefallen, dass der Unterschied sich nicht auf semantisch irrelevante Zeichen wie etwa Leer- od. Anführungszeichen beschränkt. Es gibt einen Unterschied bei der Angabe der Symbolreferenzen, d.h. der Referenzen mit der Vorsilbe #, die auf semantisch equivalente Texteinheiten verweisen. Symbolreferenzen werden nicht einzeln im Textspeicher so beibehalten, wie sie hinzugefügt worden sind, sondern werden beim Hinzufügen Equivalenzklassen gebildet und referenziert. Der Symbolformatierer gibt bei Symbolreferenzen einen bestimmten Vertreter der Equivalenzklasse so aus, dass die Semantik korrekt und vollständig wiedergegeben wird.

22.07.17 Ich hatte es schon lange vor: den Kontext, d.h. die Reihe der Texteinheiten, innerhalb derer man sich jeweils befindet, nicht global zu behandeln, sondern verschiedene Kontext-Objekte zu haben, die wie bisher für die Operationen enter und leave, aber jetzt auch für set und link angewendet werden. Bei der Implementierung von Transformationen hat sich das als unabdingbar herausgestellt und ich habe es nun angegangen. Es ist eine größere Reform geworden. Ich habe den Typ session eingeführt, der nicht nur den Kontext enthält, sondern auch die use-Direktiven, d.h. die Liste der Namensräume, die für die Namensauflösung in der aktuellen Sitzung berücksichtigt werden sollen. Da hat man tief ins System hinein eingreifen und bei Parser- und Formatieragenten sogar eine größere Umschreibung vollziehen müssen. Es war eine langwierige, müselige Großbaustelle der Sorte, bei denen die syntaktische Unnachgiebigkeit des Compilers sowie die automatisierten Regressionstests nicht nur die Arbeit stark erleichtern, sondern auch deren Geschwidigkeit und Qualität ungemein steigern.

19.07.17 Die letzten Tage habe ich an der Integration des Systems gearbeitet und Bugs behoben. Das Ganze nimmt allmählich Gestalt an! Das System kann Symbolsprache ohne Einschränkungen inklusive eingebetteter Strings parsen und erfasst entsprechende Texteinheiten mit Referenzen aufeinander, deren Integrität es gewährt. Ich hatte eben die Freude, das Beispiel aus meinem Artikel über die Textengine vollständig, erfolgreich parsen zu lassen, und zwar mit erstaunlich wenig Problemen. ~=species { ~=common name :string; ~=scientific name :string; }; ~=mammal :species; ~=elephant :mammal; =African elephant ~elephant { ~common name "African bush elephant"; ~scientific name "Loxodonta africana"; }; ... Es gibt nur einen syntaktischen Unterschied, das Semikolon ist im realen System als Abschluss der Einheit auch vor dem Zeichen „}” erforderlich. Das ist eine bewusste Entscheidung: Es scheint mir besser, eine klare Regel zu haben, die konsequent überall gilt. Es hat einen Lerneffekt, man kommt zu deutlichen Begriffen. Es vermeidet auch, Semikolon hinzufügen od. entfernen zu müssen, wenn man den Code überarbeitet und neue Einheiten hinzufügt oder entfernt, eine Operation, die ich in anderen Sprachen häufig erlebe und mich immer ärgert, weil unnötiger, zweckloser Aufwand. Der andere Unterschied ist, dass im System bisher der Datentyp Ganzzahl nicht implementiert wurde. Habe für den Test ^system ~=integer :string; hinzugefügt. Es ist schön, zu sehen, dass dieses Beispiel —mit dem ich beim Schreiben des Artikels viel Zeit verbracht habe— so geparst werden kann. Ich habe sogar einen Moment erwägt, ob ich das System als Version 0.1 freigeben sollte, mich jedoch dagegen entschieden, werde mit Version 0.5 anfangen, wenn es mindestens Transformationen select und print gibt und man das System für etwas nützliches einsetzen kann.

16.07.17 Heute habe ich den Stringformatierer implementiert und die bisherigen Namens- und Symbolformatierer angepasst, um ihn einzusetzen. Der Code hat einiges an Modularität und Kopplung, generell an Reife gewonnen, wie übrigens die letzten Tage auch. Eins gibt es aber, das ich gern anders gemacht hätte. Der Stringformatierer ist dafür zuständig, ggf. Anführungszeichen um eine Zeichenkette zu setzen. Die Konfiguration für Strings bietet da bereits einige Möglichkeiten. Nur ist innerhalb eines Namens der Ebene-Separator (also etwa der Punkt in system.string) auch ein Zeichen, das Anführungszeichen erzwingt, wenn es zum Bezeichner gehört und nicht als Separator fungieren soll. Genauso erzwingen innerhalb eines Ausdrucks in der Symbolsprache geschweifte Klammer und Semikolon auch Anführungszeichen. Doch wie signalisiert man beim Formatieren in der Symbolsprache dem Stringformatierer, dass er solche Zeichen schützen soll? Ich habe dies dadurch gelöst, dass ich bei der Stringkonfiguration einen Parameter quotedchars hinzugefügt habe, in dem man solche Zeichen deklariert. Die übergeordneten Formatierer ergänzen dann die Konfiguration des Stringformatierers entsprechend. Erwägt hatte ich auch stattdessen in den übergeordneten Formatierern eigene Agenten zu definieren, die den Stringformatieragenten anzapfen und ggf. den Konfigurationsparameter quoted aktivieren — habe es aber abgewiesen; es ist irgendwie auch unschön und definitiv zu barock. Hierzu fällt mir vielleicht später einmal eine elegantere, ökonomische Lösung.

15.07.17 Ich habe den Stringparser implementiert. Die Logik für die Behandlung von Anführungs-, Escape- und Leerzeichen habe ich vom Namensparser übernommen. Darauf hin habe ich den Namensparser umgeschrieben, so dass er nun auf den Stringparser basiert. Damit vermeidet man nicht nur redundanten Code, sondern wird der Namensparser kompakter und übersichtlicher. Den Stringparser habe ich unter Nutzung von Agenten implementiert, so wie der Symbolparser, nur verarbeiten die Agenten hier nicht Tokens, sondern Zeichen. Die Agenten zeichnen sich allmählich als solides Entwurfsmuster für Parser ab. So wird hier ein und derselbe Stringparser für verschiedene Zwecke eingesetzt, zum einen für das Speichern von Zeichen in das Textkorpus (der Agent führt für jedes gelesene Zeichen ein set-Befehl aus) und zum anderen für das Erfassen der einzelnen Bezeichner für den Namensparser (der Agent fügt jedes gelesene Zeichen zum internen Puffer des Namensparsers hinzu).

14.07.17 Größere Überarbeitung der Formatmanager vollzogen. Alles, was mit Agenten und Token zu tun hat, wurde aus der Schnittstelle verbannt und zu einer rein internen Rolle im Symbolformat relegiert. Formatierer werden nun Analog wie Parser behandelt: der Formatmanager ergibt ein Formatiererobjekt, das eine Methode zum Formatieren von einzelnen Einheiten bietet. Noch übrig bleibt, neue Testfälle für den Symbolformatierer zu schreiben, denn die alten sind aufgrund der völligen Umbearbeitung nicht mehr zu gebrauchen und mussten gelöscht werden.

13.07.17 Ich hatte es geahnt, Parser dürfen nicht generell auf Agenten aus ausgelegt werden. Ich implementiere gerade den Stringparser, der für jedes Zeichen eine Einheit ~char :char #... hinzufügen muss. Nur sind die Referenzen dieser Einheit durch Ids bekannt, denn ich befinde mich innerhalb des Formatmanagers für Strings, der alle referenzierte Einheiten selbst angelegt hat. Es wäre unsinnig, einen Token-Ausdruck zu generieren, deren Verweise der Agent auflösen muss. Hier kann und daher muss der Parser direkt mit set() die Einheiten generieren. Der ursprüngliche Ansatz mit den Agenten ist daher falsch. Natürlich sind Agenten nützlich für das Symbolformat und später wohl für Skriptformat und eventuell andere, nur müssen diese bei der Instanziierung des Parsers angegeben werden, und nicht beim Holen des Parsers aus dem Formatmanager.

05.07.17 Berichtige: Anführungszeichen sind in eingebetteten Ausdrücken relevant für alle beteiligten Parser. Zum Beispiel, wenn der Symbolparser den Namensparser veranlasst, eine Zeichenkette zu parsen, so müssen beide berücksichtigen, ob die Zeichenkette in Anführungszeichen steht oder nicht: Der Namensparser wird etwa die Leerzeichen anders behandeln, der Symbolparser wird etwa auf die Zeichen für Referenz oder Datensatzende reagieren oder aber sie dem Namensparser weiterreichen.

04.07.17 Parser (generische Funktionen wie Symbolsprachenparser) umgeschrieben, so dass sie jetzt den Agenten selbst bei jedem erfassten Token aufrufen. Es gab bisher (als Überbleibsel einer früheren Implementierung) eine separate Funktion, um den Token zu erfragen, nur gibt es keine regelmäßige Entsprechung zwischen dem Parsen eines Zeichens und dem Ausgeben eines Tokens, was die Logik der aufrufende Funktion unnötig verkomplizierte.

04.07.17 Ich frage mich, ob der Ansatz mit den Agenten der richtige ist. Ich habe Zunächst die Parser für die Arbeit in Kombination mit sogenannten Agenten ausgelegt, so dass ein Parser nur Tokens herausgibt, wobei ein Token der geparste Ausdruck einer Einheit ist (eine Reihe von Referenzen, die jeweils mit einem geparsten Namen angegeben werden). Der Agent hat dann Tokens entgegengenommen und damit etwas unternommen. Der einzige Agent, den ich nicht für Debugzwecken implementiert habe, ist ein „Erfasser”, der die Einheiten in den Text einspeist. Die Idee hinter dem Ansatz ist, den Parser auf die Erfassung des Strings zu beschränken, und sie in einer internen Repräsentation weiterzugeben. Mir kommen jetzt Zweifel auf, ob die Parser nicht die Funktion mitübernehmen sollen, den erfassten Ausdruck in den Textkorpus zu erfassen. Nicht nur, weil vielleicht aus Performancegründen es nicht ratsam ist, eine generelle interne Repräsentation zu nutzen, sondern auch grundsätzlich, vielleicht sollte der Parser selbst den String konsumieren.

03.07.17 Die Parser sollen nicht mit einem FILE * aufgerufen werden, sondern mit einem zu verarbeitendem wchar_t. Mit einem Stream wäre es zwar möglich, dies erforderte jedoch, für jeden Parser einen Custom Stream zu implementieren, der die vom untergeordneten Parser zu verarbeitenden Zeichen weitergibt. Wenn die Parser zeichenweise die Eingabe verarbeiten, ist die Implementierung zwar ähnlich aber insofern einfacher, als für einzelne Zeichen statt für Puffer beliebiger Länge ausgelegt.

03.07.17 Das gilt wiederum auch für das Formatieren. Nicht der Namenformatierer setzt die Anführungszeichen, sondern der Formatierer des Ausdrucks, in dem die Namen eingebettet sind.

02.07.17 Namen können — unter Umstände müssen — in Anführungszeichen gesetzt werden. Doch gehören die Anführungszeichen nicht dem Namen an, sondern werden sie von dem übergeordneten Ausdruck zur Abgrenzung benötigt. Infolgedessen soll nicht der Namenparser die Anführungszeichen behandeln, sondern der übergeordnete Parser, der den Namenparser aufruft.

Textengine

Dieser Webauftritt wurde zuletzt am 22.08.17 bearbeitet

img/feed.png RSS-Feed

Links

Meine Seite über Textforschung

Meine private Homepage