Obsah
1. Metalua – užitečná rozšíření sémantiky jazyka Lua
2. Nástroje „gg“ a „mlp“
3. gg – Grammar generator
4. mlp – Metalua parser
5. Demonstrační příklad – rozšíření jazyka o nepodmíněné skoky (goto)
6. Přidání nových operátorů do jazyka Lua
7. Přidání podpory pro zápis konstant do zdrojového kódu
8. Interpolace řetězců
9. Odkazy na Internetu
1. Metalua – užitečná rozšíření sémantiky jazyka Lua
V předchozí části tohoto seriálu jsme se seznámili s projektem Metalua, v jehož rámci došlo k poměrně rozsáhlému a významnému rozšíření původního překladače jazyka Lua tak, aby bylo možné s využitím dvojice takzvaných metaoperátorů měnit syntaktický strom vytvářený překladačem (přesněji jeho první částí – front-endem) ještě předtím, než dojde ke generování výsledného bajtkódu. To znamená, že je programátorovi umožněno, podobně jako například v programovacím jazyce Lisp, aby se již v době překladu měnila sémantika samotného jazyka – není například problém vytvořit nové operátory, přidat do programovacího jazyka další typy smyček či rozhodovacích příkazů atd. Na následujícím obrázku je naznačeno, jakým způsobem probíhá překlad zdrojového textu programu do jemu odpovídajícího bajtkódu:
______ ________ +-----------+ / \ +---+ / \ +--------+ |SOURCE FILE|-->< Parser >-->|AST|-->< Compiler >-->|BYTECODE| +-----------+ \______/ +---+ \________/ +--------+
Celý překlad se ve své podstatě dá popsat dvojí transformací – nejprve je zdrojový kód zpracován parserem, který v textu rozpoznává jednotlivé syntaktické kategorie (číslo, textový řetězec, část výrazu, operátor, klíčové slovo) a tvoří z nich abstraktní syntaktický strom (AST). Z tohoto stromu následně překladač (compiler) generuje bajtkód, jenž je buď uložen do externího souboru nebo ponechán v operační paměti s možností jeho následné interpretace. Proces překladu je většinou před programátorem skrytý, ovšem při použití systému Metalua lze celý překlad ovlivnit řízenou modifikací AST.
2. Nástroje „gg“ a „mlp“
Autoři systému Metalua zavedli pro nepřímou manipulaci s AST 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. Manipulace se syntaktickým stromem však při použití těchto operátorů probíhá na poměrně nízké úrovni, proto je jejich přímé použití při netriviálních úpravách syntaxe jazyka poměrně pracné. Z tohoto důvodu je k systému Metalua dodáváno i několik pomocných nástrojů (přesněji rozhraní k těmto nástrojům), které práci při metaprogramování ulehčují. Mezi dva nejdůležitější nástroje, jenž se ve skutečnosti podobají knihovnám používaným při programování (také proto se nazývají metaknihovny), patří nástroj nazvaný gg (grammar generator) a mlp (Metalua parser).
3. gg – Grammar generator
Metaknihovna nazvaná jednoduše gg neboli grammar generator, slouží k vytváření popř. změně generátorů, pomocí nichž je možné ovlivnit chování parserů (funkci parseru jsme si vysvětlili v první kapitole, kde je uveden i ilustrační obrázek postupné transformace zdrojového textu přes AST až k výslednému bajtkódu). Možná se nyní ptáte, proč se zde mluví o více parserech a ne jednom parseru. Je to z toho důvodu, že systém Metalua umožňuje vytvořit větší množství parserů, přičemž každý parser je určen pro zpracování jiné části zdrojového textu programu. Toto modulární uspořádání je výhodné, především tehdy, pokud je zapotřebí ovlivnit chování pouze jednoho parseru.
Parsery programovacího jazyka Lua pracují se čtyřmi třídami objektů – výrazy složenými ze základních elementů pospojovaných pomocí prefixových, infixových i postfixových operátorů, seznamy elementů stejného typu (seznam parametrů funkce, konstruktor pole atd.), sekvencemi příkazů a sekvencemi řízenými pomocí klíčových slov (jejich množinu je možné měnit v průběhu překladu zdrojového textu). V demonstračních příkladech uvedených v následujících kapitolách se sice přímo s metaknihovnou gg nepracuje, ovšem interně je tato metaknihovna velmi intenzivně používána další metaknihovnou – mlp – popsanou dále.
4. mlp – Metalua parser
Všechny demonstrační příklady, které (každý odlišným způsobem) rozšiřují syntaxi i sémantiku programovacího jazyka Lua, používají funkce dostupné z metaknihovny mlp neboli Metalua parser. Samotný mlp používá interně funkce či typy z gg. Mezi nejdůležitější objekty dostupné z metaknihovny mlp patří:
- mlp.expr – přístup k funkcím parseru, který je používán pro zpracování výrazů. V demonstračních příkladech se bude přistupovat k odvozeným objektům, například mlp.epxr.infix či mlp.expr.prefix.
- mlp.table – parser načítající obsah tabulky (její konstruktor)
- mlp.block – parser načítající sekvenci příkazů
- mlp.stat – parser načítající jediný příkaz. Tento parser bude modifikován hned v následujícím demonstračním příkladu a taktéž i v příkladu, který rozšiřuje jazyk Lua o možnost použití interpolace řetězců.
- mlp.lexer – velmi důležitá část celého parseru – lexer. Lexer rozeznává ve zdrojovém textu jednotlivé syntaktické kategorie (číslo, řetězec, klíčové slovo apod.) a převádí je na interní reprezentaci. Při modifikaci sémantiky jazyka Lua je většinou nutné změnit také jeho sémantiku, například přidat nové klíčové slovo či nový typ operátoru – právě to je práce pro lexer.
5. Demonstrační příklad – rozšíření jazyka o nepodmíněné skoky (goto)
V dnešním prvním demonstračním příkladu, jehož zdrojový kód je vypsán pod tímto odstavcem, je ukázáno, jakým způsobem lze poměrně jednoduše přidat do syntaxe jazyka Lua podporu pro nepodmíněné skoky. Jedná se o (nechvalně) známý příkaz goto, který při svém nadužívání může vést k tvorbě špatně čitelného programu (spaghetti code), ovšem v některých případech vede jeho použití naopak ke zjednodušení programu, například při výskoku ze zanořených programových smyček (mnoho programovacích jazyků navíc ani neumožňuje například skok do smyčky, do jiné funkce či odlišného bloku kódu – viz například Java, kde se goto skrývá pod příkazem break). Níže uvedený kód přidává do syntaxe a sémantiky jazyka dvě nová klíčová slova label a goto. Za slovem label i goto musí být uveden identifikátor odkazu ve formě řetězce, což mj. znamená, že lze použít i měnitelné řetězce. Vzhledem k tomu, že v AST již existují objekty typu Label a Goto, je přidání nových klíčových slov poměrně snadné a především přímočaré:
mlp.lexer:add "label" mlp.stat:add { "label", mlp.string, builder = |a| `Label{a[1]} } mlp.lexer:add "goto" mlp.stat:add { "goto", mlp.string, builder = |a| `Goto{a[1]} }
Použití nových klíčových slov je velmi jednoduché, jak je ostatně patrné i z následujícího úryvku zdrojového kódu:
goto "cil" print("tento příkaz nebude proveden") label "cil"
6. Přidání nových operátorů do jazyka Lua
Výše uvedený příkaz goto může být v některých případech užitečný, protože programovací jazyk Lua neobsahuje, podobně jako například céčko či C++, možnost „strukturovaného“ výskoku ze zanořených bloků kódu, například ze dvou do sebe vnořených programových smyček (právě tehdy se příkaz break nahrazuje příkazem goto). Ovšem mnohem častěji, než na neexistenci nepodmíněného skoku, si programátoři, kteří s jazykem Lua pracují, stěžují na to, že v Lua neexistují některé operátory, na které jsou zvyklí z jiných programovacích jazyků, nebo že se tyto operátory zapisují odlišným způsobem. Řešení tohoto problému je při použití systému Metalua poměrně snadné, jak se ostatně můžeme přesvědčit po přečtení zdrojového kódu uvedeného pod tímto odstavcem. V kódu je ukázáno, jakým způsobem je možné do syntaxe jazyka Lua přidat další operátory, samozřejmě se správnou prioritou. Je přidán relační operátor !=, který odpovídá původnímu operátoru ~=, logická negace !, jenž se v Lua zapisuje pomocí klíčového slova not a konečně dva logické operátory && a ||, které odpovídají původním operátorům and a or:
-- operátor "!=" bude mít stejný význam jako -- zabudovaný operátor "~=" mlp.lexer:add "!=" mlp.expr.infix:add { '!=', prec = 30, builder = |a, _, b| +{-{a} ~= -{b}} } -- k operátoru "not" je přidáno nové synonymum "!" mlp.lexer:add "!" mlp.expr.prefix:add { '!', prec = 80, builder = |_, a| +{not -{a}} } -- k operátoru "and" je přidáno synonymum "&&" mlp.lexer:add "&&" mlp.expr.infix:add { '&&', prec = 20, builder = |a, _, b| +{-{a} and -{b}} } -- k operátoru "or" je přidáno synonymum "||" mlp.lexer:add "||" mlp.expr.infix:add { '||', prec = 10, builder = |a, _, b| +{-{a} or -{b}} }
7. Přidání podpory pro zápis konstant do zdrojového kódu
Dalším syntaktickým i sémantickým prvkem, který vývojáři píšící aplikace v programovacím jazyce Lua mohou v některých případech postrádat, je možnost definice konstant. Z předchozích částí tohoto seriálu již víme, že Lua rozeznává pouze globální a lokální proměnné popř. parametry či proměnné uložené v uzávěru (closure). Konstanty, tj. neměnitelné objekty, nejsou podporovány, podobně jako v mnoha dalších skriptovacích jazycích, ovšem jejich přidání do syntaxe jazyka není se systémem Metalua problematické. V následujícím programu je ukázáno, jak lze přidat podporu pro konstanty, které se od běžných proměnných odlišují způsobem zápisu svého jména – pokud jméno obsahuje pouze velká písmena a podtržítka, je považováno za konstantu, ostatní jména jsou proměnné či funkce (ostatně právě tímto způsobem je v mnoha jazycích doporučováno odlišování konstant). Při pokusu o zápis nové hodnoty do konstanty je již při překladu vypsáno chybové hlášení, podobně jako v dalších programovacích jazycích, které konstanty podporují:
-- lokální proměnná local foo -- lokální konstanta local BAR = 10 -- přiřazení nové hodnoty do proměnné - ok foo = 42 -- výpis hodnoty proměnné - ok print(foo) -- výpis hodnoty konstanty - ok print(BAR) -- přiřazení nové hodnoty do konstanty - chyba BAR = 42 -- vypíše se chybové hlášení: error: writing to constant BAR at line xxx
Zdrojový kód pro systém Metalua má tvar:
local function check(o) if o and o.tag == 'Id' and o[1]:match('^[A-Z_]+$') then error('error: writing to constant ' .. o[1] .. ' at line ' .. o.line, 3) end end local function const_transformer(ast) if not ast then return end if ast.tag == 'Set' then for _,v in ipairs(ast[1]) do check(v) end end end mlp.stat.transformers:add(const_transformer)
8. Interpolace řetězců
Pod možná poněkud záhadným pojmem interpolace řetězců (string interpolation) se skrývá velmi užitečná vlastnost – náhrada jména proměnné v řetězci její hodnotou, obecněji též náhrada výrazu jeho (vypočtenou) hodnotou. Interpolace řetězců je podporována například v programovacích jazycích TCL či Perl, ve kterých se velmi často používá například při formátování výstupu, generování souborů ve formátech HTML či XML a podobně (v TCL je však interpolace řetězců interně poměrně často používána i samotným interpretrem, především při vykonávání vložených bloků kódu). Přidání interpolace řetězců do programovacího jazyka Lua je sice pro tvůrce „makra“ poněkud pracnější, ovšem ostatní programátoři již mohou toto makro bez dalších problémů využívat, například způsobem ukázaným níže (vyhodnocovaná proměnná či výraz musí být uvozena prefixem ${ a uzavřena pravou složenou závorkou }:
local foo = 42 local bar = 6502 print("result is ${foo+bar*2} or something similar")
Následuje výpis kódu, který do programovacího jazyka Lua přidá podporu pro interpolaci řetězců:
local function makeparser(f) return function(...) local res = f(...) if res and res.tag == 'String' then local s = res[1] local expr -- note: left-associative. desirable? local function concat(o) if not expr then expr = o else expr = `Op{'concat', expr, o} end end local i = 1 local _ = s:gsub('(.-)$(%b{})()', function(text, var, pos) var = var:sub(2, var:len()-1) if text ~= '' then concat(`String{text}) end local expr2 = mlp.expr:parse(mlp.lexer:newstream(var)) concat( expr2 ) i = pos end ) local rest = s:sub(i) if rest ~= '' then concat(`String{rest}) end expr = expr or `String '' return expr end return res end end
9. 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/…taLuaRecipes - The art of metaprogramming, Part 1: Introduction to metaprogramming
http://www.ibm.com/…taprog1.html - Template metaprogramming
http://en.wikipedia.org/…aprogramming - Metaprogramming
http://en.wikipedia.org/…aprogramming - 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/…rticle.php/6 - LuaForge:
http://luaforge.net/ - LuaForge project tree:
http://luaforge.net/…ove_list.php