Das Leibniz® System [5] entstand 1984 und mutierte 1985 zu einer der ersten hypertextbasierten Software-Entwicklungsumgebungen. Der TLSI, oder Token-Listen-Subroutinen-Interpreter, ist ein logisches Maschinenmodell, das aufgrund seiner Struktur prädestiniert ist für Hypertext. Zwischen 1984 und 1992 wuchs das Leibniz-System von einem Minimal-Kernel mit 32 Kilobyte und dem Komfort eines interaktiven Assembler-Debug-Monitors zu einer Software-Entwicklungsumgebung mit insgesamt ca. 10.000 Funktionen oder Methoden in 100.000 Zeilen Source Code und noch einmal soviel Kommentar und Dokumentation, insgesamt ca. 6 MByte. Die Hauptversion ist unter DOS auf einem PC-AT realisiert und umfaßt 550 KB compiliert. Das gesamte System ist in der rapid-prototyping Shell-Sprache LPL (Leibniz Programming Language) erstellt. Die Shell bedient sich einer Philosophie des Benutzer-Interfaces mit eigenem (PC-Character-) Windowing, frei benutzer-konfigurierbaren Menus, Hypertext-basiertem Editor, und kontextsensitiver Hilfs-Funktion. Die Leistungsfähigkeit des Leibniz-Konzepts läßt sich daran ermessen, daß es möglich war, das System in Teilzeitarbeit im wesentlichen mit einer einzigen Person zu erstellen, zu warten und zu pflegen.
Das Leibniz® System entstand 1984 auf Basis des IBM PC aus einem Kooperationsprojekt unabhängiger Software-Entwickler zur Schaffung einer deutschen Programmiersprache. Die Namensgebung erfolgte in Anlehnung an Arbeiten von A. Goppold (AI-LRN77) zur Characteristica Universalis von Leibniz und Leibnizens Bedeutung als wesentlicher Vordenker der heutigen Entwicklungen in der Computerindustrie. Die frühen Ideen zum Leibniz-System wurden in TIME-FR.DOC, PARA-PRG84 und TRENDS84 beschrieben. Das Ursprungsprojekt erwies sich als kurzlebig, da der gleichzeitige Erfolg von Turbo-Pascal [6] und später C einer auf völlig anderen Prinzipien beruhenden Programmiersprache keine Entfaltungsmöglichkeiten mehr bot. Das Leibniz-System wurde daher von A. Goppold in den folgenden Jahren in Industrieprojekten (maschinennahe Steuerungen) weiterentwickelt. Im Jahre 1985 entstand auf der Basis von Leibniz eines der ersten Hypertextbasierten Software-Entwicklungssysteme, lange bevor die Standard-Entwicklungsumgebungen von Borland und Microsoft solche Möglichkeiten erlaubten.
Der
Token-Listen-Subroutinen-Interpreter, kurz TLSI genannt, ist die
softwaretechnische Basis des Leibniz Hypertext-Systems. Man kann in der
Rückschau sagen, daß dieser Typ einer logischen Maschine
notwendigerweise prädestiniert ist für ein Hypertext-System. Dies war
natürlich im Jahre 1984 noch nicht absehbar, weil Hypertext damals
außer durch ein paar Artikel von Ted Nelson noch nicht bekannt war, und
seine Eignung für die Software-Entwicklung bei einer Programmiererschaft,
für die der vi und vielleicht der EMACS das Nonplusultra aller
Entwicklertools darstellte, kaum eine Resonanz fand.
Der TLSI gehört als Klasse von logischen Maschinen zu der
größeren Gruppe der Metacode-Maschinen. Metacode-Maschinen
sind Rechnermodelle, deren Programmcode von dem Compiler nicht in den
Objekt-Code (Maschinencode oder Assemblercode) des verwendeten physikalischen
Rechners übersetzt wird, sondern von einem residenten Kontrollprogramm
interpretierend abgearbeitet wird. Beispiele dafür sind z.B. der Pascal
P-Code (Pseudocode), APL, LISP und Postscript. Der TLSI von Leibniz ist ein
sehr einfaches Rechnermodell, das sich auf jedem beliebigen Von-Neumann Rechner
mit ca. 10 bis 30 KByte -Code installieren läßt. Für die
damalige PC-Installation wurde erstmals ein 32-Bit Modell gewählt, das aus
Geschwindigkeitsgründen in Assembler programmiert war
[7].
Die wesentlichen, damals Eigenschaften des TLSI sind: Erweiterbarkeit,
Portierbarkeit, und Interaktivität. Als wesentlich für
spätere Entwicklungen erwies sich die Möglichkeit der Umwandlung
in compilierbaren C-Code.
(Abb.: Schema der Tokenlisten-Maschine)
Erweiterbarkeit:
Der wichtigste Aspekt des TLSI ist seine Erweiterbarkeit. Der TLSI ist eine
dynamisch erweiterbare Programmiersprache. Dadurch ist es möglich, mit
einem primitiven Kernel-System in einer Minimalkonfiguration (10 bis 30 KBytes)
wie mit einem interaktiven Debugging-Monitor zu arbeiten, als auch auf einer
Workstation-Umgebung mit Megabyte-Größen sehr reichhaltige
Entwicklungssysteme zu realisieren. Die vorhandene PC-Demoversion nutzt den
verfügbaren Addressraum des PC mit ca. 550 KB bis zum letzten aus und
stellt dort die Grenze des technisch machbaren dar. Die Erweiterbarkeit erlaubt
Anpassung an jede denkbare (und nicht denkbare) Konfiguration. Der TLSI ist
eines der flexibelsten Software-Systeme überhaupt.
Portierbarkeit: Dieses genormte Rechnermodell läßt sich auf
einer Vielzahl von Ziel-Prozessoren mit relativ geringem Anpassungs-Aufwand
installieren. Es ist dadurch möglich, Standalone-Installationen auf jedem
Zielrechner zu machen, und auch auf Industrie-Rechner ohne Betriebssystem zu
portieren
[8]. Der Arbeitsaufwand liegt bei
Assembler um 3 Mann-Monate. Verwendet man die C-Installation, dann ist der
Portierungsaufwand nahe Null. Die C-Installation erfordert dringend ein 32-Bit
Modell, und war deshalb auf PC-basiserten Compilern bisher kaum machbar. Diese
wurden erst mit dem 32-Bit DOS-Extender möglich. Unter Windows sind sie
auch heute noch nicht möglich.
Interaktivität: Auch in der Minimalversion ist eine User-Shell
(UINT) integriert, daher ist immer interaktives Arbeiten möglich. Das
System verhält sich in seiner Minimal-Konfiguration wie ein sehr
leistungsfähiger Debugging-Monitor.
Abstrakter Rechner: Für theoretische Betrachtungen interessant ist
der Faktor, daß der TLSI wahrscheinlich das einfachste praktisch nutzbare
Rechnermodell ist. Es ist vergleichbar mit einer LISP-Maschine, wird aber
wesentlich effektiver implementiert, so daß kein aufwendiger Rechner zur
schnellen Abarbeitung erforderlich ist. Wie eingangs schon festgestellt, ist
das System auf Minimalanforderungen ausgelegt. Kernelversionen des Systems
laufen auch auf Single-Chip Prozessoren mit 16-bit Installationen.
Umwandlungsmöglichkeit in compilierbaren Code: Weil hier ein
Zwischencode interpretiert wird, ist die Ausführungsgeschwindigkeit nicht
so hoch wie compilierter Assembler-Code. Es ist aber in dem C-implementierten
System möglich, mit einem einfachen Filter eine Konversion von Tokencode
zu compilierbarem C-Source Code zu machen, indem man jeden Token-Call durch
einen C-Function Call ersetzt. Dies ist ein wesentlicher Vorteil gegenüber
einer konventionellen Shell, wie sie in Unix verwendet wird (C-Shell, Bourne
Shell). Die Entwicklungsgeschwindigkeit ist durch die interaktive Arbeitsweise
wesentlich höher als mit einem konventionellen Compiler-System.
Ideal für Source-Code-Debugger: Ein ganz entscheidender Vorteil
ergibt sich durch die extrem einfache Verbindung des Run-Time Token Codes mit
der Source-Information, das die Erstellung eines Source Code Debuggers zu einem
vergleichsweise einfachen Projekt macht. Statt der Größenordnung von
1 bis 5 Mannjahren, läßt sich ein ausgefeilter Source-Code Debugger
in ca. 3-6 Mannmonaten erstellen.
Die
Tokenliste (TL) besteht aus einer Folge von Toke
ns,
die in einer geeigneten Datenstruktur abgelegt sind. Diese Datenstruktur
besteht aus einer Folge von CELLs (Zellen). Jede CELL kann ein Token oder ein
Datenelement enthalten, bei der vorliegenden Implementation werden
CELL-Größen von 4 Byte verwendet. Die konzeptuelle Einfachheit
dieses Schemas gegenüber den Assembler-Instruktionsformaten normaler
(CISC-) Prozessoren besteht darin, daß die Befehlsbreite immer gleich
ist.
Ein Token ist ein Stellvertreter für eine Routine. Eine Routin
e
wird ausgeführt, indem die zugeordnete Tokenliste von der
STEP-Maschine abgearbeitet wird. Dies bedeutet, daß die
einzelnen Tokens der Liste (oder vielmehr die damit repräsentierten
Routinen) ausgeführt werden. Der jeweils aktuelle Stand der Abarbeitung
innerhalb der Tokenliste wird im Befehlszeiger (Instruction-Pointer:
IP) gehalten.
(Abb.: Schema der STEP-Maschine)
Die STEP-Maschine arbeitet somit eine Tokenliste vom Start bis zum Ende ab. Da in der meisten Fällen die Tokens in den CELLs auf weitere Tokenlisten verweisen, wird in der bekannten Art des Subroutine-Nest die augenblickliche Position des IP in der augenblicklichen TL auf dem Control- oder Return-Stack gerettet, und die entsprechende neue Tokenliste abgearbeitet. Trifft die STEP-Maschine auf eine Routine, die Assemblercode des Host-Prozessors enthält, wird der Instruktions-Satz des Host-Prozessors ausgeführt.
Der
Grund für die äußerst glückliche Verbindung von TLSI und
Hypertext beruht auf zwei Faktoren:
Der TLSI ist ein komplett hierarchisch aufgebautes System von einander
aufrufenden Subroutinen, von denen jede einen eigenen (unambiguous) Namen hat.
Alle Operatoren, angefangen von ADD, SUBtract, MULtiply, DIVide sind
Subroutinen, die über einen eigenen Namen aufgerufen werden. Jede
Composite-Subroutine, die aus schon bestehenden zusammengesetzt ist, wird
wiederum mit einem eigenen Namen in der Datenbank abgelegt. Somit stellt das
ganze System sich als ein System von Hypertextknoten dar.
Der andere Faktor liegt in der Datenstruktur der Tokenliste. Wie in der
Abbildung "Das Schema der Tokenlisten-Maschine" ersichtlich ist, ist im
Benutzerteil UINT eine Name-Key Datenbank angelegt, die mit einem sehr
einfachen Kunstgriff in die Basis für ein Hypertext-System umgewandelt
werden kann: Man muß nur zu jedem Namensfeld einen Pointer auf die Datei
und Position einfügen, wo der Name-Key vom Compile-Scan gefunden wurde,
und man erhält eine Hypertext-Datenbank-Struktur. Da der Compile-Scan
schneller als bei Turbo-Pascal funktionierte (ca. 1 MB pro Minute), war eine
Hypertext-Indizierung eines großen Projekts mit mehreren MB fast in
real-time machbar. Der nächste Schritt schließt sich folgerichtig
an: Da das Leibniz-System von Anfang an seinen eigenen, systemintegrierten
Editor hatte, war es nur ein kleiner Schritt, im Editor eine Direktabfrage nach
dem Namen der Routine unter dem Cursor einzubauen, der mit den Daten der UINT
Datenbank Dateinamen und Position augenblicklich zur Verfügung hatte. So
war es 1985 gelungen, einen Hypertext-integrierten Editor zu erstellen, der
jede beliebige Routine eines großen Projekts von 10.000 Routinen in
mehreren hundert Dateien mit einer maximalen Zugriffszeit von 100 MSec
zugänglich machte. Diese schnelle Zugriffszeit ist im 32-Bit Modell nur
mit einem Assemblerkern möglich, weshalb es bisher auch Probleme
bereitete, dasselbe Modell auf Windows (mit 16-Bit Modell des C-Compilers) zu
portieren.
Eine derartig schnelle Zugriffszeit von max. 1/10 Sec. erlaubt eine
Arbeitsweise des interaktiven Programmierens, die von keinem anderen
Software-Entwicklungssystem (nach augenblicklichen Kenntnisstand) erreicht
werden kann. Die Vielzahl von Tastendrücken, und Mausklicks, die in einem
System wie dem Turbo-Editor nötig ist, um eine Subfunktion einer C-Routine
zuzugreifen, erlaubt in keinem Fall eine derartig kohärente und
flüssige Arbeitsweise wie im Leibniz-System. Die in Leibniz möglichen
Arbeitsgänge sind allerdings in den bekannten Kategorien der
Programmierung, die sich eher an dem Prinzip "Langsam aber Sicher" orientiert,
und deren Hauptarbeitswerkzeug heute wie vor 20 Jahren immer noch der vi ist,
kaum darstellen. Dazu müssen andere Ergebnisse aus der menschlichen
Kognitionsforschung, vor allem des Kurzzeitgedächtnisses, und den
Verschmelzungseffekten, die bei schnell abfolgenden Bewegungsabläufen, die
innerhalb der 1/10 Sekundenschwelle liegen (Tadhani-Effekt) hinzugezogen
werden. Diese Effekte sind der Gegenstand eines anderen Papers.
Ein Mensch-Maschine-System sollte die folgenden Eigenschaften bieten:
- Die maximale und optimale, individuelle Benutzerkonfigurierung auf der Basis
von ASCII-lesbaren Dateien. Jeder Benutzer sollte sich die Makro-Sequenzen so
einstellen können, wie es seinen Präferenzen entspricht.
- Kontextsensitive Hilfefunktion. Die Funktion sollte auch den Systemzustand
berücksichtigen.
Einige heute erhältliche Softwaresysteme, wie etwa MS-Word bieten zwar die
Möglichkeit der individuellen Konfigurierung über eine interne
interpretierte Kommandosprache (Word-Basic), aber die aktuelle Konfiguration
und die Belegung der Funktionstasten ist nur über komplizierte, und nicht
sehr benutzerfreundliche Menus zugänglich. Einfacher wäre eine
Konfiguration, die in einer ASCII-lesbaren Datei dokumentiert ist, und mit
einem normalen Editor bearbeitet werden kann, wie sie z.B. von EMACS und
Abkömmlingen mit der LISP-Programmierung geboten wird.
Das folgende Beispiel zeigt die Konfigurationsdatei aus dem String Interaktor
des Leibniz-Systems. Diese Konfiguration ist jederzeit, auch während des
Programmlaufs, interaktiv zu ändern.
Die Abbildung "String-Aktor Menus" zeigt die Ansicht des daraus entstehenden
Menus.
Hier der Eintrag in der Menu-Konfigurations-Datei:
:GRP VX-IO/MOVE \ i/o and move
LD-VX[ VD-A1
$.user_input 0 I 0 "user input string "
$.get 5 G 0 "get string with len from adr "
$.get_cnt 12 A 0 "get counted string from adr"
$.put 6 P 0 "put string to adr"
$.put_cnt 6 U 0 "put counted string to adr"
$.dup_top 0 D 0 "duplicate top string"
$.mov_2nd 0 M 0 "xchg top 2 strings"
$.copy_2nd 0 C 0 "copy 2nd string to top"
$.copy_# O 0 "copy nth string to top (0cnt) "
$.mov_# 4 T 0 "move nth string to top (0cnt) "
$.mov_bot 0 B 0 "move bottom string to top"
$.push_#x 16 R 0 "move/del to nth_pos top string (0cnt) "
$.del_top 0 X 0 "drop top string"
$.del_#strn 9 N 0 "drop off n strings"
$.len 0 L 0 "length of top string "
END;
(| AG 19:29 08/11/92 )
Eine Zeile der Menu-Datei erklärt die Zuordnungen:
$.user_input 0 I 0 "user input string "
^ ^ ^Menu-Eintrag
^ ^ ^
^ ^ Input-Character (ALT-)
^ ^ optionaler String bei Ausführung des Menus
^
aufgerufende Methode
Durch Integration in das Leibniz-Hypertext-System ist zu jedem Eintrag zudem
noch ein kontextsensitiver Help-Text verfügbar, der ebenfalls in einer
ASCII-lesbaren Textdatei liegt, die jederzeit editiert werden kann. Unten der
Ausschnitt aus der Help-Textdatei zu dem obigen Menu. Für Texte in
verschiedenen Landessprachen genügen die Präfixe !ENG !GER !FRA !ITA
etc. Die Umschaltung in die verschiedenen Landessprachen kann ebenfalls zur
run-time durch das Konfigurationsmenu gemacht werden.
!" XXX
!" $.user_input
!ENG user input string
\LThe user enters a string at the keyboard. The key <ENTER> finishes
the string. With ESC it is possible to leave the input, and a
zero-string is produced. The string input system has an
editing capability which allows to edit the present line with
the cursor control keys and the Wordstar (R) or Turbo (R)
diamond pattern. It also allows to retrieve old inputs and gives a
full-screen editor for them.
!GER Benutzer String eingabe.
!" $.get
!ENG get string with len from adr
\LA string that is at a buffer address is collected into
the String Actor buffer. The length of the string is needed
as second parameter ( adr len -P- )
!GER String Text
!" $.get_cnt
!ENG get counted string from adr
\LA counted string in Pascal format
that is at a buffer address is collected into
the String Actor buffer. The length of the string is
in the count byte at the address.
!GER String Text
!" $.put
!ENG put string to adr
\LThe Top string in the Actor Buffer is deposited
into a buffer address.
!GER String Text
!" $.put_cnt
!ENG put counted string to adr
\LThe Top string in the Actor Buffer is deposited
into a buffer address. The count of the string is in
the first byte of the address (Pascal format).
!GER String Text
!" $.dup_top
!ENG duplicate top string
\LThe Top string in the Actor Buffer is duplicated.
!" $.mov_2nd
!ENG xchg top 2 strings
\LThe Top two strings in the Actor Buffer are exchanged.
!GER String Text
!" $.copy_2nd
!ENG copy 2nd string to top
\LThe Top second string in the Actor Buffer is copied to the
Top, pushing the former Top down to the second position.
The original of the copied second string is then in third
position.
!" $.copy_#
!ENG copy nth string to top (0cnt)
\LThe nth string in the Actor Buffer is copied to Top.
The parameter is interpreted as zero count, ie the
Top string is element # 0, the second is element #1.
!" $.mov_#
!ENG move nth string to top (0cnt)
\LThe nth string in the Actor Buffer is moved to Top.
It is deleted at the former position.
The parameter is interpreted as zero count, i.e. the
Top string is element # 0, the second is element #1.
!" $.mov_bot
!ENG move bottom string to top
\LThe bottom string in the Actor Buffer is moved to Top.
This way, one can completely traverse the whole string
buffer in steps.
!" $.push_#x
!ENG move/del to nth_pos top string (0cnt)
\LThe Top string in the Actor Buffer is moved to nth
position. It is deleted at the Top.
!" $.del_top
!ENG drop top string
\LThe Top string in the Actor Buffer is deleted.
!" $.del_#strn
!ENG drop off n strings
\LThe Top n strings in the String Actor Buffer are deleted.
!" $.len
!ENG length of top string
\LThe number of bytes of the
Top string in the Actor Buffer is left as parameter.
!" XXX
Kontextsensitive Hilfefunktion und Selbst-Auskunftssystem
Die Problematik bekannter Hilfe-Systeme, wie etwa in Windows ist, daß
keine exakte Koppelung zwischen dem augenblicklichen Systemzustand und den
Hilfstexten besteht. Dies ist nur möglich, wenn der Funktionen-Stack
ausgewertet werden kann. Dies ist bei der herkömmlichen (C-Compiler-)
Technololgie schwierig. Verwendet man aber ein Interpretersystem wie den in
"Leibniz - Ein hypertextbasiertes Softwaresystem" genannten TLSI-Prozessor, so
ist die Selbst-Analyse des Systems möglich.
[5]Leibniz® ist ein angemeldetes Warenzeichen
von A. Goppold.
[6]Pascal war ebenso wie Leibniz einer der sehr
frühen Pioniere der Rechenmaschinen.
[7]Das war 1984! Heute, zehn Jahre später
haben wir mit OS/2 und dem noch erwarteten Windows 4 endlich 32-Bit Modelle.
Ein 16-Bit TLSI ist aus diversen Gründen kontraproduktiv.
[8] Diese Anwendung war in den folgenden Jahren
dann auch der hauptsächliche Einsatzort des Leibniz-Systems.