LPC (Programmiersprache)
LPC ist eine objektorientierte Programmiersprache, in der Syntax ähnlich wie C oder C++, für Netzwerk-Textabenteuerspiele (Multi User Dungeons, kurz MUDs).
LPC ist eine Mischung aus Interpreter- und Compilersprache. LPC-MUDs erlauben in der Regel, zur Laufzeit Programme hinzuzufügen, zu starten und auch nachträglich zu ändern, ohne das ganze Spiel neu zu starten. Der LPC-Code wird von einem Compiler zuerst in einen Bytecode umgewandelt und danach von einem Interpreter ausgeführt.
Geschichte
Die Bezeichnung LPC leitet sich vom Erfinder der Sprache, Lars Pensjö, ab.[1] Pensjö entwickelte LPC Anfang der 1990er-Jahre und orientierte sich dabei an der Programmiersprache C. Von der ähnlichen Syntax abgesehen gibt es jedoch nur wenige Gemeinsamkeiten der beiden Sprachen.
Bei den ursprünglichen Implementierungen von LPC waren Compiler und Bytecode-Interpreter Bestandteile eines einzelnen Programms, das grundlegende, für den Betrieb eines Multi User Dungeons notwendige Funktionalität zur Verfügung stellte, den sogenannten Gamedriver oder kurz Driver. Auch das MUD Amylaar trug wesentliche Teil zum Treiber bei. Dazu gehört vor allem der Betrieb als Server, zu dem mit einem Telnet- oder einem speziellen MUD-Client eine Verbindung hergestellt werden kann. Diese Software wurde unter dem Namen LPMud verbreitet; eine moderne Weiterentwicklung auf Basis des ursprünglichen LPMud-Quellcodes hört auf die Bezeichnung LDMud (nach den Initialen des Maintainers). Eine weitere Implementierung desselben Konzeptes inklusive der Sprache LPC ist MudOS.
Aus LPC ging die unabhängige Scriptsprache Pike hervor. Diese hat bisher keinen hohen Verbreitungsgrad erreicht.
Merkmale
LPC unterstützt die folgenden Programmiertechniken:
Unterschiede zu anderen C-ähnlichen Sprachen
Die grundlegende Syntax von LPC entspricht der der Sprache C. Dazu gehören die verwendeten Zeichen zur Kennzeichnung von Blöcken, Funktionen, Argumenten und Argumentlisten sowie die Benutzung des Semikolons als Endzeichen eines Statements.
Im Unterschied zu C kennt LPC keine stark typisierten Variablen. Eine Variable kann zwar mit einem vorher bestimmten Typ angelegt werden, dann jedoch trotzdem einen Wert eines anderen Typs aufnehmen. Zusätzlich existiert der Typ mixed für untypisierte Variable.
Das wirkt sich auch auf Rückgabewerte und Parameter von Funktionen aus: im Normalfall müssen in der Funktionssignatur weder die Argumente noch Rückgabewert mit Typen versehen werden. Man kann jedoch durch Angabe von Präprozessor-Direktiven eine Überprüfung auf gültige Argumente zum Übersetzungszeitpunkt erzwingen:
- #pragma strong_types – Typen für Argumente und Rückgabewert müssen angegeben werden
- #pragma strict_types – der Rückgabewert von an fremden Objekten gerufenen Methoden muss gecastet werden
Die Klassenbibliothek eines MUDs
Die Sprache LPC kennt nur eine geringe Zahl von Standardfunktionen (efuns). Diese sind im ausführenden Programm (gamedriver), also beispielsweise LPMud oder LDMud, implementiert. Zusätzlich hat jedes MUD, das mit einer solchen Software betrieben wird, eine eigene Klassenbibliothek (MUDlib). Die MUDlib enthält Schnittstellen, mit denen der gamedriver einen Teil der Verantwortung an sie zurückgeben kann. Dazu gehört unter anderem das Erzeugen eines Spielerobjektes für einen einloggenden Spieler oder die Fehlerbehandlung. Es gibt hierbei keine Standard-MUDlib, die von allen MUDs genutzt würde. Einige sich im Einsatz befindliche MUDlibs sind jedoch Open Source oder auf andere Weise frei verwendbar. Aufbauend auf wenigen frei verfügbaren MUDlibs sind mehrere weit verbreitete MUDlib-Zweige entstanden, die in vielen MUDs eingesetzt werden. Im deutschsprachigen Raum sind beispielsweise Abkömmlinge der MUDlibs der MUDs MorgenGrauen einerseits und UNItopia andererseits verbreitet.
Unterschiede bezogen auf die Laufzeitumgebung
Von der sprachlichen Seite abgesehen unterscheidet sich LPC auch in mehreren Punkten in Bezug auf die Definition der Laufzeitumgebung von anderen Programmiersprachen. Durch die ursprüngliche Zielsetzung als Sprache eines Online-Rollenspiels, bei dem mehrere Entwickler mit unterschiedlichen Rechten eigenen Code einbringen können, enthält die Laufzeitumgebung beispielsweise sogenannte privilegierte Funktionen, die nur nach vorheriger Überprüfung durch ein Master-Objekt des MUDs von einem normalen Objekt aufgerufen werden können.
Aktuelle LPC-Laufzeitumgebungen unterstützt keine Threads. In einem einzelnen Thread werden nacheinander alle anfallenden Aufgaben erledigt. Dadurch ergibt sich notwendigerweise eine Begrenzung der Laufzeit, die für die Behandlung eines zusammenhängenden Ereignisses gesetzt ist. In LDMuds kann diese Begrenzung dynamisch für einzelne Funktionsaufrufe aufgehoben oder gesetzt werden.
Eine weitere Besonderheit ist die fast vollständige Gleichwertigkeit der eingebauten Funktionen (efuns) mit von der Klassenbibliothek eines MUDs (MUDlib) vorgegebenen simul_efuns. Auf diese Weise ist es möglich, Standardfunktionen zu überschreiben oder den Zugriff darauf zu verbieten.
Unterschiede bei der Objektorientierung
LPC kennt im Gegensatz zu vielen objektorientierten Programmiersprachen keine Unterscheidung zwischen Klassen und Objekten. Im Normalfall wird aus einer Quelltext-Datei ein einzelnes Objekt erzeugt. Alle im Speicher befindlichen Objekte können jedoch dupliziert (geklont / cloned) werden. Der Vorgang des Clonens ist vergleichbar mit dem der Instanziierung bei anderen Programmiersprachen; allerdings sind der Klon (clone) und das Original (blueprint) gleichberechtigt und können beide theoretisch auf die gleiche Weise eingesetzt werden. Dabei ist aber zu berücksichtigen, dass das Objektuniversum der LPC-Welt jedem Objekt ein quasi-physisches "befindet-sich-in"-Attribut zuordnet (das Avatar-Objekt des Spielers befindet sich in einem Raum, die Habseligkeiten des Spielers befinden sich in diesem virtuellen Körper usw.), und dass ein Blueprint, der einmal die Welt betreten hat, zwar noch bewegt, aber nicht mehr geklont werden kann; nur wenn das Blueprint-Objekt zerstört und aus dem Quelltext neu erzeugt wird, befindet es sich wieder außerhalb der Welt. (Das Enthaltensein ist eine elementare Eigenschaft des Gamedrivers, nicht der MUDlib.)
Besondere Konstrukte
LPC kennt das Konzept des Shadowing. Dabei handelt es sich um eine Möglichkeit, alle Zugriffe auf eine Funktion eines Objektes von außen abzufangen, beispielsweise um sie zu filtern oder ganz zu unterbinden, ähnlich dem Decorator-Muster. Das Prinzip wird angewandt, indem ein Objekt mit der efun shadow() sich als "Schatten" eines beliebigen anderen Objektes registriert. Bei einem Aufruf einer Funktion des Zielobjektes, der nicht innerhalb des Zielobjektes direkt geschieht, wird dann die gleichnamige Funktion im Schatten-Objekt aufgerufen, falls dieses Objekt eine solche Funktion enthält. Existiert im Schatten-Objekt keine Funktion gleichen Namens, wird der Aufruf wie ein normaler Funktionsaufruf in einem Objekt ohne Schatten behandelt. Das Schatten-Objekt hat ebenfalls die Möglichkeit, Funktionen im Zielobjekt, das es "überschattet", aufzurufen.
Besondere Datentypen
Funktionen sind in LPC keine First-Class-Objekte (Funktionen erster Ordnung). Dennoch kennt die Sprache Funktionsreferenzen. Aus der Welt der Funktionalen Programmierung entlehnt ist der Name closure für diesen Datentyp, in Anlehnung an den Begriff Closure. Die referenzierte Funktion kann auch mit der Funktion lambda() zur Laufzeit dynamisch erzeugt werden. Dabei wird aus einem Array von Closures und Symbolen zur Laufzeit eine neue Funktion erzeugt, die dann, wie jede andere closure, als Wert übergeben oder als Funktion aufgerufen werden kann. Die in der LPMud-Variante LDMud eingesetzte LPC-Fortentwicklung enthält zusätzlich die Möglichkeit, zur Compilezeit Closures auf formlos angegebene Inline-Funktionen zu erzeugen. Dazu wird die sogenannte Smiley-Notation verwandt, bei der normaler LPC-Code innerhalb von (: und :) vom Compiler in eine normale Funktion des Objektes umgesetzt wird und der Ausdruck an der Stelle seines Auftretens durch eine auf diese Funktion verweisende closure ersetzt wird.
Arrays existieren in LPC und können im Code mit den einzelnen Elementen, durch Kommata getrennt und von ({ und }) umschlossen, erzeugt werden. Der Zugriff auf einzelne Elemente geschieht mit dem Indexoperator [] vergleichbar zu C. Zusätzlich gibt es Standardfunktionen zur Behandlung von Arrays. Arrays sind, ebenso wie andere Variable auch, nicht typisiert. Das bedeutet, dass die Elemente eines Arrays unterschiedliche Typen haben können. Weitere Arrays sind ebenfalls als Elemente eines Arrays erlaubt. Dadurch ist es in LPC möglich, komplexe Strukturen als Arrays aufzubauen. Eine Variable, die ein Array halten können soll, wird durch einen dem Elementtyp hintangestellten Asterisk (*) gekennzeichnet. Dabei wird jedoch keine Typprüfung vorgenommen. Variable vom Typ mixed können ebenfalls ein Array enthalten.
LPC kennt zusätzlich außerdem sogenannte assoziative Arrays vergleichbar mit den Wörterbüchern der Sprache Python. In LPC wird dafür die Bezeichnung mapping verwandt. Diese mappings sind als Hashtabelle implementiert. In einem assoziativen Array kann einem Schlüsselelement ein Wert zugeordnet werden. Eine Besonderheit der assoziativen Arrays in LPC ist die pro mapping variable Anzahl von Werten pro Schlüssel. So kann ein Mapping beispielsweise zu jedem Schlüssel eine beliebige, aber innerhalb des mappings gleiche Anzahl von Werten enthalten. Um diese Werte anzusprechen, kann zusätzlich zum Schlüssel bei einer Indizierungsoperation noch ein numerischer Index angegeben werden.
Hallo-Welt-Programm in LPC
Das folgende Objekt gibt beim ersten Laden den Text "Hallo, Welt!" aus, da die create()-Methode üblicherweise beim Laden/Clonen von Objekten automatisch aufgerufen wird.
void create() { write("Hallo, Welt!"); }
Komplexeres Beispielobjekt
LPC wird hauptsächlich zur Beschreibung von Objekten in Rollenspielen eingesetzt. Solche Objekte sind in vielen Fällen Gegenstände, Einwohner oder Räume einer virtuellen Welt. Die Klassenbibliothek (MUDlib) enthält deshalb typischerweise Objekte, die ganz allgemein einen Gegenstand – oder eine bestimmte Klasse von Gegenständen – repräsentiert. Ähnliches gilt für Räume, Monster und alle anderen Objekte, die häufig in verschiedenen Variationen erzeugt werden müssen.
Das folgende Beispiel bezieht sich auf die Anwendung von LPC zusammen mit einer vorhandenen Klassenbibliothek, die nicht zum Lieferumfang von LPMud gehört. Es implementiert einen Apfel und erbt von einem Standardobjekt, das vom Spieler essbare Nahrung implementiert. Dazu gehört unter anderem die zur Implementierung des Ess-Vorgangs im Spiel notwendige Logik. Im erbenden Objekt müssen so nur noch die Werte von Eigenschaften eingestellt werden, die im Rahmen der Möglichkeiten der Basisklasse die Auswirkungen des Verzehrs bestimmten. Die in diesem Beispiel referenzierte MUDlib stellt hierzu die Funktion SetProp() zur Verfügung. Die Namen der Eigenschaften sind dabei Präprozessor-Makros. Es ist, je nach MUD, auch üblich, auf Eigenschaften über einzelne Accessor-Funktionen zuzugreifen.
inherit "/std/food";
#include <properties.h>
#include <language.h>
#include <food.h>
void create()
{
if(!is_clone(this_object())) return;
::create();
SetProp(P_SHORT, "Ein Apfel");
SetProp(P_LONG, "Dieser Apfel ist schoen prall und rot. Er schmeckt sicher vorzueglich.");
AddId( ({"apfel", "\napfel"}) );
SetProp(P_NAME, "Apfel");
SetProp(P_GENDER, MALE);
SetProp(P_VALUE, 50);
SetProp(P_WEIGHT, 50);
SetProp(P_MATERIAL, ([ MAT_FRUIT: 100 ]) );
SetProp(P_FOOD_INFO,
([ F_HEAL: ({10,10}),
F_SOAK: 5,
F_MSG: "Du isst den leckeren roten Apfel.",
F_MSG_ROOM: "isst einen leckeren roten Apfel."]) );
}
Einzelnachweise
- Xyllomer.de: The LPC Reference Manual. Abgerufen am 20. April 2016.