Mirin webspace

Nejbohatší život má ten, kdo žije s minimem nároků

22. 1. 2008 PHP

Napiš si své texy - část 1

V tomto příspěvku se bude věnovat základním popisu principů konstrukce vlastního značkovacího jazyka pro popis dokumentu. Bude se jednat ve velké míře o překlad popisu parseru pro DokuWiki, který jsem použil pro svůj blog. Často se textu budou objevovat věci, které by se v PHP5 měly řešit jinak, je to tím, že Dokuwiki je php4 kompatibilní a s přepisem do PHP5 jsem někde za polovinou. Na vlastním principu se tím ale nic nemění. Předem musím upozornit, že principy konstrukce překladačů jsem nestudoval, proto se bude z tohoto pohledu jednat spíše o pohled laika.

Komponenty

Předpokládejme, že máme zvolen soubor elementů, které budeme v naše pseudojazyce využívat. Tato volba určitě není jednoduchá, při volbě značek se určitě můžeme dostat později do problémů. Jak jsem zmínil, teoretické zázemí postrádám, nicméně předpokládám, že při použití značek na formátování obsahu budeme pravděpodobně vycházet z nějakého již existujícího značkování (markdown, wikipedia apod.), předpokládám, že stejně postupoval jak autor Dokuwiki, tak třeba dgx při návrhu Texy.

Pro převod dokumentu se značkami na nějaký výstup (třeba HTML) využijeme těchto komponent, které představují ve výsledku třídy.

  • Lexer - prochází náš dokument a na jeho výstupu je sekvence tokenů, které odpovídají naší syntaxi. Pro tokenizaci využijeme nejčastěji regulárních výrazů a jednoduchého stavového automatu.
  • Handler - přijímá tokeny od Lexeru a transformuje je na sekvenci instrukcí 1).
  • Parser - svazuje dohromady Lexer a Handler, zajišťuje vstupní bod do systému pro zdrojový dokument a registraci regulárních výrazů pro Lexer.
  • Renderer - interpretuje instrukce od Handleru tak, že vykresluje konkrétní výstup.

Tím máme zajištěnu nezávislost Rendereru na Lexeru a můžeme napsat Renderer pro jakýkoli výstup (HTML, PDF, LaTex). V praxi to tak jednoduché není, protože do Handleru často zavedeme konstrukce, které se už trochu váží na konkrétní typ výstupu.

Lexer

Slouží pro tokenizaci textu pomocí reg. výrazů a stavového automatu, takže se zachovává informace o kontextu. Lexer používá dvě pomocné třídy.

  • ParallelRegex - pro konstrukci reg. výrazu z několika podvýrazů, používá PCRE reg. výrazy opravdu intenzivně, umožňuje použít lookahead a lookback patterny. Přiznám se, že pro mne jsou některé konstrukce reg. výrazů, které se tam používají dost záhadné.
  • StateStack - jednoduchý stavový automat, který umožňuje Lexeru udržovat informaci o kontextu.

Módy Lexeru a volání Handleru

Módem rozumíme určitý stav Lexeru, označujeme ho textovým labelem. K tomuto stavu se váží regulární výrazy, pokud jim vstupní text odpovídá, bude na Handleru volána metoda stejného jména jako daný stav. Lexer zároveň do Handleru předává informaci o pozici tokenu ve vstupním textu. Instanci Handleru dostává Lexer třeba v konstruktoru a vlastní tokenizace a předávání tokenů do Handleru se provádí metodou parse Lexeru.

Základní Api Lexeru obsahuje metody pro registraci módů addEntryPattern a addExitPattern

/**
 * @param string $pattern regex
 * @param string $mode mód, ve kterém je $pattern smí nacházet,    
 * @param string $newMode vnořený mód na který se přejde, až proběhne match 
 */
public function addEntryPattern($pattern, $mode, $newMode)

Obdobně addExitPattern. Používají se i speciální módy (metoda addSpecialPattern), kde po matchnutí textu okamžitě přecházíme zpět do původního módu. Tímto způsobem se parsují smajlíci, akronymy, linky atd.

Registrace módů Parserem

Pokud bychom registraci módů prováděli pouze přes výše zmíněné metody Lexeru, bylo by to dosti nepřehledné. Takto bychom například registrovali mód link.

 $linkRegexp = "......." //some regexp for hyperlink
 $lexer->addSpecialPattern($linkRegexp,'base','link');
 $lexer->addSpecialPattern($linkRegexp,'footnote','link');
 $lexer->addSpecialPattern($linkRegexp,'table','link');

Každý mód, který může obsahovat linky musí být vyjmenován. Nic moc, proto se používají Mode třídy.

class WikiText_Parser_Mode_Link extends WikiText_Parser_Mode 
{
//=======================
public function connectTo($mode) 
{
 $linkRegexp = "......." //some regexp for hyperlink
 $this->Lexer->addSpecialPattern($linkRegexp,$mode,'link');
}
 
#END CLASS
}

Parser pak před zahájením parsování Lexerem volá metody connectTo() těchto tříd jen pro nadřazené módy, ve kterých se daný mód link dá použít.

Celé použití pro inicializaci vypadá asi následovně

// Create the parser
$Parser = new WikiText_Parser();
 
// Add the Handler
$Parser->Handler = new WikiText_Handler();
 
// Load modes
$Parser->addMode('listblock',new WikiText_Parser_Mode_ListBlock());
$Parser->addMode('preformatted',new WikiText_Parser_Mode_Preformatted()); 
$Parser->addMode('table',new WikiText_Parser_Mode_Table());
 
$formats = array (
    'strong', 'emphasis', 'underline', 'monospace',
    'subscript', 'superscript', 'deleted',
);
foreach ( $formats as $format ) {
    $Parser->addMode($format,new WikiText_Parser_Mode_Formatting($format));
}
 
// Loads the raw wiki document
$doc = file_get_contents('wiki/syntax.txt');
 
//Parser registers patterns with the Lexer, and calls Lexer->parse(), Handler then
//makes list of instructions and returns it
$instructions = $Parser->parse($doc);
 
/*
 Renderer processes instructions
 ...
*/

Pro konkrétní použití doporučuji nahlédnout do zdrojáků Dokuwiku třeba přes tento web interface.

1) konkrétně se jedná poměrně jednoduché vnořené PHP pole

Komentáře (0)

Komentáře jsou uzavřeny.