Obsah
1. Metalua – programovatelné rozšíření sémantiky jazyka Lua
2. Metaprogramování
3. Zjednodušená syntaxe při zápisu anonymních funkcí
4. Funkce zapisované v podobě infixového operátoru
5. Syntaktické stromy – AST
6. Manipulace se syntaktickými stromy
7. Rozšíření syntaxe a sémantiky jazyka Lua
9. Odkazy na Internetu
1. Metalua – programovatelné rozšíření sémantiky jazyka Lua
V předchozích dvou částech tohoto seriálu jsme si popsali jednu z „alternativních“ implementací jazyka Lua – jednalo se o projekt LuaJ, který umožňuje vzájemnou interakci mezi programem napsaným v Javě a Lua skripty. Dnes se budeme zabývat další implementací tohoto jazyka nazvanou příhodně Metalua, která je oproti standardnímu interpretru rozšířena (a to dosti podstatným způsobem) o možnost metaprogramování. V rámci projektu Metalua došlo ke změně vlastního překladače jazyka (překladač se stará o transformaci zdrojového textu programu do bajtkódu), který je rozšířen o některé vlastnosti, které můžeme najít například v lispovských jazycích – podporu maker, metaprogramování a možnost rozšíření syntaxe jazyka o další jazykové konstrukce, například nové operátory, další typy smyček, řídicí struktury atd. Tyto vlastnosti jsou do překladače přidány takovým způsobem, že je možné vlastní překládaný kód modifikovat na základě speciálních konstrukcí zapisovaných přímo do zdrojového kódu programu. Programový kód, přesněji řečeno jemu odpovídající AST – abstract syntax tree, je tedy modifikován už v době překladu (compile time), nikoli až v době běhu (runtime).
2. Metaprogramování
Metaprogramování není pouze doménou Lispu (Scheme) a jazyka Lua – projekty podobné Metalua existují i pro další dynamické programovací jazyky, například projekt Moose pro Perl či Joose pro JavaScript. Jedna z forem metaprogramování je založena na šablonách, které jsou podporovány především v jazyku C++ a dále pak například v programovacích jazycích Curl, D, Eiffel, Haskell či ML. V lispovských jazycích (včetně jazyka Scheme) je manipulace s překládaným (transformovaným) či již interpretovaným kódem velmi jednoduchá, protože zde existuje přímá a jednoznačná ekvivalence mezi kódem a daty (vše je chápáno jako seznam, přesněji sekvence provázaných tečka-dvojic tvořících binární strom, odpovídající výše zmíněnému AST), tudíž lze s kódem manipulovat stejně jako s jinými daty, dokonce i za použití stejných „seznamových“ operací. Tuto velmi mocnou techniku se tvůrci systému Metalua snažili použít i pro jazyk Lua, což se jim v rámci daných možností podařilo, dokonce poměrně elegantním způsobem – přidáním pouhých dvou metaoperátorů do jazyka (a samozřejmě poměrně velkou úpravou vlastního překladače, tato úprava je však před běžným programátorem skryta).
3. Zjednodušená syntaxe při zápisu anonymních funkcí
Prvním (v tomto případě syntaktickým) rozšířením zavedeným v projektu Metalua je možnost zjednodušeného zápisu anonymních funkcí, které se poměrně často používají především v programech zapsaných funkcionálně (tj. tak, aby funkce neměly vedlejší efekty – side effect). Anonymní funkce jsou samozřejmě podporovány i v původní verzi jazyka Lua, ovšem jejich zápis byl zbytečně zdlouhavý – anonymní funkce se používají na jednom jediném místě, proto není vhodné, aby se v jejich definici vyskytovala dlouhá klíčová slova function, return či end. Namísto toho zavádí Metalua alternativní syntaxi založenou na použití znaku | (roura, pipe), který odděluje parametry funkce od jejího těla. Například anonymní funkce, jenž sečte své dva parametry a vrátí výsledek tohoto součtu, by se s použitím standardní syntaxe zapsala následovně:
function(x,y) return x+y end
V Metalua lze kromě výše uvedeného zápisu použít tento tvar:
|x,y| x+y
Připomeňme si, že v jazyce Lua je možné funkce přiřazovat do proměnných, ukládat je do asociativních polí či předávat jako parametry jiným funkcím. Právě v těchto případech je možné s výhodou použít výše uvedenou zjednodušenou syntaxi. Dokonce je možné tímto způsobem vytvořit bezparametrickou anonymní funkci, která například může být použita v čase návrhu programu na místě, které je posléze nahrazeno složitějším kódem:
| | 1234
4. Funkce zapisované v podobě infixového operátoru
Dalším rozšířením původního jazyka Lua, které je podporováno v projektu Metalua, je podpora zápisu funkcí v infixové podobě, tj. jméno funkce v tomto případě leží mezi dvojicí operandů, které jsou funkci předány jako její parametry. V některých případech, například při vytváření programů provádějících různé složitější matematické operace nad různými strukturami (vektory, polynomy, maticemi, kvaterniony atd.), může být infixový způsob volání funkcí jednodušší na pochopení funkce algoritmu, než tradiční způsob prefixový. Dokonce ani nutné do jazyka přidávat podporu pro přetížení operátorů. Funkce akceptující dva parametry může být stále volána „standardním“ prefixovým způsobem nebo způsobem infixovým – v tomto případě se však její název musí zapsat mezi zpětné apostrofy. Mějme například funkci plus vytvořenou jedním z následujících způsobů (poslední uvedený způsob jsme si ukázali v předchozí kapitole – jedná se o vytvoření anonymní funkce s jejím následným přiřazením do proměnné):
function plus(x,y) return x+y end plus=function(x,y) return x+y end plus=|x,y| x+y
Tuto funkci lze zavolat dvěma způsoby:
plus(1,2) 1 `plus` 2
Samozřejmě lze vytvořit i složitější funkce, například pro provádění skalárního součinu dvou vektorů:
function cdot(v1, v2) return v1.x * v2.x + v1.y * v2.y end vektor1={x=10,y=20} vektor2={x=1,y=2} print(cdot(vektor1,vektor2)) print(vektor1 `cdot` vekto2)
5. Syntaktické stromy – AST
Při použití systému Metalua má programátor přístup i k takzvaným syntaktickým stromům – abstract syntax tree, neboli AST. Jedná se o datovou strukturu, do které se pomocí parseru převádí zdrojový kód programu před jeho kompilací do bajtkódu. Pro uživatele většiny programovacích jazyků probíhá tento proces skrytě, ovšem v systému Metalua existují možnosti řízené modifikace AST, převodu části vybraného zdrojového kódu do AST atd. Celý proces překladu vypadá následovně:
______ ________ +-----------+ / \ +---+ / \ +--------+ |SOURCE FILE|-->< Parser >-->|AST|-->< Compiler >-->|BYTECODE| +-----------+ \______/ +---+ \________/ +--------+
V uzlech AST jsou uloženy takzvané tagy, přičemž každý tag je reprezentován svým názvem (například Table či Call) a podstromem, jehož uzly jsou přímo atomické hodnoty (čísla, pravdivostní hodnoty, řetězce) nebo další podstromy. Následují příklady některých uzlů reprezentujících typické části programu (tagy jsou uvozeny zpětnými apostrofy):
Prázdná tabulka vytvořená například pomocí {}: `Table{ } Tabulka se třemi prvky {1, 2, "a"}: `Table{ `Number 1, `Number 2, `String "a" }; Volání funkce se dvěma parametry print(foo, "bar"): `Call{ `Id "print", `Id "foo", `String "bar" } Volání funkce s libovolným množstvím parametrů f(x, ...): `Call{ `Id "f", `Id "x", `Dots } Jednoduchý aritmetický výraz 1+2*3: `Op{ 'add', `Number 1, `Op{ 'mul', `Number 2, `Number 3 } } (v tomto případě je vytvořen podstrom reprezentující druhou část výrazu) Programová smyčka typu while [foo] do [bar1]; [bar2]; ...: `While{ [foo], { [bar1], [bar2], ... } }.
6. Manipulace se syntaktickými stromy
Výše popsaná struktura syntaktických stromů je sice z teoretického hlediska úplná a přesná, ovšem není příliš čitelná. Z tohoto důvodu by manipulace se stromem v uvedené podobě byla nepřehledná, čehož si byli vědomi i autoři systému Metalua. Proto zavedli dvojici symbolů (metaoperátorů) +{ a }, pomocí nichž je možné zapisovat části programového kódu (výrazy, příkazy, bloky příkazů) v jazyku Lua, které se posléze samy převedou na uzly AST. Druhá dvojice symbolů -{ a } naopak slouží k přímému vkládání části AST (získaného prakticky libovolným postupem, typicky právě pomocí symbolů +{}) do generovaného výsledného stromu, který je posléze přeložen do bajtkódu. Naproti tomu metaoperátor -{} může obsahovat kód, který je během překladu vložený do AST a může tak ovlivnit činnost překladače. V následující kapitole je ukázán způsob rozšíření jazyka Lua ternární operátor ?:, ve kterém se oba metaoperátory používají: jeden pro generování nových uzlů do AST ze zdrojového kódu, druhý pro „přenos“ kódu, který představuje všechny tři operandy ternárního operátoru.
Pokud například potřebujeme, aby se na určitém místě vložil do AST podstrom představující jednoduchý matematický výraz 1+2, není nutné psát:
`Op{ 'add', `Number 1, 'Number 2}
Ale postačuje použít metaoperátor +{}:
+{expr: 2+2}
Popř. dokonce pouze:
+{2+2}
7. Rozšíření syntaxe a sémantiky jazyka Lua
Pro rozšíření syntaxe a současně i sémantiky jazyka Lua se kromě manipulace s AST, používají i dva užitečné nástroje – gg (grammar generator) a mlp (Metalua parser). Ve skutečnosti je mlp postavený na gg. Tyto nástroje si popíšeme v navazující části tohoto seriálu, ovšem již dnes si můžeme ukázat způsob rozšíření jazyka Lua o ternární operátor, který důvěrně znají programátoři v C, C++, Javě či JavaScriptu. V první části příkladu je nadefinována funkce b(), která na základě pravdivostní hodnoty parametru x vyhodnotí buď první část programového kódu přenesenou v parametru suffix, nebo část druhou. Důležité je, že se vyhodnotí vždy jen jedna část kódu, to znamená, že případné vedlejší efekty jsou eliminovány (podobným způsobem pracují i speciální formy v Lispu). Následovně je pomocí nástroje mlp rozšířena syntaxe jazyka tak, aby se ternární operátor transformoval právě na volání funkce b a to tak, že kód před otazníkem je přenesen v prvním parametru a kód před čárkou a za čárkou v parametru druhém (ternární operátor tedy ve skutečnosti používá pro oddělení druhého a třetího operandu čárku, to je však dáno tím, že dvojtečka již má v syntaxi jiný význam, který sice lze rozšířit, ale výsledek by byl poněkud delší).
-- přidání ternárního operátoru do sémantiky jazyka -- (tento operátor je použitý v jazycích odvozených od céčka: -- C++, Java, JavaScript atd.) -- Povšimněte si zde použití metaoperátoru +{} a -{} local function b(x, suffix) local v, ontrue, onfalse = mlp.gensym "test", unpack (suffix) return `Stat{ +{ block: local -{v} if -{x} then (-{v}) = -{ontrue} else (-{v}) = -{onfalse or `Nil} end }, v } end -- vlastní rozšíření syntaxe pomocí nástroje mlp - Metalua parser, -- který si popíšeme v následující části tohoto seriálu mlp.expr.suffix:add{ "?", mlp.expr, gg.onkeyword{ ",", mlp.expr }, prec=5, builder=b }
8. Odkazy na Internetu
- Lambda the Ultimate: Metalua
http://lambda-the-ultimate.org/node/2105 - Metalua: Static meta-programming for Lua
http://metalua.luaforge.net/ - Meta Lua (lua-users wiki)
http://lua-users.org/wiki/MetaLua - Meta Lua Recipes
http://lua-users.org/wiki/MetaLuaRecipes - The art of metaprogramming, Part 1: Introduction to metaprogramming
http://www.ibm.com/developerworks/linux/library/l-metaprog1.html - Template metaprogramming
http://en.wikipedia.org/wiki/Template_metaprogramming - Metaprogramming
http://en.wikipedia.org/wiki/Metaprogramming - Lua (programming language)
http://en.wikipedia.org/wiki/Lua_(programming_language) - Moose (Perl)
http://www.iinteractive.com/moose/ - Joose (framework)
http://code.google.com/p/joose-js/ - Code Generation vs. Metaprogramming
http://qcodo.com/documentation/article.php/6 - LuaForge:
http://luaforge.net/ - LuaForge project tree:
http://luaforge.net/softwaremap/trove_list.php