Makro procesor GNU m4 (7)

5. 11. 2001
Doba čtení: 5 minut

Sdílet

Tato kapitola se zabývá vestavěnými makry, která umožňují různé manipulace s řetězci -- zjištěním délky řetězce počínaje a nahrazováním pomocí regulárních výrazů konče.

Makro procesor m4 poskytuje podporu pro základní manipulaci s řetězci. Všechna makra kromě příkazu format jsou rozpoznávána pouze s argumenty.

Zjištění délky řetězce

len(řetězec)

expanduje na délku předaného řetězce.

   len
=> len
   len()
=> 0
   len(`abcdef')
=> 6

Vyhledání podřetězce

Pozici podřetězce v řetězci vrací makro

index(řetězec, podřetězec)

První znak řetězce má index 0. Jestliže podřetězec není v řetězci nalezen, makro index expanduje na –1.

   index(`automobilismus', `mobil')
=> 4
   index(`Příležitostné makrování', `x')
=> -1

Získání podřetězce

Vrácení podřetězce daného řetězce dosáhnete pomocí makra substr:

substr(řetězec, start, délka)

substr expanduje na podřetězec daného řetězce, který bude začínat od indexu start a bude délka znaků dlouhý. Pokud zadaná délka přesáhne délku řetězce nebo ji vůbec nezadáte, vybere se podřetězec až do konce zdroje.

   substr(`bim bam bum', 4)
=> bam, bum
   substr(`bim bam bum', 4, 3)
=> bam

Makro pro přistupování k řetězci po znacích:

   define(`znak', `substr(`$1', `$2', 1)')
=>
   znak(`ahoj',0) znak(`ahoj',1)
=> a h

Nahrazování po znacích

Makro translit má podobnou funkčnost jako Unixová utilita tr.

translit(řetězec, sada vzorů, sada náhrad)

Jeho úkolem je nahradit všechny výskyty znaků ze sady vzorů odpovídajícími (podle pořadí) znaky ze sady náhrad. Jinými slovy, translit expanduje na řetězec, jehož každý znak, který se nalezne v sadě vzorů, bude nahrazen znakem ze sady náhrad se stejným indexem.

   define(`velka_pismena', `translit(`$1', `a-z', `A-Z')')

=> velka_pismena(`Ahoj Karle!')
   AHOJ KARLE!
=>

V příkladu jsme definovali makro, které převádí znaky svého argumentu na velká písmena. Makro bohužel trpí základní nemocí. Neumí převést znaky s českou diakritikou. Opravení tohoto nedostatku nechávám na pilném čtenáři.

Jestliže je sada vzorů delší než sada náhrad, odpovídající přebývající znaky budou ze zdrojového řetězce odstraněny. Pokud sadu náhrad neuvedete vůbec, budou z řetězce vymazány všechny znaky vyjmenované v sadě vzorů.

   define(`vymaz_samohlasky', `translit(`$1', `aeiouAEIOU')')
=>
   vymaz_samohlasky(`Ahoj Karle!')
=> hj Krl!

Jak jste si již asi všimli, znaky v sadách můžete zapsat pomocí intervalu, tj. například jako a-j, což znamená všechna malá písmena anglické abecedy od a do j. (Nebo 0–9, a to je stejné, jako byste napsali 0123456789.) Potřebujete-li do sady znaků zahrnout i pomlčku („-“), uveďte ji v seznamu na prvním nebo posledním místě. Je dovoleno dokonce psát i obrácené rozsahy. Třeba Z-A znamená všechna velká písmena anglické abecedy zapsaná od konce.

Jako ukázku si definujme jednoduchou dětskou šifru:

   define(`sifruj', `translit(`$1', `a-zA-Z0-9', `z-aZ-A9-0')')
=>
   sifruj(`Ahoj Karle!')
=> Zslq Pziov!
   sifruj(sifruj(`Ahoj Karle!'))
=> Ahoj Karle!

Formátování

V procesoru m4 naleznete i makro, které je velice podobné funkci printf z programovacího jazyka C.

format(formát, ...)

První argument je řetězec formátu, který může obsahovat speciální sekvence znaků začínající na %, a ty se určitým způsobem nahrazují hodnotami dalších parametrů makra. Makro format umí různě formátovat číselné hodnoty nebo řetězce. Podporuje všechny standardní sekvence printf: %c, %s, %d, %o, %x, %X, %u, %e, %E a %f. Akceptuje i specifikace délek položek, počtu desetinných míst a rozeznává modifikátory +, -, mezeru, 0, #, h a l. Na tomto místě nebudu popisovat, co která sekvence znamená, protože si myslím, že je to zbytečné, neboť funkci printf z céčka jistě většina z vás zná. (Pokud ne, můžete si o ní přečíst v kterékoliv knize o jazyku C nebo rovnou na manuálových stránkách (man 3 printf).)

Příklady:

   define(`makro', `Strč prst skrz krk')
=>
   format(`Řetězec "%s" je %d znaků dlouhý.', makro, len(makro))
=> Řetězec "Strč prst skrz krk" je 18 znaků dlouhý.

   define(`krat', `format(`%2d x %2d = %4d
   ', $1, $2, eval($1 * $2))')
=>
   krat(5,1)krat(5,5)krat(50,10)krat(50,50)
=>  5 x  1 =    5
    5 x  5 =   25
   50 x 10 =  500
   50 x 50 = 2500

Vyhledávání podle regulárního výrazu

Pro vyhledávání a nahrazování řetězců pomocí regulárního výrazu slouží makro

regexp(řetězec, reg-výraz [, náhrada])

Vynecháte-li argument náhrada, regexp expanduje na index prvního znaku podřetězce, který zadanému regulárnímu výrazu vyhoví. Pokud žádnou shodu nenalezne, vrátí –1.

   regexp(`Kapitola 1. - Úvod', `[0-9]+\. -')
=> 9
   regexp(`Ahoj světe!', `[0-9]+\. -')
=> -1

Makro regexp se všemi parametry expanduje na hodnotu třetího argumentu, přičemž všechny podřetězce tvaru \n, kde n je přirozené číslo, nahradí podřetězci, které odpovídají n-tému podvýrazu uzavřenému do závorek. \& se nahrazuje celým vyhovujícím podřetězcem.

   regexp(`bim bam bum',
          `\(\<\w+\) \(\<\w+\) \(\<\w+\)',
          `\3 \2 \1')
=> bum bam bim
   regexp(`Ahoj světe!', `.oj', `*** \& ***')
=> *** hoj ***

Příjemné je, že výsledný text znovu podléhá expanzi procesoru. Může tedy obsahovat volání dalších maker a docílíte tak opravdu divokých funkcí.

O samotných regulárních výrazech se už na Rootu psalo, proto je zde znovu vysvětlovat nebudu.

   define(`makro', `ahoj')
=>
   regexp(`macku', `..', `\&kro')
=> ahoj

Globální nahrazování podle regulárního výrazu

Pod tímto nechutným názvem se skrývá velice užitečné makro, jehož syntaxe je podobná regexpu:

patsubst(řetězec, reg-výraz[, náhrada])

patsubst prohledá zdrojový řetězec a místo každého podřetězce odpovídajícího regulárnímu výrazu vloží danou náhradu. Výsledkem expanze je tedy zdrojový řetězec s podřetězci vyhovujícími regulárnímu výrazu nahrazenými náhradou. To znamená, že části řetězce, které regulárnímu výrazu nevyhoví, jsou do expanze zahrnuty nezměněné. Kdykoliv nějaký podřetězec vyhoví danému regulárnímu výrazu, provede se náhrada a pokračuje se ve vyhledávání dalšího podřetězce za místem výskytu předchozí shody. Žádný podřetězec proto není nahrazován dvakrát. V parametru s náhradou můžete opět používat konstrukce \n a \& se stejným významem jako u makra regexp.

   define(`prostrkej', `patsubst(`$1', `.', `\& ')')
=>
   prostrkej(`Ahoj Karle!')
=> A h o j   K a r l e !

Třetí parametr může být vynechán. V takovém případě se každý regulárnímu výrazu vyhovující podřetězec vymaže.

   define(`vymaz_samohlasky2', `patsubst(`$1', `[aeiouAEIOU]')')
=>
   vymaz_samohlasky2(`Ahoj Karle!')
=> hj Krl!

Na závěr uvedu jeden realisticky vyhlížející příklad z manuálu k procesoru m4. Budeme definovat makro, které změní první písmena každého slova na velká a ostatní na malá:

bitcoin školení listopad 24

   define(`velka_pismena', `translit(`$*', `a-z', `A-Z')')
=>
   define(`mala_pismena', `translit(`$*', `A-Z', `a-z')')
=>
   define(`$kapitalky',
          `regexp(`$1', `^\(\w\)\(\w*\)',
                  `velka_pismena(`\1')`'mala_pismena(`\2')')')
=>
   define(`kapitalky',
          `patsubst(`$1', `\w+', `indir(`$kapitalky', `\&')')')
=>
   kapitalky(`Ahoj KARLE! Jak se vede?')
=> Ahoj Karle! Jak Se Vede?

Aby makro kapitalky fungovalo spolehlivě i pro znaky s českou diakritikou, museli bychom upravit překladové sady maker velka_pismena a mala_pismena a také všechny regulární výrazy. Výraz \w totiž vyhoví jen písmenu anglické abecedy. Zkuste si to za domácí úkol opravit. :-)

Pokračování příště.

Autor článku

Michal Burda vystudoval informatiku a aplikovanou matematiku a nyní pracuje na Ostravské univerzitě jako odborný asistent. Zajímá se o data mining, Javu a Linux.