Folgende Begrifflichkeiten legen den Grundwortschatz eines Compilerbauers fest,
gleichwohl um welche
(formale)
Sprache es sich handeln sollte.
… oder auch der
Zeichensatz
der menschlichen Sprache
(wohl eher englisch)
umfasst alle Zeichen, die mit einer Tastatur bzw. mit einer Schreibmaschine erzeugt werden können
(Sonderzeichen eingeschlossen).
Das Alphabet der meisten Programmiersprachen entspricht in der Regel dem der englischen Sprache
(echte Teilmenge des ASCII-Zeichensatzes, neuerdings UTF-8).
Es gibt aber auch Programmiersprachen, die beispielsweise nur acht Zeichen verstehen
(wie zum Beispiel Brainfuck, eine Turing-vollständige Sprache).
Die Maschinensprache kennt nur zwei Zeichen, die
0
und die
1,
also ein Alphabet mit nur zwei Zeichen, das Binäralphabet.
Wie kommuniziert man mit einer Maschine, die nur zwei Zeichen versteht?
Möchte der Mensch eine Fremdsprache erlernen, dann lernt er nicht gleich einhunderttausend Vokabeln auswendig.
Um sich in der neuen Sprache verständigen zu können, genügt eigentlich ein Grundwortschatz von etwa 500 Vokabeln.
Unbekannte Begriffe werden umschrieben, der Text nimmt an Umfang zu.
Umgekehrt gilt natürlich: Je größer der Wortschatz, desto prägnanter die Formulierung.
So verhält es sich auch bei Programmiersprachen.
Ein Wort einer Programmiersprache wird als
Lexem
bezeichnet.
Eine
Low-Level-Sprache
kennt etwa 50 Lexeme, wenn man einmal von den Zahlen absieht.
Sie käme aber im Extremfall mit nur einer Handvoll aus;
ein paar Lexeme für Rechenoperationen und ein paar weitere für Speicherzugriffe.
Kennt der Programmierer alle Lexeme der jeweiligen Programmiersprache, kann er den Code zum einen prägnanter formulieren
und zum anderen das Programm bezüglich seiner Ausführungszeit
(Verbesserung der Laufzeit),
bezüglich seines Platzbedarfs während der Ausführung
(Verbesserung des Speicherbedarfs)
optimieren.
Um eine neue Programmiersprache zu erlernen, nimmt man normalerweise zu allererst ein Beispielprogramm namens
„Hello World”
unter die Lupe, um die ersten Lexeme einer Sprache kennen zu lernen.
Im folgenden
„Hello World”-Programm,
welches in PHP verfasst ist, lernt man zum Beispiel mit dem Lexem
echo
die Ausgabe des Lexems
„Hello World!”
kennen.
Mit diesem Programm könnte man schon fast behaupten, ein
PHP-Experte
zu sein.
:-)
<?php echo "Hello World!"; ?>
Das
Lexem
ist eine Zeichenkette, die in der Regel einem Token zugeordnet wird.
Ein
Token
gibt einer Menge von Lexemen lediglich einen Namen.
Im Folgenden sehen Sie ein Beispiel, das vier Lexeme dem Token
KLAMMER_AUF
zuordnet:
KLAMMER_AUF := {'(', '[', '{', '<'}
Oft definiert man ein Lexem mit einem regulären Ausdruck
wie zum Beispiel:
SIMPLER_TYP := 'boolean'|'byte'|'int'|'char'
UND := 'and'|','|'&'|'&&'
INTEGER := ('0'-'9')+
Mit Tokens abstrahiert man von konkreten Zeichenketten,
um beispielsweise Syntaxdefinitionen
(Grammatiken)
übersichtlicher bzw. abstrakter vornehmen zu können.
Grundsätzlich wird versucht, die benötigten Tokens
(Mengen von Lexemen)
disjunkt zu halten,
so dass sich zum Beispiel bei der Konstruktion einer Grammatik keine ungewollten Mehrdeutigkeiten einschleichen.
Wie Tokens nun verwendet werden, können Sie beispielsweise auf vorliegender Website unter
- Setty-Scanner
und
- Tinyray-Scanner
nachlesen.
Allein mit dem Alphabet und dem Wortschatz einer Sprache ist es noch nicht getan.
Eine Sprache ist geprägt von ihrer
Grammatik
beziehungsweise von ihrer
Syntax,
das gilt für natürliche Sprachen sowie für Computersprachen gleichermaßen.
Die Grammatik verleiht der Sprache Struktur. Die englische Sprache kennt zum Beispiel die
S-P-O-Regelung
(S für Subjekt; P für Prädikat; O für Objekt),
um Sätze zu bilden.
Nur
S-P-O-formulierte
Sätze sind, von ein paar Ausnahmen abgesehen,
grammatikalisch korrekte Sätze der englischen Sprache,
wobei die Struktur eines Satzteiles wiederum von Grammatikregeln vorgegeben wird.
Ohne Grammatik wäre die Umschreibung bzw. die Definition von weiteren Begriffen gar nicht vorstellbar.
Dabei soll aber nicht der Eindruck entstehen, dass grammatikalisch korrekte Sätze Sinn ergeben müssen,
wie dies zum Beispiel bei der Aussage
„Gestern ist morgen.”
in unserer Realität nicht der Fall ist.
Mit einer Grammatik wird lediglich festgelegt, welche Begriffe und Zeichen
(Token)
in welcher Reihenfolge stehen dürfen.
Grammatik G = (Nichtterminale, Terminale, Produktionen, Start) mit
Nichtterminale = {Objekt, Prädikat, Satz, Subjekt},
Terminale = {' ', '.', 'frisst', 'Hund', 'Katze', 'mag', 'Maus'},
Produktionen = {
Satz ::= Subjekt ' ' Prädikat ' ' Objekt '.',
Subjekt ::= 'Hund' | 'Katze' | 'Maus',
Prädikat ::= 'frisst' | 'mag',
Objekt ::= Subjekt
},
Start = Satz.
Die Grammatik
G
beschreibt bzw. erkennt simple
S-P-O-Sätze
mit Hund, Katze und Maus.
Es können mit der Grammatik
G
einige sinnvolle, jedoch auch unsinnige Sätze gebildet bzw. erkannt werden.
- Unsinnig, aber syntaktisch korrekt:
Maus frisst Katze.
Maus mag Katze.
- Sinnvoll:
Katze frisst Maus.
Hund frisst Katze.
Maus mag Hund.
Die Grammatik kümmert sich nicht um Sinn oder Unsinn eines Satzes,
sondern nur um die Struktur.
Die Frage nach Sinn bzw. Unsinn eines Satzes gehört der Semantik an.
Eine Sprache ist lediglich eine Menge von ausgewählten Zeichenketten.
Ein Element dieser Menge wird manchmal als
Wort
(kein Lexem)
bezeichnet.
- Ein Element der deutschen Sprache ist beispielsweise ein deutschsprachiger Brieftext
oder der gesamte Inhalt eines deutschsprachigen Romans oder auch nur ein simpler, deutscher Satz.
- Ein Element einer Programmiersprache ist ein Programm.
- Die Sprache einer Programmiersprache enthält
alle
Programme, der zugrundeliegenden Programmiersprache.
- Eine Programmiersprache
P
ist die Menge aller Programme von
P.
Offensichtlich existieren sehr viele Sprachen, die unendlich viele Elemente enthalten,
also unendlich sind, d. h. nicht regulär sind,
sodass es einem fast schon schwer fallen könnte, eine endliche Sprache anzugeben.
:-)
Als Beispiel einer endlichen Sprache möchte ich Ihnen die von der oben vorgestellten Grammatik
G
induzierte Sprache
L(G)
vorstellen.
L(G) = {
'Hund frisst Hund.', 'Hund frisst Katze.', 'Hund frisst Maus.',
'Katze frisst Hund.', 'Katze frisst Katze.', 'Katze frisst Maus.',
'Maus frisst Hund.', 'Maus frisst Katze.', 'Maus frisst Maus.',
'Hund mag Hund.', 'Hund mag Katze.', 'Hund mag Maus.',
'Katze mag Hund.', 'Katze mag Katze.', 'Katze mag Maus.',
'Maus mag Hund.', 'Maus mag Katze.', 'Maus mag Maus.'
}
Die Sprache
L(G)
ist endlich
(nur 18 Elemente bzw. 18 Wörter),
überschaubar
und zweifellos eine Teilmenge der deutschen Sprache.
Später werden Sie sehen, wie die Sprache
L(G)
zur Programmiersprache wird.
Wenn eine Sprache von einer Grammatik
induziert
(erzeugt)
wird, so enthält sie genau die Elemente,
die von der zugrunde liegenden Grammatik beschrieben bzw. erkannt
(akzeptiert)
werden.
Von einer Grammatik induzierbare Sprachen sind
rekursiv-aufzählbare
Sprachen.
Abgrenzend muss ich noch klar stellen, dass es auch Sprachen gibt, die nicht von einer Grammatik induziert werden können,
also nicht
rekursiv-aufzählbar
sind.
Es stellt sich mir gerade die philosophische Frage, ob die vollständige deutsche Sprache überhaupt
rekursiv-aufzählbar
ist.
Oder kurz: Kann sich der Mensch mit dem Computer
‚natürlich’
unterhalten?
Programmiersprachen gehören generell zur Klasse der
rekursiv-aufzählbaren
Sprachen.
Der Computer benötigt nämlich zur Interpretation eines Programms eine Grammatik
— Geht es auch ohne Grammatik? —
der zugrundeliegenden Programmiersprache.
Die Klasse der kontextfreien Sprachen, eine Unterklasse der Klasse
rekursiv-aufzählbarer
Sprachen, ist von zentraler Bedeutung für Nutzer und (Er)Bau(e)r einer Programmiersprache.
Gemäß der
Chomsky-Hierarchie
gehören die kontextfreien Sprachen zu den von
Typ-2-Grammatiken
erzeugten Sprachen,
die oben gezeigte Grammatik
G
ist zum Beispiel eine solche
Typ-2-Grammatik
und somit eine kontextfreie Grammatik
— eine Regel hierfür lautet: Links einer jeden Produktion steht genau ein Nichtterminal.
Will man allerdings einen Kontextbezug herstellen wie zum Beispiel für die Erzeugung aller sinnvollen Sätze/Wörter,
benötigt man zumindest eine kontextsensitive Grammatik.
Der Vorteil wäre hierbei, dass man sich einen
Semantik-Check
sparen könnte
— aber mehr dazu unter Semantik.
Wie könnte denn eine kontextsensitive Grammatik für die
Hund-Katze-Maus-Sprache
aussehen?
Mit der
Semantik
wird festgelegt,
welche Bedeutung den syntaktischen Einheiten zukommt.
Was nun ein grammatikalisch korrekter Satz auf dem Rechner bewirken soll,
wird mit der Semantik der jeweiligen Programmiersprache eindeutig festgelegt.
Die Prüfung der Bedeutung eines Satzes, ob Sinn oder Unsinn, wird als
Semantik-Check
bezeichnet.
Einen
Semantik-Check
für eine endliche Sprache zu realisieren, ist im Prinzip sehr einfach, weil es nur endlich viele Elemente zu prüfen gibt.
Man entfernt aus der endlichen Sprache die Elemente, die keinen Sinn ergeben,
und überprüft, ob sich der Input als Element in der Sprachdifferenz befindet.
Als Beispiel sehen Sie die Sprachdifferenz
D
bezüglich der Sprache
L(G).
D = L(G) / {
'Hund frisst Hund.', 'Hund frisst Maus.',
'Katze frisst Hund.', 'Katze frisst Katze.',
'Maus frisst Hund.', 'Maus frisst Katze.', 'Maus frisst Maus.',
'Hund mag Katze.',
'Katze mag Hund.', 'Katze mag Maus.',
'Maus mag Katze.'
} = {
'Hund frisst Katze.',
'Katze frisst Maus.',
'Hund mag Hund.', 'Hund mag Maus.',
'Katze mag Katze.',
'Maus mag Hund.', 'Maus mag Maus.'
}
In diesem Beispiel bin ich jedoch vom Basiskontext ausgegangen.
Es könnte auch sein, dass in einem anderen Kontext
(spezielles Wissen, spezielle Zusammenhänge)
auch der Satz
‚Maus mag Katze.’
semantisch korrekt sein kann.
Der Mensch gibt dem Computer vor, was er verstehen soll
— Gewissen und Ethik gehen hierbei im gewissen Sinne vom Menschen aus.
Hat man es aber mit einer unendlichen Sprache zu tun,
muss man sich Strategien überlegen, die semantisch fehlerbehaftete Elemente der Sprache herauszufiltern.
Streng genommen wäre ein
Semantik-Check
gar nicht nötig,
wenn bereits die zugrundeliegende Grammatik keine semantisch fehlerbehafteten Elemente mehr zuließe.
Doch dann würde diese Grammatik in der Praxis kaum handhabbar sein
und eine kontextsensitive Sprache induzieren
— der Kontext müsste der Grammatik bekannt sein.
Die Skeptiker unter Ihnen könnten sich jetzt fragen, was nun das
Hund-Katze-Maus-Beispiel
mit Programmiersprachen zu tun hat und das mit Recht,
denn es fehlt noch die Angabe einer Maschine, die die Sprachelemente aus dem
Hund-Katze-Maus-Beispiel
erwartet.
Eine Definition einer
abstrakten Maschine
zur Sprache
L(G)
macht sie automatisch zur Programmiersprache.
Ich definiere eine abstrakte Maschine, die die
Infix-Relationen
‚frisst’
und
‚mag’
gemäß meiner Vorstellung von Fressen und Mögen bildlich darstellt.
Diese Maschine erwartet als Eingabe ein Element der Sprache
L(G).
Die Akteure sind gemäß der Eingabe: Hund, Katze, Maus.
Mit dieser Maschinendefinition wird das hier vorgestellte Sprachbeispiel
L(G)
— wenn auch ein wenig an den Haaren herbeigezogen —
zur Programmiersprache.
Es gibt noch primitivere Programmiersprachen als das
Hund-Katze-Maus-Beispiel
sogar für reale Maschinen!
Ich denke da gerade an einen Lichtschalter mit der Programmiersprache
{'an', 'aus'},
der bei Eingabe
'an'
bzw.
'aus'
den Stromkreis mit einer Glühbirne als Verbraucher schließt bzw. unterbricht.
Die Eingabe eines Programms der Programmiersprache
{'an', 'aus'}
wird von einem Kippschalter
(Lichtschalter)
simuliert.
- Das Betätigen eines Lichtschalters ist eine simple Form des Programmierens.
- Eine Person, welche einen Lichtschalter betätigt, ist ein Programmierer.
- Ein Handbuch zur korrekten Benutzung eines Lichtschalters ist ein Programmierhandbuch.
- Viele programmieren tagtäglich extrem viele Programme, ohne sich dessen überhaupt bewusst zu sein.
- Weitere Beispiele programmierbarer Maschinen:
- Haustür
- Heizung
- Mikrowellengerät
- Waschmaschine
- Fernseher
- Computer
- Philosophisch gesehen, kann jede Aktion, die eine bestimmte Reaktion auslöst,
als Akt des Programmierens angesehen werden
(Anhänger Newtons mögen es mir verzeihen).
Wie viele Programme haben Sie heute eigentlich schon programmiert?
:-)