Wolfgang Roller, Ehningen

Der BrainExtender

Die Sprache oder besser die Sprach-Einheit

1. Die Sprache an sich

Die Sprache für den BrainExtender ist eine neue Computersprache. Sie orientiert sich so nah es geht an der englischen Sprache. Englisch ist recht kurz und prägnant, zu dem eine, bzw. die Weltsprache. Es gibt schon ähnliche Sprachen wie z.B. Object Pascal von Delphi oder VBA von Microsoft, die ebenfalls als ein reduziertes Englisch bezeichnet werden können.

Dann gibt es die "geschweifte Klammer"-Sprachen wie C, C++, Objective C, Java, C# und viele andere. Das sind Sprachen die einen recht kompakten Code ergeben. Der Ursprung ist der, dass in den Anfängen selbst der Speicherplatz für den Quellcode knapp war und man somit sich Sprach-Konstrukte überlegte die zum einen von den Zeichen, die die damaligen Tastatur-Layouts hergaben und zum anderen wenig Speicherplatz belegten.

Die Idee hinter der Sprache für den BrainExtender ist der, dass ich auch per Spracheingabe, mit diesem reduzierten Englisch, mit meinem BrainExtender kommunizieren kann. Und da eignet sich ein:

if (counter == 5) { ...

weniger. "if left parenthesis counter equals integer value five right parenthesis left curly bracket ...". Ein Konstrukt wie:

if counter = 5 do ...

ergibt: "if counter equals integer value five do ...".
Ganz ohne runde Klammern geht es nicht. Ist die If-Bedingung ein komplexerer Ausdruck mit unterschiedlichen Operatoren, so sind unter Umständen wegen der Rangfolge der Operatoren runde Klammern notwendig um das gewünschte Ergebnis zu erzielen. Aber man gut auf die umschließenden Klammern verzichten und stattdessen alles was sich zwischen dem "if" und dem "do" befindet als Bedingung interpretieren.

Wo anfangen? Bei Adam und EVA?

Als ich damals im Herbst 1982 das Praktikum bei IBM Deutschland in Sindelfingen begonnen habe, wurde mir noch das EVA-Prinzip beigebracht. EVA = Eingabe, Verarbeitung, Ausgabe. Wenn ich im Web nach EVA-Prinzip suche finde ich auch heute noch Erklärungen, dass so EDV funktioniere. Ich behaupte, dass das EVA-Prinzip heute längst überholt ist. Wir arbeiten interaktiv mit dem Computer.

In meiner Gedankenwelt, in der alles ein Objekt ist, spreche ich lieber von

  • Erzeugen
  • Finden
  • Ändern
  • Löschen
  • Interagieren

Nun ist EFÄLI keine so schöne passende Abkürzung oder so schönes Synonym wie EVA. Drum lassen wir das. Aber behalten diese fünf Tätigkeiten im Hinterkopf.

Neben der Beschaffenheit der Sprache sind folgende Design-Fragen zu klären:

  1. Interpreter vs. Compiler
  2. Maschinensprache vs. Bytecode
  3. Keine Design-Frage, aber wie soll die Sprache heißen?

Die Antworten

  1. Einfach zu beantworten: Beides!
    Für die direkte Interaktion braucht es einen Interpreter. Anders geht es gar nicht.
    Aber für große Programme braucht es einen übersetzten Code. (Unabhängig davon, ob in die Maschinensprache oder Bytecode übersetzt wird.)

  2. Auf diese Frage habe ich momentan keine endgültige Antwort. Beide Verfahren haben ihre Vor- und Nachteile. Im augenblicklichen Zustand muß die Antwort noch nicht festgelegt werden. Wenn jemand mir seine Erfahrungen schildern möchte und mir pro- und contra-Argumente nennen möchte, nehme ich diese gerne entgegen. (eMail-Adresse ist im Impressum hinterlegt.)

  3. Beim Überlegen wie das Kind denn nun heißen soll, habe ich verschiedene Namen überlegt. Irgendwann bin ich auf die Idee gekommen es nach mir, nach meinem Nachnamen Roller zu bennen. Mal ganz unbescheiden zu sein. Ich finde den Gedanken cool, dass wenn sich Leute über ihre Programmiersprache unterhalten dann einer sagt: "Ich programmiere in Roller" Da Roller case insensitive ist, ist auch die Schreibweise egal. Man kann schreiben: roller, Roller, rOLLER, rOlLeR oder wie man gerade lustig ist. Ich verwende zukünftig diese Schreibweise: roller.

    Kleine Anmerkung: Es ist auch im englischen Sprachraum leicht auszusprechen, bzw. ein Wort aus dem Alltag.

Kommunikation mit dem BrainExtender

Ich stelle mir die "Standard"-Komunikation so vor: Das System wird eingeschaltet. Durch meine ID-Karte, oder was auch immer einer meine ID gegen über der Maschine ausdrückt, bin ich im 1. Schritt legitimiert. Die 2. Legitimation erfolgt durch Eingabe eines Passwortes oder eines biometrischen Merkmals. Danach öffnet sich ein Terminal. Wer Unix/Linux kennt, weiß was ich meine. Dort meldet das System seine Bereitschaft. Es gibt den Prompt "system>" und "ich>". Und die Kommunikation funktioniert ganz ähnliches wie wenn man mit jemandem chatten würde. Nur nicht mit einer anderen Person, sondern mit seinem eigenen BrainExtender.

Nach dem Einschalten meldest sich das System höflich mit einem "Guten Morgen/Tag/Abend." Und einem kleinen Status-Bericht in dem es mittelt, dass alles System ordnunggemäß gestartet wurden und arbeiten der gegebenfalls auch nicht. Z.B., wenn das Netzwerk oder eine andere Resource nicht zur Verfügung steht. Der Cursor steht hinter meinem Prompt ich> und ich kann an dieser Stelle loslegen mit dem, was ich nun mit dem System eben gerne tun möchte.

Das klingt jetzt für die User, die gerne ein graphische Benutzeroberfläche wollen nicht gerade einladend. Aber ihnen sei gesagt, dass es selbstverständlich auch eine graphische Benutzeroberfläche geben wird und der BrainExtender vollständig graphisch benutzt werden kann, wenn man das möchte. Die graphische Benutzeroberfläche wird aber auf die Befehle, wie wir sie jetzt besprechen, aufbauen. Das hat den Vorteil, dass jede Aktion aufgezeichnet und gespeichert werden kann und später wieder abgespielt werden kann. Und dieses Aufzeichnen erfolgt über das ganze System hinweg.

Das kann momentan keines der heterogenen Computersysteme am Markt. Apple hat vor Jahren mit seinem AppleScript versucht dies zu realisieren. Letzendlich sind sie gescheitert, weil jeder Softwarehersteller seine Programme hätte AppleScript-fähig machen müssen. Und das haben diese nicht gemacht.

Hätte Apple es geschafft, dann gäbe es unter Mac OS X jetzt eine durchgängige Programm übergreifende Script-Sprache. Beim BrainExtender geht es um viel mehr. Es gibt roller als einzige Programmiersprache. Eine durchgängie Sprache für das komplette System.

Es gibt eine gemeinsame Parallele von roller und AppleScript. Auch Apple hat die Sprache AppleScript so nah als möglich an die englische Sprache angelegt.

Da roller sowohl interpretierend als auch "compilierend" arbeiten wird, erlaube ich mir die Kreatkion eines neuen Wortes. Nämlich den neuen Begriff Interpiler. Das Wort Interpiler ist ein Kunstwort aus Interpreter und Compiler. Alternativ ginge auch "Compreter". Ich habe mich jedoch für Interpiler entschieden, weil der Interpreter der wichtigere Teil bei der Mensch-Maschinen-Kommunikation ist.

Hello World

In roller ist alles ein Objekt. Auch ein Programm. Als kleines Beispiel soll das beliebte "Hello world"-Programm:
create class helloWorld extends programAbstract.
Abstrakte Klassen sind sozusagen Schablonen für Schablonen. Von abstrakten Klassen können keine Objekte erzeugt werden. Per Namenskonvention sollen sie den Namenszusatz "Abstract" hinten angehängt bekommen. So erkennt man bereits am Namen der Klasse, dass es sich um eine abstrakte Klasse handelt.
Da es mühsam wäre in dem Stil "create task", etc. die Klasse weiter aufzubauen, geht stattdessen ein roller-Quellcode-Editor-Fenster auf. (So wie man das von fast allen Programmiersprachen bzw. Entwicklungsumgebungen kennt.)
In roller gibt es keine Methoden sondern Tasks, also Aufgaben, die eine Klasse erfüllt. Beim Erzeugen der Klasse helloWorld weiß das System, dass die Klasse programAbstract die Implementierung von zwei Tasks vorschreibt. Das sind die beiden Tasks do() und run(). Sie unterscheiden sich dadurch, dass do() im Sinne eines Skripts ist. D.h. do() wird aufgerufen, und endet von alleine mit dem Ausführen der letzten Anweisung.
run() dagegen ist eine Endlosschleife, die explizit mit Hilfe der stop()-Task beendet werden muß.
Das helloWorld-Programm soll ja nur den String "Hello world!" ausgeben. Drum genügt die do()-Task. Dort schreiben wir die Anweisung:
output!!display("Hello world!").
rein. output ist eine statische Eigenschaft der Klasse programAbstract und steht somit jedem Objekt, oder manche würden auch sagen jeder Instanz, der von programAbstract abgeleiteten Klassen zur Verfügung.
Was nun folgt ist die Erzeugung eines Objektes der Klasse helloWord und der Aufruf der Task do().
create program helloWord().
program!!do().

Ausgabe: Hello world!

Alles ist eine Objekt. Das Programm ist noch im Speicher. Darum:
program!!destroy(). // Direkter Aufruf des Destruktors.
oder
program := undefined. /* Indirekter Aufruf des Destruktors. Der Zeiger program ist der einzige Zeiger, der auf das Programm-Objekt verweist. Dadurch, dass das System erkennt, dass der letzte Verweis entfernt wird, wird der Destruktor aufgerufen. */

Es gibt einen definierten Zeitpunkt für den Aufruf des Destruktors und etwaige Resourcen können mit Sicherheit freigegeben werden.

Um einen Einblick in die Sprache roller zugeben, beschreibe ich ein paar Sprachkonstrukte. Die übrigens auch wieder als Objekte betrachtet werden können. Eine For-Schleife oder ein If-Zweig sind aus der Sicht der objektorientierten Programmierung auch ein Objekt.

Dabei gilt: <Name> ist ein Platzhalter für einen sinnvollen Namen oder <Befehl1> steht für eine sinnvolle Anweisung.
Schlüsselwörter sind so gekennzeichnet: keyword

create class <NeueKlasse> extends <BestehendeKlasse> .
oder wegen der "case insensitivity" geht auch:
Create class <NeueKlasse> extends <BestehendeKlasse> .
create object of class <KlassenName> ( <Parameter1><Argument1><Parameter2><Argument2> ).
Kommentare sind gleich wie in Java.
/* Ab jetzt zeigt der implizite Zeiger object immer auf dieses Objekt. Und zwar solange wie ihm kein anderes Objekt zugewiesen wird. */
// Er kann verwendet um z.B. eine Task dieses Objektes aufzurufen.
object@calculateValue(amount: 50).
create object of class Auto(Marke: "VW", Modell: "Golf").
/* Der implizite Zeiger object zeigt jetzt auf das soeben neu erzeugte Objekt von der Klasse Auto. D.h. die vorherige Anweisung: "object@calculateValue(amount: 50)." würde jetzt interpretierend einen Laufzeitfehler verursachen und compilierend einen Compiler-Fehler. Dafür kann jetzt z.B. die Leistung des Autos festgelegt werden. */
object•setLeistung(inKW: 105).
/* Angenommen wir wollen jetzt ein weiteres Objekt erzeugen und dieses Auto weiterhin referenzieren, dann erzeugen wir einen Zeiger auf dieses Objekt und haben es weiter im Fokus. */
pointer golf := object.
create object of class Auto(Marke: "Porsche", Modell: "911").
/* Über "object" kann auf das neue Objekt, den Porsche zugriffen werden und der Golf ist über den "Golf-Pointer" immernoch referenzierbar. */
object!!setLeistung(inKW: 350).
object!!setAnzahlSitze(4).
golf!!setAnzahlSitze(5).

Jetzt folgen noch, nur um mal ein Gefühl für die Sprache zu bekommen, die Verzeigungen und Schleifen-Konstrukte. (Wobei, unter uns gesagt, sich dabei nicht weltbewegend Neues tut.)

Die Verzweigungen

Die einfachste If-Bedingung mit nur einer Anweisung
if <Bedingung> do: <Anweisung> endif

Blöcke im klassischen Sinne, wie man sie von anderen Programmiersprachen kennt, gibt's in Roller nicht. Wenn mehrere Anweisungen zusammenhängend in einem If-Zweig ausgeführt werden sollen, dann werden sie mit einem "." oder einem ";" getrennt. (Und der Übersichtlichkeit halber am besten untereinander angeordnet.)

If-Bedingung mit mehreren Anweisungen
if <Bedingung> do:
<Anweisung1>;
<Anweisung2>;
<Anweisung3>
endif
If-/Else-Zweige mit mehreren Anweisungen
if <Bedingung1> do:
<Anweisung1>;
<Anweisung2>;
<Anweisung3>
elseif <Bedingung2> do:
<Anweisung4>;
<Anweisung5>;
<Anweisung6>
else do:
<Anweisung7>;
<Anweisung8>
endif

Die Mehrfach-Verzweigung

in case of <Variable/Objekt/Ausdruck etc.>
= 1 do:
<Anweisung1>;
<Anweisung2>;
<Anweisung3>.
= 2, 3 do:
<Anweisung4>;
<Anweisung5>;
<Anweisung6>;
continue
= 4 do:
<Anweisung7>;
<Anweisung8>.
else do:
<Anweisung9>;
<Anweisung10>
endcase
in case of <String-Variable>
= "abc" do:
<Anweisung1>;
<Anweisung2>;
<Anweisung3>.
= "de", "ef" do:
<Anweisung4>;
<Anweisung5>;
<Anweisung6>;
continue
= "xyz" do:
<Anweisung7>;
<Anweisung8>.
else do:
<Anweisung9>;
<Anweisung10>
endcase

Im Gegensatz zu C, C++, Java etc. ist das "break" implizit, sondern es muß mit continue bestätigt werden, dass der Programmfluß in den nächsten Fall übergeht.

Die Programm-Schleifen

For-Schleife

Es gibt zwei Ausführungen (wie z.B. bei Java auch).
Die Zähler-Variable kann außerhalb der Schleife definiert sein:

integer counter.
for counter := 5 to 23 with step size 5 do:
<Anweisung1>;
<Anweisung2>;
<Anweisung3>
endfor

Dadurch, dass die Variable "counter" außerhalb der For-Schleife definiert wird, ist auch noch nach der Ausführung der Schleife verfügbar. Das with step size ist optional. Wird keine Schrittweite angegeben, dann wird am Ende eines Durchlaufes der Zähler immer um 1 erhöht, auch dann, wenn der Zähler vom primitiven Datentyp real ist.

Der Schleifenzähler kann auch "runterzählen". Und hier wird die Schleifen-Variable innerhalb der For-Schleife definiert und ist außerhalb der Schleife nicht gültig. Gleichwohl darf sie keinen Namen haben der außerhalb bereits vergeben ist. Einfach um Verwirrungen vorzubeugen. Wenn es z.B. außerhalb eine Integer-Variable namens counter gibt und innerhalb einer For-Schleife auch, dann überliest man vielleicht die Definition in der For-Schleife und wundert sich unterhalb der For-Schleife warum der counter nicht den erwarteten Wert hat.

for integer counter2 := 100 down to 1 do:
<Anweisung1>;
<Anweisung2>;
if <Bedingung> do:
break
endif
<Anweisung3>
endfor

Am obigen Beispiel habe ich auch das neue Schlüsselwort break eingeführt. Das kennt man von fast allen Programmiersprachen. Nach dem break muß kein weiteres Zeichen folgen, weil klar ist, dass an diesem Punkt der Schleifendurchlauf abgebrochen wird.

In zwei ineinander geschachtelten Schleife gibt es die Möglichkeit in der inneren Schleife mit zwei break-Anweisungen hintereinander aus beiden Schleifen auszusteigen.

integer counter.
for counter := 5 to 23 with step size 5 do:
<Anweisung1>;
for integer counter2 := 100 down to 1 do:
<Anweisung3>;
<Anweisung4>;
if <Bedingung> do:
break; break // sofortiger Abbruch beider Schleifen.
endif
<Anweisung5>
endfor
<Anweisung2>
endfor

Hier müssen wir die beiden breaks durch einen . oder einen ; trennen. Dann gibt es noch die continue-Anweisung, die wir schon von der Mehrfach-Verzweigung kennen und von vielen anderen Programmiersprachen.

for integer counter2 := 100 down to 1 do:
<Anweisung1>;
<Anweisung2>;
if <Bedingung> do:
continue // Es wird von dieser Stelle direkt zum Schleifenanfang gesprungen.
endif
<Anweisung3>
endfor
In einer verschachtelten Schleife ist auch beim Abbruch die Kombination:
break; continue
denkbar. (Während die beiden weiteren Kombinationen "continue; break" und "continue; continue" keinen Sinn ergeben.)

Beim break; continue bewirkt das "break" den Abbruch der inneren Schleife und in der äußeren Schleife endet dieser Durchgang direkt nach der inneren Schleife, es wird der Schleifen-Zähler erhöht oder dekrementiert, je nach dem ob es sich im eine "to" oder "down to" handelt und dann sofort zum Schleifen anfang gesprungen.

Die While-/Until-Schleifen

In roller gibt's beide Konstrukte. Die einen wollen eine Schleife so lange ausführen lassen solange eine bestimmte Bedingung erfüllt ist und den anderen ist es lieber die Schleife laufenzulassen bis eine bestimmte Bedingung erreicht ist. Es ist Geschmacksache, welches der beiden Konstrukte man anwendet.
Und jede der beiden beiden Ausführungen gibt es jeweils mit der Abfrage der Bedingung am Anfang der Schleife und am Ende.

while <Bedingung> do:
<Anweisung1>;
<Anweisung2>;
if <Bedingung2> do:
break
endif
<Anweisung3>
endwhile
do:
<Anweisung1>;
<Anweisung2>;
<Anweisung3>
while <Bedingung> .
until <Bedingung> do:
<Anweisung1>;
<Anweisung2>;
<Anweisung3>
enduntil
do:
<Anweisung1>;
<Anweisung2>
if <Bedingung> do:
continue
endif
<Anweisung3>
until <Bedingung> enddo

Bei den do-while-, bzw. do-until-Schleifen kann man entweder am Ende einen . schreiben oder ein enddo. Beide schließen die Schleife syntaktisch ab.

With each object-Schleife

Das ist eine Sonderform der Schleifen, die sich auf Container-Objekte anwenden läßt. Ein Container-Objekt ist eine Objekt, das andere Objekte in sich beherbergt. Die dazugehörigen Klassen bezeichne ich als Container-Klassen. Mit Hilfe der With each object in-Schleife ist es möglich in einer sequentiellen Reihenfolge auf alle Objekte eines solchen Container-Objektes zuzugreifen.

with each object in <Pointer auf Container-Objekt> do:
<Anweisung1>;
// Hier innerhalb der Schleife kann jedes Objekt über den
// impliziten Zeiger "object" referenziert werden.
object!!task1();
<Anweisung3>;
<Anweisung4>
endwith

Container-Objekte können ins sich Container-Objekte enthalten. Bei der obigen Schleife wird nur die erste Ebene durchlaufen. Wenn man die Objekte der enthaltenen Container auch ansprechen möchte, muß man do recursive verwenden.

with each object in <Pointer auf Container-Objekt> do recursive:
<Anweisung1>;
// Hier innerhalb der Schleife kann jedes Objekt über den
// impliziten Zeiger "object" referenziert werden.
object!!task1();
<Anweisung3>;
<Anweisung4>
endwith

Die bisher festgelegten Schlüsselphrasen (keyphrases)

Statt von Schlüsselwörtern rede ich lieber von Schlüsselphrasen. Schließlich möchte ich ein Konstrukt wie with each object in nicht als Schlüsselwort bezeichnen. Hier folgt jetzt eine Auflistung aller bisher festgelegten Schlüsselphrasen. In alphabetischer Reihenfolge.

adapter
bit(n) // wobei n = 0 bis 63, also bit0, bit1, ...
boolean
break
byte
character
complex // Komplexe Zahl
container
continue
create class
create object of class
currency
destroy
do:
do recursive:
down to
doubleword
else
elseif
endcase
enddo
endfor
endif
enduntil
endwhile
endwith
extends
for
if
in case of
index
integer
interface
list
list classes
list objects
list programs
matrix
natural
natural0
now
object
pause recording
pointer// Zeiger, der auf Objekte beliebiger Klassen zeigen kann.
pointer->// Zeiger, der auf Objekte einer bestimmten Klasse und deren Ableitungen zeigen kann.
pointer-->// Zeiger, der nur auf Objekte einer ganz bestimmten Klasse zeigen kann.
program
project
prototype
longword
real
record
static
stop recording
string
task
to
transaction /* Wie bei SQL-Datenbanken wird es Transaktionen geben. Aber die Details habe ich noch nicht festgelegt. */
type // Zur Definition eigener Datentypen.
undefined
until
uses // Auflistung der anderen Klassen, die eine Klasse verwendet.
while
with each object in
with step size
word

Die bisher festgelegten Operatoren (momentan noch sehr unvollständig)

:= // Zuweisung
=: // gleichwertige Zuweisung, aber in der umgekehrten polnischen Notation
= // Gleichheit, und zwar inhaltlich
=(caseInsensitive) // Gleichheit von Strings, allerdings gilt A = a usw.
=(pointer) // Gleichheit bezüglich des referenzierten Objektes
// ungleich
≠(caseInsensitive)
≠(pointer)
Trenner für's vert. Verschieben bei Touchscreens