Hlavní navigace

Metalua: užitečná rozšíření jazyka Lua

11. 8. 2009
Doba čtení: 9 minut

Sdílet

V dnešní části seriálu o programovacím jazyku Lua bude dokončen popis projektu Metalua, který umožňuje metaprogramování, tj. vývojářem řízenou modifikaci programového kódu generovaného překladačem. Popíšeme si především dvojici nástrojů „gg“ (grammar generator) a „mlp“ (Metalua parser) i některá užitečná rozšíření jazyka Lua.

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 labelgoto 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 }:

CS24_early

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

  1. Lambda the Ultimate: Metalua
    http://lambda-the-ultimate.org/node/2105
  2. Metalua: Static meta-programming for Lua
    http://metalua.luaforge.net/
  3. Meta Lua (lua-users wiki)
    http://lua-users.org/wiki/MetaLua
  4. Meta Lua Recipes
    http://lua-users.org/…taLuaRecipes
  5. The art of metaprogramming, Part 1: Introduction to metaprogramming
    http://www.ibm.com/…taprog1.html
  6. Template metaprogramming
    http://en.wikipedia.org/…aprogramming
  7. Metaprogramming
    http://en.wikipedia.org/…aprogramming
  8. Lua (programming language)
    http://en.wikipedia.org/wiki/Lua_(programming_lan­guage)
  9. Moose (Perl)
    http://www.iinteractive.com/moose/
  10. Joose (framework)
    http://code.google.com/p/joose-js/
  11. Code Generation vs. Metaprogramming
    http://qcodo.com/…rticle.php/6
  12. LuaForge:
    http://luaforge.net/
  13. LuaForge project tree:
    http://luaforge.net/…ove_list.php

Byl pro vás článek přínosný?

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.