Achtung! Das Projekt wurde eingestellt, da ich stattdessen von der Sprache Rust überzeugt wurde. Diesen Artikel behalte ich zu Bildungszwecken hier.
Eigentlich habe ich diese Programmiersprache nur als mein eigenes Werkzeug entwickelt. Ich bin Perfektionist und mit C, Java oder JavaScript zu arbeiten befriedigt mich nur beschränkt. SSC ist eine sehr abstrakte Sprache, die sich aber theoretisch dennoch effizient ausführen lässt. Die erste offizielle Anwendung fand im Sonden-Projekt "CanSat" 2016 statt. Hierfür war auch eine ausführliche Dokumentation der Sprache notwendig, um eine Bewertung meines Programmes zu gewährleisten. Dieses Dokument soll eine überarbeitete und praxisnähere Ausgabe jener Dokumentation sein.
Diese Dokumentation ist offen zugänglich und darf nicht als Kopie, sondern nur als Verweis auf diese Seite (l3p3.de) weitergegeben werden. Sie zu erlernen, kann die Denkweise beim Programmieren stark beeinflussen. Eine reine Anwendung ist zum Stand 2018 noch nicht so praktikabel. Außerdem ist SSC nur ein Zwischenschritt zu der noch abstrakteren Programmiermethode SENG (script engine), an der ich ebenfalls arbeite. Jene Methode baut auf SSC im Sinne des Bootstrappings auf. SSC (SENG source code) wird nach ihrer Fertigstellung (idealerweise) nicht mehr benötigt. Ich möchte verhindern, dass SSC später in Konkurrenz zum eigentlichen Zielprodukt steht.
Ich bevorzuge abstrakte Programmiersprachen und setze sie überall dort ein, wo es geht. Dabei gefällt mir aus der Welt der etablierten Sprachen der Ansatz von JavaScript am meisten, besonders dessen Portabilität und Einfachheit. Heute sind Rechnersysteme "intelligent" genug, zu sprechen und zu hören, zu sehen, komplexe wirtschaftliche Vorhersagen zu treffen, gute Produktempfehlungen an Internetnutzer zu richten oder beste Fahrtwege mit Berücksichtigung von Umwelteinflüssen zu finden. In Anbetracht dieser Tatsachen scheint es mir absurd, dem Rechner im Beispiel von Java noch mitteilen zu müssen, dass 15
eine Zahl und true
ein Wahrheitswert sind. Und new Rose()
ist vom Typ Rose
ist vom Typ Rose
. Sicher hat diese Redundanz ihre historische Berechtigung, nämlich wenn ein Programm mit einem primitiven Texteditor oder gar mit dem Stift auf Papier verfasst wird. Bei Textverarbeitung, grafischer 2D- und 3D-Gestaltung und Erzeugung von Musikstücken ist der Übergang vom Quelltext hin zu interaktiven Eingabemöglichkeiten längst vollzogen — und ein Schritt zurück scheint in den allermeisten Fällen absurd. Ich möchte einen radikalen Neuanfang wagen. Schon seit vielen Jahren denke ich oft darüber nach, was eine ideale saubere Programmiersprache ausmacht. Schon mehrere Fachgenossen, mit denen ich darüber sprach, sind der Meinung gewesen, dass Programmierung bald automatisiert wird. Ich kann mir eine komplette Automatisierung nicht vorstellen. Wiederholende Schritte werden durch Funktionsbanken minimiert. Bei Algorithmen sehe ich kein Bedarf nach künstlicher Intelligenz. Denn Probleme werden ausschließlich von Menschen, die der Schwäche des Willens unterliegen, beschrieben. Ein Rechner kann noch so viel verstehen — einen Willen zur Veränderung wird er nur haben, wenn dieser explizit einprogrammiert wurde — und allein dafür sehe ich schon keinerlei Anlass! Die Symbiose von menschlicher Kreativität und Willen in Verbindung mit künstlicher Intelligenz und Rechenkraft wird also in absehbarer Zeit bestehen bleiben. Also macht es Sinn, sich ein optimiertes Konzept zu suchen, mit dem der umzusetzende Wille dem Rechner kommuniziert werden kann — eine optimale Programmiersprache also. Analog zur Mathematik soll sie möglichst abstrakt und übertragbar sein. Um die Umsetzung in Maschinenbefehlsketten haben sich die Rechner zu kümmern! SSC selbst ist nicht annähernd so optimal, wie ich es mir wünsche. Aber für die Programmierung der "optimalen" Programmierumgebung reicht meines Ermessens nach keine der verbreiteten Programmiersprachen aus — daher muss SSC als Kompromiss einspringen. Für manche mag SSC mit ihrer Abstraktheit eher komplizierter als andere Sprachen sein. Für mich ist sie einfacher und ich mache generell nur wenige Denkfehler.
Gemessen an meinen Idealen ist SSC eine primitive Textsprache, die auch mit Stift auf Papier angewendet werden kann. Sie ist jedoch redundanzarmer und abstrakter als beispielsweise Javascript. Sie ist wie Skriptsprachen aufgebaut und kann als eine solche behandelt werden. Vor der Ausführung von Programmtext kann dieser aber auch in einen Bytecode übersetzt werden, welcher wiederum von einer virtuellen Maschine ausgeführt oder in eine Maschinenbefehlskette übersetzt werden kann.
Es gibt eine begrenzte Menge an Schlüsselbegriffen und vorgegebenen Klassen, welche alle höchstens 3 Zeichen umfassen. Dazu kommen Struktursymbole und Operatoren. Es besteht bei der Belegung dieser Symbole große Ähnlichkeit mit C-ähnlichen Sprachen (Java/JavaScript).
Wie bei vielen Skriptsprachen üblich, können in SSC viele redundante Klammern und Trennzeichen weggelassen werden. Das aus JavaScript bekannte (und berüchtigte) Konstrukt with(...){...}
findet in ähnlicher Form ständige Verwendung. Es ist in SSC sehr einfach, verschachtelte Klassen zu erstellen und auch set- und get-Methoden sind praktisch anwendbar. Ebenfalls sehr einfach können Unterklassen erstellt und erweitert werden. Enumeratoren sind fester Bestandteil und sie können als Schlüssel in Tabellen und als abstrakte Werte verwendet werden, anstatt intern Strings zu benutzen, wie es bei JavaScript und Java leider immer üblicher wird. Ein effektives Programm in SSC benötigt deutlich weniger Zeichen als in anderen Sprachen und lässt den zugrundeliegenden Algorithmus viel einfacher erkennen und manipulieren als mit alten Sprachen. Noch eine gewöhnungsbedürftige Änderung ist, dass Klammern und Trennzeichen optional sind und oft weggelassen werden können. Damit ist die Einrückung nicht völlig egal, wie es bei C-ähnlichen Sprachen der Fall ist. Zu allem Überfluss sind innerhalb einer Funktion direkte Sprünge möglich.
Es gibt eine umfangreiche zentrale Bank an Funktionen und Daten, die automatisch per Netzwerk auf allen angebundenen Rechnern zur Verfügung steht und in Programme einbezogen werden kann. Mittels automatischer Paketverwaltung können Bankelemente einfach im Programm adressiert werden — und werden bei der Installation automatisch geladen. Der Programmierer muss sich um nichts mehr kümmern außer um sein Programm — und sein Leben, natürlich. SSC kann in der generellen Entwicklungsumgebung (wie auch andere Datentypen) verfasst werden und ist voll in das neue Ökosystem integriert, welches ein Killer sämtlicher Betriebssysteme ist: Von Symbian bis Windows.
Um ein grobes Gespür zu vermitteln, biete ich hier ein kleines Beispiel. Alle verwendeten Sprachelemente werden natürlich später erklärt.
a=lop 2,1000
if idx>2 lop 2,idx-1
if a.idx%idx==0 a.cnt
.app.terminal.write a.idx
Dieses Programm durchläuft Zahlen von 2 bis 1000. Für jede Zahl wird geprüft, ob es eine kleinere gibt, die sie teilt. Falls es keine gibt, wird sie im Terminal ausgegeben. Zu beachten ist, dass die Überprüfung für 2 übersprungen wird, da sonst die Schleife über 2 und 2-1=1 ausgeführt laufen würde. Für 3 wird nur die 2 einmalig überprüft. Das gleiche lässt sich zum Spaß auch mit Sprungbefehlen lösen:
n=2
:c
a: n+!
m=2
b: if n%m==0 :d
m+!
if m<n :b
c: .app.terminal.write n
d: if n<1000 :a
Denjenigen, die daran keinen Spaß haben, zur Beruhigung: Durch automatische Optimierungen sollten beide Implementierungen die gleiche Befehlskette generieren. Man sieht deutlich, dass Sprungbefehle nicht für Geschwindigkeitssteigerung angeboten werden, sondern die Übersichtlichkeit unterstützen sollen.
Da ich schon lange programmierte, bevor ich anfing, alles zu wissen, verwende ich wahrscheinlich unübliche Begriffe. Außerdem möchte ich auch für Anfänger Klarheit schaffen.
Begriff (aka) | Erklärung | Beispiele |
---|---|---|
Block (scope) | Ansammlung an Anweisungen | if(...){...} |
Verweis (Variable) | Block-abhängig namentlich identifizierter Platzhalter für Objekte | sdl=43 |
Eigenschaft (Attribut) | Objekt-abhängiger Verweis auf Kinder-Objekte | ich.sdl=43 |
Objekt (Abstraktwert) | Instanz einer Klasse, die dessen Eigenschaften instanziell übernimmt; alle nachfolgenden Dinge sind auch Objekte | ich={.sdl=43} |
Funktion | Objekt, das einen Block repräsentiert und ausführbar ist | anull=fun a=0 |
Methode | Funktion als Eigenschaft, die sich auf das Eltern-Objekt bezieht | ich.hüpf! |
Klasse (constructor) | Objekt, von dem Instanzen gebildet werden können | Mensch=obj+{.alter=0} |
Wert (Primitivwert) | Einer der folgenden 3 Datentypen | |
numerischer Wert (Zahl) | Element der komplexen Zahlen | sdl=43 |
Auswahl-Wert (enum) | Individuell bestimmte Option | os=oses#debian |
Tabelle (map) | Zuordnung von Werten zu Schlüsseln | oses=[#debian:os!] |
boolescher Wert (Wahrheitswert) | Wahr oder falsch | x=sdl>42 |
Menge (set) | Menge, die Elemente enthält oder nicht | myoses=set!+[#debian] |
Vektor (array) | Menge, die Elemente in bestimmter Reihenfolge enthält | ospri=[#debian,#wxp] |
Symbol | Erklärung |
---|---|
abc | Verweis |
#abc | Auswahl |
"abc" | UTF-8-String |
.abc | Eigenschaft |
abc: | Sprungmarke setzen |
:abc | Springen zur Marke |
xi | Imaginäre Zahl |
-x | Negative Zahl |
/x | Stammbruch |
x+y , x-y , x*y , x/y , x%y , x^y | Addieren/Subtrahieren/Multiplizieren/Dividieren/Modulo/Potenzieren |
x&y , x|y , x~y | Und/Oder/Exklusiv-Oder |
~x | Nicht |
x<y , x<=y , x>y , x>=y | Numerischer Vergleich |
== , x~=y | Überprüfung Gleichwertigkeit |
=== , x~==y | Überprüfung Identität |
abc=... | Verweis/Eigenschaft auf Objekt setzen |
f! | Instanzieren |
f(...) | Instanzieren mit Parametern |
x~! | Boolesch umkehren |
x+! , x-! | Um 1 erhöhen/verringern |
x+=y , x-=y , x*=y , x/=y , x%=y , x^=y | Verrechnen und x überschreiben |
{...} | Objekt mit darauf bezogenen Anweisungen |
[...] | Tabelle/Menge/Vektor |
nul | Verweis ist Wertlos |
ret | Rückgabewert von Funktion |
req x | Tautologie |
if x ... | Bedingte Ausführung |
els ... | Sonstige Ausführung |
swt x[...] | Führt x aus der Sprungtabelle aus |
wfr x | Wartet bis Bedingung erfüllt |
lop | Schleife |
idx , itm | Aktuelles Schleifenelement |
brk , cnt | Block verlassen/neu beginnen |
lop ... | Endlosschleife |
lop x ... | Wiederholen, solange x wahr ist bzw. x-mal |
lop x,y ... | Von x bis y wiederholen |
Diese sind Trennzeichen und Klammern.
Kontext | Struktur |
---|---|
Block | {x;x} |
Parameter | (x,x) |
Tabelle/Menge/Vektor | [x,x] |
Sie können mit Ausnahme von []
weggelassen und ggf. durch Leerzeichen ersetzt werden, wenn die Struktur eindeutig ist. Werden sie weggelassen, so wird sich an Tabulator-Einrückung orientiert. Fehlt diese, so wird nach der Methode des minimalen Rahmens vorgegangen. Ich bevorzuge, Struktursymbole nur in Notfällen zu verwenden und möglichst viel einzurücken. ()
können benutzt werden, um Dinge zusammenzufassen.
Bei mangelhafter Einrückung/Abgrenzung können Dinge sehr kompliziert werden und schnell zu ungewollten Effekten führen. Beispiel:
g=f x=5
Dieser Text bedeutet g=f(x=5)
bzw. x=5;g=f(x)
— und nicht g=f;x=5
. Eine korrekte Einrückung oder ;
/()
würde Klarheit schaffen. Einrückung scheint mir am schönsten:
g=f
x=5
g=f
x=5
Ja, das eine Tabulatorzeichen kann den Unterschied machen! Nimmt f
im ersten Fall keine Parameter entgegen oder ist gar keine Funktion, wird der Syntaxfehler gemeldet. Ja, der Syntax ist von Verweis-Objekt-Klassen abhängig!
Längere Dokumentationen haben im Quelltext nichts zu suchen. Also gibt es ausschließlich einzeilige Kommentare, die durch ein //
begrinnen.
Folgende Klassen sind vorgegeben und können, wie alle importierten Dinge, nicht verändert, wohl aber erweitert werden.
Klasse | Erklärung | Beispiel |
---|---|---|
obj | Objekt | {} |
cls | Klasse | student=schüler+{.abitur=1} |
num | Zahl | 43 |
bol | Wahrheitswert | sdl==43 |
opt | Auswahlwert | #apfel |
map | Tabelle | [1:"s",5:"z"] |
arr | Vektor | [1,2,3] |
set | Menge | set!+[#A,#B]+[#C] |
str | Zeichenkette | "abc" |
fun | Funktion | fun x ret=x*2 |
Objekt | Erklärung | Beispiel |
---|---|---|
eul | Eulersche Zahl | e^5 |
pi | Kreiszahl | e^(pi/3) |
tru | Wahr | lop tru endlos! |
fal | Falsch | lop fal nie! |
mod | Modulobjekt | mod.ding=obj |
lib | Bank | wfr lib.time.point!+5 |