LPC (programovací jazyk)
Author
Albert FloresLPC je objektově orientovaný programovací jazyk, vyvinutý původně Larsem Pensjöem (podle něho iniciály LP) na základě jazyka C. Původně byl určen především k programování MUDů, jeho snadnost a všestrannost však vedla k jeho používání i mimo tuto oblast a ke vzniku nových a samostatně se dále vyvíjejících implementací, z nichž nejrozšířenější je známa jako programovací jazyk Pike.
Struktura jazyka
LPC je strukturou velmi podobný programovacím jazykům C a C++. Je objektově orientovaný, ale neužívá pojmu třídy. +more Počítačový program se skládá z programů jednotlivých objektů; některé z nich se pak vyskytují jedinečně, zatímco jiné fungují jako prototypy (blueprint), z nichž se vytvářejí klony (clone). Jedinečné objekty a prototypy jsou uloženy v oddělených souborech (zpravidla s příponou . c) v adresářové struktuře programové knihovny (mudlib), odkud je v případě potřeby LP ovladač (driver) načítá do paměti a kompiluje, a v případě nepotřeby z paměti opět odstraňuje.
Program se spouští načtením a kompilací řídicího objektu (master object); ten pak načte do paměti další objekty, jejichž přítomnost je pro běh programu bezpodmínečně nutná. Další objekty, které podle průběhu programu mohou, ale nemusí být zapotřebí, se načítají nebo klonují z prototypů na základě požadavků jiných objektů.
Programový kód objektů se skládá z deklarací globálních (v rámci celého objektu platných) proměnných a z popisu funkcí. Proměnné definují vlastnosti (atributy) objektu, některé z funkcí vystupují navenek jako metody, jimiž spolu objekty navenek komunikují. +more Proměnné jsou zvenčí objektu přístupné zpravidla jen pomocí k tomu určených metod (v LPC tradičně odlišovaných předponami set_ a query_).
Všechny klony jednoho objektu mají k dispozici stejné metody a stejné proměnné (které samozřejmě mohou mít různý obsah). Objekty mohou dědit vlastnosti a metody jiných objektů stejným způsobem, jako je dědí třídy v programovacích jazycích, které pojem třídy používají. +more Většina implementací LPC navíc umožňuje stínění (shadowing), kdy mohou metody jednoho objektu překrýt metody jiného objektu, takže se zastíněný objekt navenek (přechodně) chová jinak, aniž je sám nějak změněn.
Protože jazyk LPC byl původně navržen pro programování MUDů, počítá se s tím, že herní objekty (hráči, nehráčské postavy, předměty, místnosti) jsou objekty též v LPC, pročež se mohou vzájemně obsahovat (stojí-li hráč v místnosti a má s sebou svačinu v batohu, pak objekt místnosti obsahuje objekt hráče, objekt hráče obsahuje objekt batohu a objekt batohu obsahuje objekt svačiny), být někde spolupřítomny a podle toho si vzájemně nabízet různé metody (např. hráč může hovořit s postavou přítomnou v téže místnosti). +more Standardním konstruktorem je create, které v mnoha případech může být jedinou metodou explicitně popsanou v objektu (další metody pak objekt dědí z prototypu):.
inherit "/i/room"; // tímto zdědíme proměnné a funkce prototypu místnosti
void create { // nejdříve spustíme konstruktor definovaný v prototypu: ::create; // a poté nastavíme některé zděděné proměnné: set_short("V obyčejné místnosti"); set_long("Nacházíš se v obyčejné místnosti, na které není vůbec, " "ale vůbec nic zvláštního. Ale aspoň to tu voní. +more"); set_smell("Hm, voní to tu hezky. "); set_exit("sever","/room/example"); }.
Odkaz na "/i/room" v uvedeném příkladu není v LPC obecně platný, stejně jako například funkce set_smell. Jsou to vlastnosti konkrétní mudové knihovny, která v tomto případě obsahuje prototypový soubor místností na adrese "/i/room. +morec" v rámci svého adresářového stromu, a která v tomto prototypu (nebo některém souboru jím děděném) definuje funkci set_smell. Charakter konkrétní implementace LPC je tak z velké části dán používanou mudovou knihovnou. Každá implementace má přinejmenším svou základní mudovou knihovnu, často však konkrétní MUDy nebo skupiny MUDů pracují s mnohem rozsáhlejšími vlastními knihovnami, které umožňují jemnější zacházení s herními objekty a řízení průběhu hry.
Ahoj světe
Vzhledem k charakteru LPC není možné říci „Ahoj světe“ jednoznačným způsobem. Program nemá výstup ve vlastním smyslu slova; výstup pro uživatele programu (tedy zpravidla hráče MUDu) je zprávou, kterou některý z objektů poslal interaktivnímu objektu, který ztělesňuje uživatele.
Nejjednodušším výstupem je poslání zprávy při vytvoření objektu, a to interaktivnímu objektu, který vytvoření způsobil:
void create { write("Ahoj, světe!"); }
Má-li být zpráva odeslána uživateli, který se právě s objektem setkal (tedy ocitl se s ním v téže místnosti, objekt se ocitl v něm samotném nebo on sám se ocitl v objektu), je nutno zavolat funkci write ve funkci s vyhrazeným názvem init. Má-li být zpráva odeslána po zadání určitého hráčského příkazu, pak je nutno tento hráčský příkaz ve funkci init přiřadit určité funkci, a v té potom zavolat write.
Kromě write obsahuje LPC standardně ještě výstupní funkce printf (funguje jako write, ale umožňuje složitý formátovaný výstup), say (pošle zprávu všem dalším objektům přítomným v téže místnosti jako vysílající objekt), tell_object (pošle zprávu danému objektu) a tell_room (pošle zprávu všem objektům nacházejícím se v dané místnosti). Mnoho mudových knihoven však nabízí sofistikovanější a programátorsky výkonnější funkce pro předávání zpráv. +more Vyslání zprávy hráči v reakci na jeho příchod do místnosti, v němž se nachází vysílající objekt, pak může vypadat třeba takto (příklad použitelný například v knihovně mudu UNItopia nebo Prahy):.
inherit "/i/item/message";
#include
void init { send_message_to(this_player,MT_NOTIFY,MA_MOVE_IN,wrap("Ahoj, hráči!")); }
void create { // tady by v reálu něco bylo, ale pro tento příklad nás to nezajímá }
Datové typy
Objekt (object) : Typ dostupný ve všech implementacích. Proměnná typu objekt obsahuje paměťový ukazatel na libovolný objekt (ať už prototyp, nebo klon), který se účastní běhu programu (tedy jak prototyp, tak klon). +more Konstanta tohoto typu se nedá zapsat, je možné jen řetězcem popsat soubor, ze kterého byl objekt vytvořen, nebo opět řetězcem popsat jednoznačné jméno objektu:.
object ob1 = touch("/apps/database"); // vytvoření jedinečného objektu // ze souboru /apps/database.c object ob2 = clone_object("/obj/batoh"); // vytvoření klonu (pokud dosud nebyl // vytvořen prototyp, pak se nyní vytvoří)
Celé číslo (int) : Typ dostupný ve všech implementacích, zpravidla jako 32bitové celé číslo se znaménkem, v některých implementacích jako číslo 64bitové. Konstanta se zapisuje jako běžné celé číslo s případným znaménkem.
int cislo1 = 789; int cislo2 = -45;
Desetinné číslo (float) : Typ dostupný ve všech implementacích, většinou jako 32bitové číslo s pohyblivou řádovou čárkou. Konstanta se zapisuje jako celočíselná a desetinná část oddělené tečkou s případným předsazeným znaménkem.
float pi = 3.1415;
Řetězec (string) : Typ dostupný ve všech implementacích. Řetězec znaků o proměnné (prakticky libovolné) délce. +more Konstanta se zapisuje jako text uzavřený do programátorských uvozovek (případné programátorské uvozovky se do textu zapisují jako dvojznak \").
string text = "Nějaký pěkný textík.";
Smíšená hodnota (mixed) : Typ dostupný ve všech implementacích. Proměnná tohoto typu může nabývat hodnot různých typů, což v praxi znamená vypnutí typové kontroly pro tuto proměnnou.
mixed anything = 13.56; anything = 333; anything = "blablabla";
Pole (*|array) : Typ dostupný ve všech implementacích. Pole (seznam) hodnot jiného typu o proměnném (prakticky libovolném) počtu prvků. +more Deklaruje se zpravidla pomocí modifikátoru * za označením typu obsažených hodnot, tedy např. object *, string *, int *, jen v implementaci MudOS pomocí modifikátoru array (modifikátor * je však rovněž přípustný). Modifikátor nemůže být skládán, tedy případné pole polí je nutno deklarovat jako mixed *, tedy pole smíšených hodnot. Konstanta se zapisuje jako seznam hodnot oddělených čárkou (s volitelnou čárkou i za poslední hodnotou) a ohraničený dvojznaky ({ a }).
int * cisla = ({ 1, 2, 3 }); string * retezce = ({ "ble", "bla", "blu" }); mixed * vsechno = ({ 1, "text", -56.5, ({ "podpole", 2 }) });
Asociativní pole (mapping): Typ poskytovaný téměř všemi implementacemi, neuspořádané pole klíčů, k nimž jsou přiřazeny hodnoty. Konstanta se zapisuje jako seznam přiřazení mezi klíčem a hodnotou oddělený čárkami (s volitelnou čárkou po posledním přiřazení) a uzavřený do dvojznaků ([ a ]), přičemž přiřazení se zapisuje jako klíč a hodnota, oddělené od sebe dvojtečkou. +more Klíčem mohou být zpravidla všechny jednoduché typy (čísla, řetězce, objekty), hodnotami všechny typy. Některé implementace umožňují vícenásobná asociativní pole, tedy přiřazení více hodnot každému klíči (ale všem klíčům týž počet) - pak se jednotlivé hodnoty oddělují středníkem.
mapping tabulka = ([ "jméno" : "Josef", "příjmení" : "Novák", "výška" : 183, "děti" : ({ "Marie", "Anna", "Jan" }), ]);
Klauzura (closure|function) : Typ podporovaný implementacemi Amylaar LPMud, LDMud, MudOS a některými dalšími, v MudOSu deklarovaný klíčovým slovem function, v ostatních implementacích klíčovým slovem closure. Jedná se o ukazatel na funkci nebo samostatnou a vůči objektu uzavřenou část kódu, kterou je pak možno například předávat jako parametr dalším funkcím nebo sdělovat jiným objektům. +more Konstanta se zapisuje buď jako dvojznak #' následovaný jménem lokální (v objektu definované) funkce, nebo jako úsek kódu uzavřený do dvojznaků (: a :), nebo jako funkce lambda, umožňující zápis výrazů, které je nutno vyhodnotit při každém výskytu znova, v lambda kalkulu. MudOS podporuje pouze druhý typ zápisu, Amylaar LPMud a LDMud všechny tři.
closure c1 = #'query_je_to_k_necemu; closure c1 = (: $1->query_invis?"Někdo":$1->query_short :); closure c1 = lambda( ({ 'a, 'b }), ({ #'>, 'a, 'b }) );;
Symbol (symbol) : Typ podporovaný implementacemi, které umožňují konstrukci lambda klauzur (zejména Amylaar LPMud a LDMud), v nichž symboly zastupují proměnné obdobně, jako klauzury zapsané pomocí #' zastupují funkce. Konstanta se zapisuje jako programátorský apostrof následovaný jménem symbolu.
symbol var = 'x;
Chráněné pole (quoted array) : Typ podporovaný v implementacích, které umožňují vytváření lambda klauzur, v nichž má běžně zapsané pole význam vložené funkce. Aby bylo možno používat v rámci lambda zápisu pole, je nutno je odlišit buď uzavřením do funkce quote, nebo v případě konstant předsazením programátorského apostrofu. +more Typ nemá žádné vlastní klíčové slovo pro deklaraci, chráněná pole proto musí být deklarována jako mixed.
mixed nedopole = '({ 1, 2, 3 });
Záznam (class|struct) : Typ podporovaný v implementacích LDMud od verze 3. 3 a MudOS. +more V LDMudu se deklaruje klíčovým slovem struct, v MudOSu klíčovým slovem class. Umožňuje sdružení několika proměnných různých typů do jednoho záznamu.
struct udalost { int rok; string nazev; }; struct udalost napr = (); napr->rok = 1415; napr->nazev = "Upálení Mistra Jana Husi"; | class udalost { int rok; string nazev; }; class udalost napr = new(class udalost); napr->rok = 1415; napr->nazev = "Upálení Mistra Jana Husi"; |
---|
Stav (status) : Typ zavedený do některých implementací jako forma typu boolean (přičemž true a false jsou zastoupeny celočíselnými hodnotami 1 a 0), většinou ovšem jen jako synonymum typu int, tedy bez významu pro funkčnost jazyka. V pozdějších verzích implementací se status zpravidla označuje za zastaralý typ, jejž není vhodné používat.
status flag = 1;
Funkce
Běh programu v LPC spočívá zpravidla v reakcích na interaktivní vstupy uživatelů (tj. v případě MUDu hráčů). +more Reakce spočívají ve spouštění funkcí, přiřazených určitým uživatelským akcím, v objektech, s nimiž se uživatel nějakým způsobem dostal do kontaktu (v případě MUDu se jedná například o vstup do místnosti, v níž se objekt nachází). Tyto akcím přiřazené funkce pak mohou spouštět funkce v daném objektu přímo nebo se zpožděním, volat funkce v jiných objektech, žádat ovladač o provedení vestavěných funkcí, případně vytvářet další objekty, které mohou následně vykonávat další funkce. Běh programu se tedy skládá z vykonávání funkcí volaných jednotlivými objekty.
Funkce musí být vždy volána z nějakého objektu. Podle toho, kde je volaná funkce definována, se pak rozlišují tři základní případy:
Lokální funkce objektů (lfun) : Funkce definované v objektech samotných, resp. v jejich prototypech (klony mají k dispozici tytéž funkce jako jejich prototyp), napsané v LPC. +more Volá-li se lokální funkce v jiném objektu, volá se pomocí vestavěné funkce call_other, resp. jejího zkráceného zápisu -> (call_other(obj,nejakafunkce,par1,par2);, resp. obj->nejakafunkce(par1,par2)). Volá-li se lokální funkce v témže objektu, volá se buď prostým jménem funkce a parametry (nejakafunkce(par1,par2);), nebo pomocí call_other, přičemž na objekt samotný se odkazuje vestavěnou funkcí this_object (call_other(this_object,nejakafunkce,par1,par2);, resp. this_object->nejakafunkce(par1,par2)). Je-li v objektu překryta lokální funkce zděděného objektu, je možno volat původní zděděnou funkci pomocí operátoru :: (tedy ::prekrytafunkce, resp. v případě více děděných objektů pro jednoznačnost zdedenyobjekt::prekrytafunkce).
Vestavěné funkce (efun, kfun) : Vestavěné funkce (ve většině implementací označované zkratkou efun z „externí funkce“, v implementaci DGD kfun z „kernelová funkce“) jsou definovány přímo v ovladači, tedy napsány v jazyce C a zakompikovány do ovladače, který je dává k dispozici všem objektům. Jako takové jsou volatelné odkudkoli prostým jménem funkce a parametry (nejakafunkce(par1,par2);), případně, pokud byla nějaká vestavěná funkce překryta lokální nebo druhotně vestavěnou funkcí, pomocí operátoru efun:: (tedy efun::nejakafunkce(par1,par2);).
Druhotně vestavěné funkce (simul_efun, sefun, auto) : Druhotně vestavěné funkce (ve většině implementací označované jako simul_efun nebo sefun ze „simulované externí funkce“, v implementaci DGD označované jako auto z „automaticky děděné funkce“) jsou naprogramovány v LPC v rámci programové knihovny určité implementace nebo konkrétního MUDu a umístěny do speciálního objektu, jehož funkce ovladač dává k dispozici všem objektům stejně jako funkce vestavěné, resp. (v případě DGD) jejž ovladač považuje za automaticky děděný do všech dalších objektů. +more Tyto funkce se pak volají stejně jako vestavěné funkce prostým jménem funkce a parametry (nejakafunkce(par1,par2);).
Typové modifikátory
void : Používá se jako zástupce typové deklarace v případě funkcí, které nevracejí žádnou hodnotu. Je k dispozici ve všech implementacích.
varargs : Modifikátor pro deklarace funkcí, poskytovaný většinou implementací. V případě použití modifikátoru se počet parametrů funkce bere jako fakultativní, tedy parametry zvenčí předané funkci se doplní nulami do počtu celkem deklarovaných parametrů. +more V některých implementacích je rovněž možno použít varargs u deklarace posledního parametru; pak je možno funkci předat více parametrů, než bylo deklarováno, a poslední parametr uvnitř funkce vystupuje jako pole, které všechny tyto parametry obsahuje.
private : Modifikátor pro deklarace funkcí a globálních (tedy v celém objektu platných) proměnných, specifikující, že funkce nebo proměnná není dostupná odjinud, než z objektu, v němž je deklarována, tedy ani v objektech, které tento objekt dědí. Je k dispozici ve většině implementací.
protected : Modifikátor pro deklarace funkcí, dostupný v mnoha implementacích. Deklaruje, že funkci je možno volat jen ze samotného objektu a z objektů, které tento objekt dědí, a to jen interním voláním lokální funkce, nikoli však pomocí call_other či jinými způsoby.
static : Modifikátor dostupný ve většině implementací, který v případě funkcí deklaruje, že funkci není možno volat z jiných objektů. Volání ze samotného objektu i z objektů, které tento objekt dědí, je však možné jak interním voláním lokální funkce, tak voláním pomocí call_other, tak voláním jinými způsoby (zpožděný procese spuštěný call_out, čekání na vstup pomocí input_to apod. +more). V případě proměnných, pokud je povolen, má stejný význam jako nosave.
nosave : Modifikátor pro deklarace proměnných a dědičnosti, dostupný v mnoha implementacích. Deklaruje, že proměnná se nemá ukládat při ukládání stavu objektu (serializaci).
nomask : Modifikátor pro deklarace funkcí, dostupný ve většině implementací. Deklaruje, že funkci není možno ani překrýt v objektech, které dědí tento objekt, ani zastínit jiným objektem.
public : Modifikátor dostupný ve většině implementací. V případě funkcí i proměnných deklaruje, že funkci či proměnnou není možno v objektu, který dědí tento objekt, předeklarovat jako private, static nebo protected, resp. +more že taková deklarace nebude účinná a funkce či proměnná bude nadále plně přístupná.
virtual : Modifikátor pro deklarace dědičnosti, dostupný v mnoha implementacích. Deklaruje, že proměnné děděného objektu se mají dědit pouze v případě, že nejsou již zděděny jinou větví dědičnosti.
deprecated : Modifikátor dostupný v LDMudu od verze 3. 5, deklarující, že funkce nebo proměnná je zastaralá a neměla by být nadále užívána. +more Jakékoli užití dotyčné funkce nebo proměnné pak vydá varování.
Předávání parametrů
Parametry jednoduchých datových typů (celé či desetinné číslo, řetězec, stav) se v běžném případě předávají hodnotou, ale je možné též předávání odkazem (které se nestanovuje už v deklaraci, nýbrž operátorem & až při volání funkce). Strukturované typy (objekt, pole, asociativní pole, klauzura, záznam) se předávají vždy odkazem. +more Díky tomu je zacházení s těmito typy rychlejší a úspornější, ale nese s sebou bezpečnostní riziko, které je nutno programátorsky ošetřit: Aby takto nebyla volanou funkcí změněna hodnota proměnné, kterou volaná funkce měnit nesmí, je potřeba v těchto případech nejprve vytvořit kopii hodnoty v jiné proměnné, a tu teprve předat volané funkci.
Řídicí struktury
složený příkaz : Příkazy se vykonávají postupně v pořadí, v jakém jsou uvedeny v programu. Jednoduchý příkaz je zakončen středníkem. +more Složený příkaz vzniká uzavřením několika příkazů do složených závorek. Výsledná konstrukce se potom vůči dalším řídicím strukturám chová jako jediný příkaz, jehož vykonání znamená postupné vykonání všech obsažených příkazů. Složené příkazy je možno opět skládat.
if/else : Základní řídicí struktura umožňující podmíněné vykonávání příkazu a větvení algoritmu. V případě splnění podmínky, tedy pokud má výraz uvedený jako podmínka po vyčíslení nenulovou hodnotu, se provede následující příkaz. +more V případě nesplnění podmínky se provede příkaz následující po bezprostředně následujícím else, nebo žádný příkaz, pokud else nenásleduje.
if (environment->query_prsi) { do_command("obuj si holínky"); do_command("rozevři deštník"); } else do_command("dej deštník do batohu");
switch : Řídicí struktura umožňující vícenásobné větvení. Výraz uvedený jako podmínka se vyčíslí, a pak se uvnitř následujícího složeného příkazu porovnává s hodnotami uvedenými v jednotlivých úsecích case. +more Pokud se hodnota najde, pokračuje se ve vykonávání po hodnotě následujících příkazů. Pokud se hodnota nenajde, pokračuje se za klíčovým slovem default, pokud se toto ve stávajícím složeném příkazu vyskytuje, nebo za stávajícím příkazem, pokud se v něm default nevyskytuje.
switch (player->query_invis) { case V_VIS: write("Je tu nějaký hráč. \n"); break; case V_NOLIST: write("Po chvilce rozhlížení si všimneš, že je tu nějaký hráč. +more\n"); break; case V_HIDDEN: write("Máš pocit, že je tu někdo schovaný. \n"); break; default: write("Máš pocit, že tu někdo je, ale nikoho nevidíš. \n"); }.
for : Základní podoba cyklu v LPC. Jako řídicí parametry se uvádějí tři výrazy (z nichž první může být též seznamem výrazů oddělených čárkami) oddělené středníky. +more Při startu cyklu se vyčíslí první výraz (respektive seznam výrazů), nejčastěji přiřazení výchozí hodnoty řídicí proměnné cyklu. Pak se vyčíslí druhý výraz, a pokud je nenulový, provede se následující příkaz a vyčíslí se třetí řídicí výraz. V opakovaném vyčislování druhého výrazu, provádění následujícího příkazu a vyčislování třetího výrazu se pokračuje, dokud druhý výraz nenabude nulové hodnoty.
for (i=0; iremove; write("Další předmět se rozpadl!\n"); }
foreach : Provede následující příkaz postupně pro všechny prvky předaného pole nebo asociativního pole. Hodnota prvku pole nebo asociativního pole se vždy nejprve přiřadí uvedené řídicí proměnné, takže je možno s prvkem pracovat v rámci příkazu.
foreach (object ob:predmety) { ob->remove; write("Další předmět se rozpadl!\n"); }
while : Cyklus s kontrolou podmínky na počátku. Uvedený výraz se vyčíslí, a pokud je jeho hodnota nenulová, provede se následující příkaz. +more To se opakuje tak dlouho, než výraz dosáhne nulové hodnoty.
while (environment(npc)->is_dangerous) { write("Tady není bezpečno!\n"); npc->random_move; }
do/while : Cyklus s kontrolou podmínky na konci. Provede se daný příkaz, a poté se vyčíslí výraz. Dokud je výsledná hodnota výrazu nenulová, postup se opakuje.
do { npc->random_move; do_command("řekni Kde jen mohou ty housle být?\n"); } while (!present("housle",environment(npc))) do_command("řekni Aha, tady!\n");
break : Vyhrazený příkaz break, je-li použit v cyklu, způsobí okamžité ukončení nejbližšího cyklu a pokračování prvním příkazem za cyklem. Je-li použit v konstrukci switch, pak způsobí okamžité pokračování prvním příkazem za touto konstrukcí (viz příklad uvedený výše u konstrukce switch).
continue : Vyhrazený příkaz continue, je-li použit v cyklu, způsobí okamžité ukončení současného průběhu nejbližšího cyklu a zahájení průběhu příštího (u cyklů for a while začínajícího testem podmínky).
return : Vyhrazený příkaz return ukončí vykonávání právě prováděné funkce, a je-li mu předán výraz, pak jej vyčíslí a výslednou hodnotu vrátí jako návratovou hodnotu funkce.
string look_krovi { if (mec && present(mec,this_object) && mec->query_invis) return wrap("Husté křoví... a v něm leží meč!"); return wrap("Naprosto nezajímavé křoví."); }
Odkazy
Reference
Literatura
Související články
Externí odkazy
(založen na LPC) * * * * [url=http://lpmuds.net/]Fórum uživatelů LPC a LPMudu[/url] * [url=http://amylaar.pages.de/doc/]Dokumentace k LPC a implementacím Amylaar LPMud a LDMud[/url]