jakubův notes – programování a vejšplechty

Píšu o:

Představení at

Představení at.

at – rozšiřujeme syntaxi

Posledním příkladem praktického využití at je rozšíření syntaxe jazyka PHP. Nemohu si pomoci, ale velice se mi líbí sinatra.rb. Jen se podívejte na tu jednoduchost a eleganci (která je převážně zajištěna jazykem Ruby), s jakou lze psát aplikace. S čistým PHP tohoto nikdy nedocílíte. at může pomoci – rozšíříme si jím syntax do nějaké takovéhle podoby:

@get / {
    // ... show something ...
    return "hello, world!\n";
}

@post / {
    // ... create something ...
    return "ok, created\n";
}

@put / {
    // ... update something ...
    return "ok, updated\n";
}

@delete / {
    // ... annihilate something ...
    return "ok, it's gone\n";
}

Hezké, není-liž pravda? Teď zase jak na to. Prolog:

<?php
require 'at.php';

// zpracování HTTP požadavků
$methods = array(
    'GET'       => array(),
    'POST'      => array(),
    'PUT'       => array(),
    'DELETE'    => array()
);

1:N

Samozřejmě budeme potřebovat at. Pak si připravíme pole, ve kterém budou callbacky na zpracování jednotlivých HTTP metod. Ty musíme nějak naplnit. Budou k tomu sloužit makra @get ... { ... } apod. Teď k nim ještě nějaký hezký callback. Jelikož má makro vždy název typu HTTP požadavku, můžeme využít toho, že volané funkce dostávají jako poslední argument název makra. Proto si připravíme pouze jednu funkci na přidávání:

$add = function ($regexp, $block, $method /* a.k.a. $name */) use (&$methods) {
    $code = '';
    foreach ($block as $_) $code .= $_;
    $methods[strtoupper($method)]['%' . $regexp . '%'] = create_function('', $code);
};

A naň vlastně uděláme „alias“ několika maker:

$_ = at()
    ->fn('get',     $add)
    ->fn('post',    $add)
    ->fn('put',     $add)
    ->fn('delete',  $add);

Když se vrátíme zpět ke callbacku, je snad jasné, co dělá. První argument se namapuje na první parametr makra (používáme výchozí fnname callback, takže bacha na čárky!), dále dostaneme blok a název makra, což je vlastně název HTTP metody. create_function() vytvoří funkci, která se při zpracovávání HTTP požadavku bude volat. Celé by to šlo také udělat přes notfound, podobně jako konfigurace minule, ale chtěl jsem ukázat využití posledního argumentu callbacku.

Pak si naplníme pole s callbacky spuštěním at:

$_->run(substr(file_get_contents(__FILE__), __COMPILER_HALT_OFFSET__));

Tentokráte to však není všechno, protože požadavek ještě chceme nějak zpracovat:

foreach ($methods[$_SERVER['REQUEST_METHOD']] as $regexp => $code) {
    if (preg_match($regexp, $_SERVER['REQUEST_URI'])) {
        echo call_user_func($code);
        return ;
    }
}

Velice jednoduché. Vyzkoušíme, jestli regulární výraz odpovídá požadavku. A pokud ano, tak callback spustíme.

Rozšířená syntaxe

Nakonec ještě zbytek ze souboru (zase je tu totiž ten __halt_compiler()):

__halt_compiler();

@get / {
    // ... show something ...
    return "hello, world!\n";
}

@post / {
    // ... create something ...
    return "ok, created\n";
}

@put / {
    // ... update something ...
    return "ok, updated\n";
}

@delete / {
    // ... annihilate something ...
    return "ok, it's gone\n";
}

Pokud váš editor nezná __halt_compiler() a nezastaví na něm zvýrazňování syntaxe, máte zvýrazněnou syntaxi v makrech grátis.

Závěr

Není to sinatra.rb, ještě tomu hodně chybí. Ale jak je vidět, rozšířit PHP syntaxi jde :-)

Tímto posledním příkladem končí představování at. Všechny příklady naleznete v repozitáři. Je to open-source, takže jakékoli připomínky, hlášení chyb, používání a jiné věci jsou vítány. Když už nic, tak doufám, že se vám alespoň líbí __halt_compiler() a/nebo closures. Snad se PHP 5.3 stabilizuje brzy a rozšíří se na hostinzích, aby se daly closures využívat. __halt_compiler() je ale přítomný ve všech verzích PHP 5, takže se dá krásně používat již dnes.

vydáno 19. 7. 2009, 10:00:00

žádný komentář

Zařazeno mezi:

Patří do skupiny:

Představení at

at – konfigurujeme

Pokračování představování at, tentokrát praktická ukázka toho, jak se dá takový metajazyk použít ke konfiguraci. Jak může vypadat konfigurace pomocí at?

Konfigurace

@localhost {
    @debug          { true      }
    @loglevel       { 50        }
    @db {
        @host       { localhost }
        @username   { jakub     }
        @password   { xxx       }
        @database   { test      }
    }
}

Nám jde o to z toho získat nějaké hezké pole. Opět na začátek takový menší prolog:

<?php
require 'at.php';

// zde bude nakonec načtená konfigurace
$configuration = array();

// vytvoří se nová instance
$at = at();

A teď už již k tomu zábavnějšímu. Jako první si trochu upravíme parser názvu makra a parametrů:

$at->fnname(function ($tokens) {
    return array(trim($tokens[0]['content']), array());
});

fnname()

Metoda fnname() je getter/setter callbacku na naformátování názvu makra a jeho parametrů. Callback je opět realizován pomocí closure. Teď již k obsahu. Jako jediný argument dostane callback pole tokenů. Každý token je pole, které má na indexu type typ tokenu (který nabývá hodnot konstant: at::TEXT – textový token –, at::AT – znak @ –, at::LBRACE – znak { – a at::RBRACE – znak }), přičemž v názvu makra, resp. v textu mezi zavináčem a levou složenou závorkou, mohou být pouze textové tokeny a další zavináče.

Na indexu content každého tokenu se skrývá jeho textový obsah vyparsovaný ze vstupu. Jelikož naše konfigurace nebude nijak chytrá, vezmou se jednoduše všechny znaky mezi zavináčem a levou složenou závorkou (tj. všechny textové a zavinářové tokeny) a jejich obsah se spojí do jednoho názvu. Při pohledu na ukázku, jak bude konfigurace vypadat, je jasné, že callback dostane vždy pole s jedním textovým tokenem. Ale kdyby bylo např. makro @foo @bar @baz { ... }, dostaneme pětiprvkové pole.

fnname callback musí vracet dvouprvkové pole, kdy na prvním indexu je výsledný název makra a na druhém pole argumentů.

notfound()

Jelikož u konfigurace nemusíme vědět, jaké všechny názvy maker, resp. názvy konfiguračních hodnot, se tam mohou objevit, je nejjednodušší si to obstarat pomocí notfound callbacku:

$at->notfound(function ($block, $name) use ($at, &$configuration) {
    if (count($block) === 1) {
        $value = trim($block[0]);
        if (strcasecmp($value, 'true') === 0) $value = TRUE;
        else if (strcasecmp($value, 'false') === 0) $value = FALSE;
        else if (((string) $_ = intval($value)) === $value) $value = $_;
        $configuration[$name] = $value;
        return ;
    }

    $up = $configuration;
    $configuration = array();

    $at->run($block);

    $up[$name] = $configuration;
    $configuration = $up;
});

notfound callback je volán v případě, pokud není název makra registrován pomocí fn(). Argumenty dostává stejné, které by dostala funkce, kdyby byla nalezena. My jsme si pomocí předefinování fnname zajistili, že seznam parametrů makra bude vždy prázdný, takže jako první argument bude notfound callback přijímat blok a jako druhý (zároveň poslední) jméno makra. Pokud tápete, co tam dělá to use ( ... ), pak vězte, že takhle se PHP říká, že tahle closure bude používat dané proměnné z nadřazeného kontextu. My potřebujeme mít k dispozici instanci at a odkaz na konfiguraci.

Nejdříve ve funkci zkusíme, jestli se jedná o skalární hodnotu, nebo o seznam hodnot. Pokud o hodnotu, tak ji prostě přidáme do současné konfigurace. Je tam ještě převod na nativní booleovskou hodnotu apod., ale to není zas tak důležité. Podstatné je, že argumentem bloku je předáván podstrom AST bloku daného makra. Tento strom je neinterpretovaný (říká se tomu lazy evaluation) a je to docela důležité, protože to umožňuje implementovat různé věci. Třebas tady zanořování.

Pokud se tedy jedná o seznam hodnot, zanoříme konfiguraci, projedeme předaný blok (tady je využito, že run() přijímá i naparsovaný strom), čímž se hodnoty přidávají do zanořené konfigurace atd. atp. Prostě rekurze :-)

Poslední drobek

Nakonec už jen pustíme at na konfiguraci (zase je využito __halt_compiler()):

// a jede se
$at->run(substr(file_get_contents(__FILE__), __COMPILER_HALT_OFFSET__));

// kouknout na configuraci
//var_dump($configuration);

__halt_compiler();

@localhost {
    @debug          { true      }
    @loglevel       { 50        }
    @db {
        @host       { localhost }
        @username   { jakub     }
        @password   { xxx       }
        @database   { test      }
    }
}

Když si jednotlivé drobečky spojíte dohromady, máte ukázku dalších možností at a praktický příklad, jak se dá vyřešit např. konfigurace. Příště o tom, jak může at rozšiřovat syntaxi jazyka.

vydáno 18. 7. 2009, 10:00:00

žádný komentář

Zařazeno mezi:

Patří do skupiny:

Představení at

at – text-processing metalanguage

Jak nadpis praví, at je metajazyk na zpracování textu. Co to vlastně znamená? at přijme na vstupu nějaký text, v něm najde volání maker, makra se rozvinou a na výstupu je vrácen text s rozvinutými makry. Jednoduché. A teď praktické využití.

Nejdříve je potřeba získat zdrojový kód. at je knihovna (dobře, jedna třída, ale já jsem prostě minimalista) pro PHP a má repozitář na githubu. Buď si můžete naklonovat Gití repozitář:

$ git clone git://github.com/jakubkulhan/at.git

Nebo si stáhnout zabalené zdrojáky – buď jako tarball, či v ZIPu.

Šablonovací systém

Docela intuitivní způsob využití, když se jedná o knihovnu na zpracování textu. Příklad mluví za vše:

<?php
require 'at.php'; // potřebujeme knihovničku

// nadefinujeme si nějaké proměnné
${'@foo'} = "hello, world!";
${'@bar'} = "that's at :-)";

// a už jedem
echo at()
    ->fn('', function ($block) {
        $var = trim($block[0]);
        if (!isset($GLOBALS['@' . $var])) return  '';
        return (string) $GLOBALS['@' . $var];
    })
    ->run(substr(file_get_contents(__FILE__), __COMPILER_HALT_OFFSET__));

// šablona
__halt_compiler();

@{foo}
@{bar}

Postupně. Nejdříve se vloží knihovna s at, to je jasné. Poté si nadefinujeme nějaké proměnné (v příkladě jsou to globální proměnné (i když s prefixem, který zabraňuje, abychom si je jen tak s něčím spletli), ale můžeme si klidně celé šablonování zapouzdřit do třídy). A teď už přichází na řadu at. Funkce at() je zkratka pro new at(), abychom nemuseli psát:

$_ = new at;
echo $_->...

fn() a definování callbacků maker

Metoda fn() je setter/getter maker. V tomto příkladě konkrétně ten setter, který spojuje název makra (první argument) s callbackem (druhý). Tedy je využito closures z PHP 5.3 (pro nižší verze můžete zkusit použit create_function()). Funkce se podívá, jestli existuje nějaká globální proměnná s požadovaným názvem, a když ano, tak její obsah vrátí.

run() aneb zpracování

run() má jeden argument, který může být:

  1. řetězcem; v tom případě je argument naparsován (pomocí at::parse()) a vytvořen z něj AST

  2. polem; a tudíž je považován za již naparsovaný AST

V příkladu je využito __halt_compiler(), kterýžto jazykový konstrukt zastaví zpracovávání souboru, takže můžeme mít kód i šablonu v jednom souboru, a run() je předána právě ta šablona, která se nachází za __halt_compiler().

Makra

Je vidět, že šablona je docela jednoduchá, a tak lze lépe vysvětlit, co je vlastně co. Je patrné, že ona makra budou @{foo} a @{bar}. Takové volání makra se skládá z několika částí. Každé volání uvozuje znak @ (česky zavináč, anglicky at (odtud pochází i název knihovny)). Po něm následuje název makra se seznamem parametrů – řetězec mezi zavináčem a levou složenou závorkou ({). U těchto maker je prázdný, a proto i při volání metody fn() byl jako název makra předáván prázdný řetězec. Nakonec je tu obsah složených závorek ({ ... }), který je v terminologii at nazýván blok a v tomto příkladě je obsahem bloku název proměnné, kterou chceme vypsat.

Když se vrátíme ke spojování prázdnojmenného makra s funkcí (fn('', ...)) a podíváme na dotyčnou funkci, je vidět, že blok přebírá jako první argument. To nemusí být vždycky pravda. Jde o to, že tyto makra nemají žádné parametry. Obecně je totiž callback volán s těmito argumenty: param0, param1, ..., paramN, block, name. block je AST bloku a name název, s jakým bylo makro zavoláno (můžete tedy přiřadit jeden callback pod víc názvů maker a podle názvu se rozhodnout, co se má udělat).

Odvozování názvu makra

O rozhodování, co je název makra a co jednotlivé parametry, se stará callback nastavovaný metodou fnname(), jeho defaultní implementace je at::_fnname_(). Ta jako název makra bere všechny znaky až do prvního bílého (mezery, tabulátoru atp.) a argumenty jsou oddělovány čárkou – tzn. že makro @foo bar, baz { ... } by bylo nahrazeno voláním callbacku, který je zaregistrován pod názvem foo s argumenty: "bar", "baz", array( ... ), "foo".

To be continued…

Tohle byla jen taková lehká ukázka, jak využít at pro šablonovací systém. Ještě by bylo samozřejmě potřeba doplnit možnosti větvení a iterování. Ale to již můžete udělat sami. A nejlépe se o to poté podělte.

Další způsoby praktického využití a větší ponoření se do magie at přijde v následujícím článku.

vydáno 17. 7. 2009, 10:00:00

žádný komentář

Zařazeno mezi:

Patří do skupiny:

Představení at