Designentscheidungen
Nicht direkt offensichtliche oder triviale Designentscheidungen, welche getroffen wurden, werden im Folgenden dokumentiert.
Ausführung der Skripte in Form eines Interpreters in TypeScript
Zu Beginn des Projektes wurden verschiedene Ansätze getestet und evaluiert.
Der erste Ansatz war eine automatische Transpilierung der Altskripte in das neue Skriptformat. Hierzu sollten die Altskripte geparsed und dann mittels Transformationen des AST (Abstract Syntax Tree) transpiliert werden. Es stellte sich jedoch heraus, dass das automatische Parsen des Bestandes an Altskripten ein aussichtloses Unterfangen ist. Problematisch ist dass viele Skripte fehlerhaft sind (also ungültige Syntax enthalten). Dies trifft sowohl auf Code zu der ausgeführt wird (und das alte Kalibri-Script dann bspw. die Fehler einfach schluckt oder irgendwas macht), als auch auf Code der niemals erreicht wird und deswegen für einen stupide zeilenbasierten Interpreter wie Kalibri kein Problem darstellt. Es wurde versucht dieses Problem mit einer automatischen Pipeline an Korrekturen zu lösen, allerdings war die Menge an benötigten Korrekturen zu groß. Zudem ist die Syntax von Kalibri extrem schwer zu parsen, was ein zusätzliches Hindernis darstellte. Selbst wenn diese Probleme gelöst worden wären, hätte immer noch ein Transpiler geschrieben werden müssen, was an sich schon sehr aufwändig und komplex ist.
Weiterhin wurde evaluiert, ob ein Refactoring des bestehenden Kalibri-Codes sinnvoll ist. Allerdings ist der Code in einem derartig schlechten Zustand, dass die Erfolgsaussichten zu gering erschienen. Zusätzlich hätte dann einiges an Funktionalität was in der neuen Skript Engine (für Skripte in TypeScript) sowieso vorhanden ist, erneut implementiert werden müssen was ein zusätzlicher Arbeitsaufwand gewesen wäre.
Es fiel letztendlich also die Entscheidung, den alten Kalibri-Skript Interpreter nachzubauen. Da ein “funktionierender” Interpreter in Form des alten Kalibri-Skript Interpreters existiert, muss es rein logisch möglich sein einen solchen nachzubauen. Offen war noch, ob der nachgebaute Kalibri-Skript Interpreter in C++ oder TypeScript sein sollte. Aufgrund des oben beschriebenen Problems der erneuten Implementierung von Funktionalität die sowieso in der neuen Skript Engine vorhanden ist, wurde entschieden den Interpreter für die Altskripte in die neue Skript Engine (eigentlich für die neuen TypeScript Kalibrierskripte) zu integrieren.
Live-Interpretation von Protokollen / “Virtual Interpreter”
Im alten Kalibri-Skript wird während der Kalibrierung ein Kalibrierprotokoll erzeugt. Dieses erzeugte Kalibrierprotokoll ist in Textform und wird in einem zweiten Schritt erst nach vollständigem Abschluss der Kalibrierung durch einen zweiten Interpreter (primär DRUCKEN.CPP) in ein PDF umgewandelt.
Da die Altskripte allerdings das neue, moderne Format an PDF-Kalibrierscheinen mit all seinen Vorteilen nutzen sollten, war klar dass der alte DRUCKEN.CPP Interpreter im Gegensatz zum Rest quasi vollständig ignoriert wird. Zudem ist der DRUCKEN.CPP Code auch in einem äußerst schlechten Zustand, das Layouting geschieht komplett manuell und ist dementsprechend fragil.
Außerdem bestand die Problematik, dass im alten Kalibri erst nach komplettem Abschluss der Kalibrierung das erzeugte Kalibrierprotokoll vom DRUCKEN.CPP Interpreter verarbeitet wurde. Fehlerhafte Ausgabe in das Kalibrierprotokoll fällt also erst nach Abschluss einer ggf. sehr zeitaufwändigen Kalibrierung auf, die dann wiederholt werden müsste.
Eines der größten Probleme aufgrund dessen andere Ansätze ausschieden ist dass komplett freie und manuelle Ausgabe in das Kalibrierprotokoll in Textform möglich war. Von dieser Möglichkeit wurde auch umfangreich Gebrauch gemacht, um Limitierungen des alten Kalibri zu umgehen. Ein Beispiel ist bspw. dass Tabellen teilweise nicht über die Tabellen-Funktionalität des alten Kalibri-Skript in das Protokoll eingefügt wurden, sondern mittels @print manuell Tabellenzeilen in das Protokoll formatiert wurden. Deswegen war der einfache Ansatz Tabellen nur über die Tabellen-Funktionalität in die neuen Protokolle einfügen zu können nicht möglich.
Eine gesonderte Behandlung von @print oÄ wurde im ersten Schritt versucht, wurde aber von der Komplexität her unwartbar und deswegen verworfen.
Zur Erzeugung von Kalibrierprotokollen von Altskripten in der neuen Laufzeitumgebung wurde also eine Live-Interpretation implementiert, auch “Virtual Interpreter” genannt. Dieser Virtual Interpreter wird jedes Mal aufgerufen, wenn eine Zeile in des Kalibrierprotokoll geschrieben wird. Dadurch dass der Virtual Interpreter nicht erst nach Abschluss der Kalibrierung sondern während der Kalibrierung schon das Kalibrierprotokoll verarbeitet, können Fehler frühzeitig erkannt und korrigiert werden.
Der Virtual Interpreter also hat primär die Aufgabe das alte Format der Kalibrierprotokolle zu verstehen und auf das neue strukturierte Format und die neue API für Kalibrierprotokolle umzusetzen. Er behandelt dabei eine ganze Reihe an Fehlern um ein korrektes Protokoll sicherzustellen.
Keine Konvertierung der Kodierung / des Zeichensatzes von Skripten
Die Altskripte sind eigentlich in Windows-1252 kodiert. Es wäre wünschenwert gewesen, diese Windows-1252 kodierten Skripte nach UTF-8 zu kodieren, da dies das native Encoding von Node.JS ist.
Dies ist allerdings nicht möglich, da viele Skripte keine valides Windows-1252 sind. Das liegt daran, dass die Skripte unescapte Bytes aus anderen Kodierungen enthalten. Dies unescapten Bytes sind bspw. IBM437, da die Rohde & Schwarz TS9000 diese Kodierung genutzt hat und Konvertierungsroutinen für IBM437 -> Windows 1252 in Form von Kalibri Altskripten implementiert sind. Diese Konvertierungsroutinen machen sich zu nutze, dass das alte Kalibri Bytes stupide liest und nicht interpretiert.
Werden diese Zeichen jetzt aber nach UTF-8 konvertiert gibt es zwei Probleme:
- Die Zeichen fallen teilweise in den undefinierten Bereich von Windows-1252 (Die Windows-1252 Kodierung hat lücken)
- Bei der Konvertierung werden aus einem Byte mehrere Bytes und die Konvertierungslogik kann nicht mehr funktionieren, da diese auf rohen Bytes arbeitet
Deswegen müssen die Skripte zwangsweise in Windows-1252 kodiert bleiben. Es erfolgt keine Konvertierung der Kodierung der Skripte n der neuen Skript Engine. Nur Ausgaben an Benutzer, ins Protokoll, etc. werden konvertiert, da diese alle UTF-8 benutzen und sonst Sonderzeichen nicht korrekt angezeigt werden würden.
Interne Operation auf Windows-1252 kodierten Daten
Da wie oben beschrieben die Altskripte zwangsweise in Windows-1252 kodiert bleiben müssen, muss auch die Skript Engine intern auf Windows-1252 arbeiten. Hierzu werden Binär-Buffer und entsprechend sparsam Konvertierungsroutinen genutzt (nur dort, wo es wirklich nötig ist, also bspw. Ausgabe von Text an Benutzer).
Nachbildung so nah am original wie möglich, so frei wie nötig
Das alte Kalibri-Skript zeigt zahlreiche unerwartete / überraschende / unlogische Verhaltensweisen. Diese Verhaltensweisen erlauben jedoch teilweise die Umgehung von Limitationen von Kalibri-Skript, weswegen an zahlreichen Stellen davon Gebrauch gemacht wurde. Die Funktionalität vieler Skripte basiert also auf fragwürdigen Verhaltensweisen des alten Kalibri-Skript. Aus diesem Grund müssen alle diese Verhaltensweisen auch genau so nachgebildet werden, um die Funktionalität der Skripte sicherzustellen.
Da der alte Kalibri-Code äußerst undurchsichtig ist (“Spaghetti-Code”) und viele Seiteneffekte usw. hat, ist der einfachste Ansatz um diese Verhaltensweisen nachzubilden schlicht den alten Kalibri-Skript Code möglichst unverändert nach TypeScript zu übersetzen. Es gilt also der Grundsatz “So nah am original wie möglich, so frei wie nötig”.
Eine Ausnahme hiervon bilden Verhaltensweisen, welche im alten Kalibri-Skript schlicht undefiniert sind (bspw. Zugriff auf nicht initialisierten Speicher, Zugriff auf Speicher außerhalb der Grenzen, etc…). Da diese auch im alten Kalibri-Skript als Fehler hätten behandelt werden müssen aber stattdessen oft einfach verschluckt wurden und dieser Umstand für die Skriptentwickler nicht offensichtlich ist, wurde entschieden für klar undefinierte oder fehlerhafte Verhaltensweisen entsprechende Fehlermeldungen zu erzeugen, damit die Altskripte welche diese Verhaltensweisen hervorrufen korrigiert werden können.
Nachbildung der globalen Variablen als quasi-globales State-Objekt
Wie eingangs beschrieben arbeitet das alte Kalibri-Skript fast ausschließlich auf Basis globaler Variablen. Das macht den Code extrem undurchsichtig. Viele Funktionen haben unerwartete oder nicht offensichtliche Seiteneffekte, welche aber für die korrekt Funktionalität essentiell sind.
Das Aufdröseln dieses alten Spaghetti-Codes und der globalen Variablen wäre sehr zeitaufwändig und fehleranfällig gewesen.
Aus diesem Grund wurde entschieden nach dem oben beschriebenen Leitsatz “So nah am original wie möglich, so frei wie nötig” die globalen Variablen als quasi-globales State-Objekt nachzubilden (LegacyState). Dieses quasi-globale State-Objekt bietet also eine gewisse Kapselung gegenüber normalen globalen Variablen bei gleicher Funktionalität. Es ist nur quasi-global, da es explizit an die Funktionen, welche darauf Zugriff benötigen, mitgegeben wird, was aber den Großteil der Funktionen ausmacht.
Dieser Ansatz ist akzeptabel, da das Ziel lediglich eine Nachbildung des alten Kalibri-Skript ist. Es soll keine Weiterentwicklung des alten Skript-Formates oÄ stattfinden.