Hlavní navigace

Generování kódu v M4: úvod

Autor: jkubin
Josef Kubín

Makro procesor M4 se používá ke generování libovolně složitého kódu z jednoduchého zdrojového kódu. Úvodní díl seriálu obsahuje jeho historii, základní principy jazyka, příklady použití a nutné předpoklady pro jeho zvládnutí.

Doba čtení: 37 minut

Sdílet

Obsah (English version)

1  Úvod

1.1  Příklady pro čtenáře

2  Historie makro jazyků

3  Základy jazyka M4

3.1  Bezkontextová gramatika

3.2  Automaty

3.3  Výstupní fronty

4  Hlavní použití M4

4.1  Generování kódu

4.2  Preprocesor

5  Předpoklady pro zvládnutí M4

5.1  Základy gramatik

5.2  Základy automatů

5.3  (GNU) make

5.4  Vim

5.5  Talent a čas

6  Odkazy


A  Příklady generování kódu

B  Příklady preprocesoru

C  M4 – příklady

D  Proč používat M4 a proč ne?

1  Úvod 

Čtenáři tohoto seriálu se naučí psát skripty pro strojové generování kódu. Strojově generovaný kód může být libovolně složitý a může obsahovat další vnitřní závislosti. Vzájemně závislé soubory se složitým kódem jsou pro člověka jen těžce udržitelné v konzistentním stavu. Je už nutné použít nějaký mechanismus pro generování kódu. Generování kódu provádí nástroj určený pro transformaci textu – makro procesor.

Seriál je zaměřen na praktické použití univerzálního makro procesoru M4 (dále jen M4) pomocí malých příkladů. Popisuje také teoretický základ všech implementací. Cílem seriálu je seznámit čtenáře s tímto nástrojem a také programovacím jazykem.
Na co se používá, jak se v něm programuje a jaké jsou jeho výhodynevýhody.

Vícejazyčný seriál „Generování kódu v M4“ je generován M4 skripty[1], které (možná) usnadní psaní článků a seriálů pro www.root.cz také jiným autorům.

Úvodní díl popisuje základní principy jazyka na jednoduchých příkladech. Všechny příklady používají přepisovací pravidla bezkontextové gramatiky. Později se naučíme používat výstupní fronty, automaty, asociativní paměti, zásobníky a zásobníkové automaty. Naučíme se také psát testovací automaty pro testování vstupních dat.

1.1  Příklady pro čtenáře 

Příklady pro čtenáře tvoří komplementární část seriálu a budou do jisté míry vytvářeny na základě podnětů v diskuzi pod článkem. Na začátku každého dílu je popsána nějaká část jazyka M4 doplněná sadou příkladů na konci. Každý díl je možné číst v libovolném pořadí.

2  Historie makro jazyků 

Makro jazyky byly vynalezeny v době, kdy dominoval jazyk symbolických adres – JSA. Zdrojový kód JSA velmi často obsahuje shodné sekvence instrukcí odlišující se pouze hodnotami operandů. Shodné sekvence instrukcí je možné seskupit do jednoho slova, nebo-li makro instrukce. Jméno obvykle popisuje účel skryté sekvence instrukcí. Makro instrukce se přeloží makro procesorem na původní sekvenci instrukcí, která se posléze přeloží do spustitelného strojového kódu. Programování v JSA pomocí makro instrukcí je jednodušší, rychlejší a méně náchylné k lidským chybám.

Později byly makro jazyky použity k rozšíření kompilovaných programovacích jazyků, protože umožnily psát zdrojový kód na vyšší úrovni abstrakce než jakou poskytuje samotný programovací jazyk. Rychlost, výkonnost a efektivita složitého programovacího jazyka nižší úrovně je zachována díky makro jazykům. Avšak je důležité dobře rozumět všem vrstvám kódu.

» GPM (General Purpose Macro-generator)

Základní myšlenku přepisování textových řetězců s argumenty, které se přepíší do dalších rekurzivně přepisovatelných řetězců, představil Christopher Strachey ve svém GPM[2] v roce 1965. Další generace makro procesorů M3 a M4 původní GPM v podstatě už jen rozšiřovaly. Základní myšlenka původního návrhu ale zůstala stejná.

» M3

Dennis Ritchie převzal základní myšlenku GPM a napsal vylepšený makro procesor pro generování zdrojového kódu programovacího jazyka C (1972), který sám navrhl. Nový makro procesor napsal pro minipočítač AP-3 – odtud jméno M3. Tento přímý předchůdce současného M4 dokázal výrazně ušetřit těžkou a časově náročnou práci, čímž zaujal vývojáře programující v jiných jazycích (FORTRAN, COBOL, PL/I, …). Vývojáři upravovali M3 pro tyto jazyky čímž ho proměnili na univerzálně použitelný makro procesor M4.

Dennis Ritchie byl také spolutvůrcem operačního systému UNIX a proto:
  • M4 je minimalistický a rychlý, dělá jednu věc a tu dělá dobře
  • výhradně spoléhá na neinteraktivní rozhraní příkazové řádky
  • parametry a závislosti M4 skriptů popisuje Makefile
  • znakem # začíná jednořádkový komentář jako v UNIX-ovém shell-u
  • proměnné $@, $*, $#, $0, $1, $2, … mají podobný význam jako v shell-u
  • oddělovač argumentů je čárka

Makro procesor M3 rozšířil také Jim E. Weythman, autor programové konstrukce, která se používá téměř v každém M4 skriptu:

divert(-1)
…
define(…)
…
divert(0)dnl
…
Klíčové slovo divert() přepíná výstupní fronty. Argument -1 zcela vypne jakýkoliv textový výstup. Argument 0 přepne výstup na stdout (standardní výstup).

» M4

Brian Kernighan makro procesor M3 rozšířil na preprocesor jazyka FORTRAN 66, aby mohl vytvořit hybridní jazykovou nadstavbu pojmenovanou RATFOR[3]. Základní programové konstrukce této nadstavby (podmínky, cykly) jsou stejné jako v jazyce C. Programování v RATFOR-u se tak podobá programování v „céčku“. Makro procesor zdrojový kód překládá zpátky do FORTRAN-u, poté kompilátor provede překlad do strojového kódu.

Všimněte si téměř dokonalé symbiózy s jazykem C
  • direktivy CPP #define, #include, #ifdef, … jsou pro M4 komentáře
  • klíčová slova oddělená od závorek mezerou, ztrácí svůj původní význam
    • M4 například ignoruje funkci void define (char c, int i) {…}
  • argumenty maker oddělují čárky stejně jako argumenty funkcí jazyka C
    • je-li definováno makro FUNC(char c, int i), jeho proměnné jsou:
      $# → 2, $0 → FUNC, $1 → char c, $2 → int i
  • levý řídící znak ` pro neterminály není součástí syntaxe rodiny jazyků C
  • pravý řídící znak ' nevadí, není-li součástí makra
    • oba řídící znaky lze skrýt do uživatelsky definovaných maker LQ(), RQ()
  • makra se píší VELKYMI_PISMENY, stejně jako neterminální symboly
    • tím je vymezen jejich jmenný prostor

Uživatelský manuál[4] zmiňuje ještě další, zde neuvedené spoluautory. Bylo by tedy značně nespravedlivé napsat, že autory makro procesoru M4 (1977) jsou pouze dva lidé.

 

Obrázek 1: Christopher Strachey[5], Dennis Ritchie[6], Brian Kernighan[7]

» GNU M4

Dnes existuje několik implementací lišící se od původní implementace spíše drobnostmi. Nejrozšířenější implementace M4 je GNU M4 používaná pro Autotools a pro překlad jednoduchého konfiguračního souboru sendmail.mc na složitý sendmail.cf. Autorem této implementace z roku 1990 je René Seindal. Následující příkaz nainstaluje m4:

dnf -y install make m4 pinfo

Podrobný popis klíčových slov se nachází v dokumentaci[8]:

~]$ pinfo m4
~]$ man m4
~]$ m4 --help

3  Základy jazyka M4 

Základem jazyka M4 je bezkontextová gramatika, automaty, zásobníkyvýstupní fronty. Pro pochopení jazyka M4 je proto velmi důležité rozumět základním pojmům teorie formálních jazyků – co jsou terminální symboly (stručně terminály) a neterminální symboly (stručně neterminály). Zmíněné pojmy si podrobněji vysvětlíme někdy později. Cílem tohoto úvodního dílu je hlavně ukázat praktické použití M4 na příkladech.

3.1  Bezkontextová gramatika 

Bezkontextová gramatika (krátce CFG) je formální gramatika, ve které mají všechna přepisovací pravidla tvar A → β. Neterminál A se přepíše na libovolně dlouhý řetězec β složený z terminálů Σ nebo neterminálů N. Kleeneho hvězda * znamená, že se neterminál A může přepsat na ε (přepisovací pravidlo: A → ε).

P: A → β
   A ∈ N
   β ∈ (N ∪ Σ)*

» Přepisovací pravidla M4

Přepisovací pravidla M4 jsou stejná jako přepisovací pravidla bezkontextové gramatiky.

# A → β
define(`A', `β')

# A → ε
define(`A')
define(`A', `')

Všechna klíčová slova M4 jsou neterminály (makra), provedou nějakou akci a přepíší se na ε nebo jiný symbol. Všechna klíčová slova lze přejmenovat nebo úplně vypnout. Tato vlastnost je velmi důležitá pro režim preprocesoru.

divert(ℤ) → ε
define(`A', `β') → ε
ifelse(`', `', `yes', `no') → yes
…

» Řízení expanze neterminálů

Výchozí dvojice znaků `' v M4 řídí expanzi neterminálů. Klíčové slovo changequote() je může změnit na jiné znaky, například {[], ␂␆, 〖〗}. Neterminály, které nechceme (ihned) expandovat, jsou obklopeny touto dvojicí znaků. Při průchodu makro procesorem jsou všechny symboly mezi touto dvojicí znaků terminálními symboly a vnější dvojice znaků je odstraněna. Další průchod již způsobí expanzi původně chráněných neterminálů. Dvojice řídících znaků se nastavuje na začátku kořenového souboru.

3.2  Automaty 

Automaty používají přepisovací pravidla gramatiky jako uzly a mění své stavy podle vstupních symbolů. Aktuálně používané přepisovací pravidlo produkuje do výstupní fronty (nebo do několika výstupních front) specifický kód, dokud automat nepřejde do jiného uzlu s jiným přepisovacím pravidlem. Automaty slouží jako „přepínače“ pravidel gramatiky. Příklady generujících automatů jsou ukázány v příloze.

3.3  Výstupní fronty 

Výstupní fronty jsou dočasné úložiště pro části výsledného kódu. Tyto části výsledného kódu jsou produkovány přepisovacími pravidly gramatiky, které přepisují vstupní symboly. Klíčové slovo divert(ℤ) nastavuje aktuální výstupní frontu. Na závěr jsou všechny neprázdné fronty vypsány ve vzestupném pořadí na standardní výstup a složí výsledný kód z částí kódu. Výstupní fronty jsou ukázány v příloze.

Zásobníky si ukážeme později.

4  Hlavní použití M4 

M4 se používá ke generování zdrojového kódu libovolného programovacího jazyka nebo jako preprocesor jakéhokoliv zdrojového kódu.

4.1  Generování kódu 

M4 transformuje vstupní data ze souborů .mc na výsledná data následujícím příkazem:

m4 root.m4 stem.m4 branch.m4 leaf.m4 input1.mc input2.mc > output.file

Během načítání souborů jsou prováděny dvě základní operace:

  1. čtení transformačních pravidel ze souborů s příponou .m4
  2. expanze maker uvnitř souborů s příponou .mc

Soubory input1.mcinput2.mc obsahují vstupní data ve specifickém formátu, který umožňuje jejich transformaci na výstupní data podle pravidel v předchozích .m4 souborech. Datové soubory .mc obvykle neobsahují žádná transformační pravidla.

Vstupní data mohou také přicházet z kolony:

cat input.mc | m4 root.m4 stem.m4 branch.m4 leaf.m4 - > output.file
cat input.mc | m4 root.m4 stem.m4 branch.m4 leaf.m4 - | gcc -x c -o progr -

Vyzkoušejte: Příklady generování kódu

4.2  Preprocesor 

M4 může pracovat v režimu preprocesoru. Vstupní zdrojový kód jím prochází beze změny s výjimkou neterminálních symbolů. Nalezené neterminály jsou expandovány na terminály a odchází spolu se zdrojovým kódem na výstup. M4 může rozšířit jakýkoliv jiný jazyk, kde je preprocesor nedostatečný (bez rekurze) nebo žádný. Důležité je zvolit vhodný levý znak pro řízení expanze neterminálů, který nesmí kolidovat se znakem vstupního zdrojového kódu. Kolize znaku je ale snadno řešitelná regulárním výrazem.

m4 root.m4 stem.m4 branch.m4 leaf.m4 file.c > preproc.file.c
m4 root.m4 stem.m4 branch.m4 leaf.m4 file.c | gcc -x c -o progr -

M4 v režimu preprocesoru může být součástí kolony. Konfliktní znak ` ze vstupního zdrojového kódu je skryt do makra, například `'LQ(). Prázdný pár řídících znaků `' před makrem slouží jako oddělovač symbolů.

sed '/^#/!s/`/`'\''LQ()/g' any_src.code | m4 rootq.m4 leaf.m4 - | gcc …

Při průchodu zdrojového kódu makro procesorem se makro `'LQ() přepíše zpátky na původní znak ` a prázdný pár `' je odstraněn. Použijeme-li pro řízení expanze neterminálů hranaté závorky, skryjeme stejným způsobem levou [ hranatou závorku.

sed '/^#/!s/\[/[]LB()/g' any_src.code | m4 rootb.m4 leaf.m4 - | gcc …

Pro řízení expanze neterminálů lze použít netisknutelné znaky (0x02) a  (0x06). Tyto znaky nemohou kolidovat s tisknutelnými znaky zdrojového kódu.

m4 rootn.m4 leaf.m4 any_src.code | gcc …

Vyzkoušejte: Příklady preprocesoru

» Smíšený režim

Smíšený režim je kombinací předchozích režimů a je používán hlavně na pokusy. Data nejsou oddělena od transformačních pravidel. Listový soubor leaf.m4 obsahuje definice těchto pravidel spolu se vstupními daty.

m4 root.m4 leaf.m4

Vyzkoušejte: M4 – příklady

5  Předpoklady pro zvládnutí M4 

Pro úspěšné zvládnutí tohoto makro jazyka je důležité splnit několik předpokladů. M4 není jednoduchý jazyk, protože není možné v něm myslet a programovat jako v běžném programovacím jazyce. Nejdůležitější je uvědomit si, že se v něm programují přepisovací pravidla gramatiky. Každý řetězec je buď terminální nebo neterminální symbol včetně všech klíčových slov jazyka (symboly #, jsou speciální případy neterminálů).

M4 záměrně nemá klíčová slova pro cykly (for/while), protože jeho základ je zcela jiný, než jaký mají procedurální nebo funkcionální jazyky.
  • cykly jsou pouze levorekurzivní nebo pravorekurzivní
  • větví se řetězením symbolů nebo klíčovými slovy ifelse(), ifdef()

5.1  Základy gramatik 

Základem všech gramatik jsou přepisovací pravidla, jejichž podobu obecně popisuje:

» Formální gramatika (Chomského typu)

G = (N, Σ, P, S)
N: neprázdná konečná množina neterminálních symbolů
Σ: konečná množina terminálních symbolů
   N ∩ Σ = ø
P: konečná množina přepisovacích pravidel
   (N ∪ Σ)* N (N ∪ Σ)* → (N ∪ Σ)*
S: je počáteční (startovací) symbol
   S ∈ N

Formální gramatika popisuje podmnožiny přepisovacích pravidel formálního jazyka. Jedna z podmnožin se jmenuje bezkontextová gramatika, krátce CFG. Jak již bylo dříve zmíněno, přepisovací pravidla CFG pracují stejně jako přepisovací pravidla jazyka M4. Některý z následujících dílů seriálu se podrobněji zaměří na formální gramatiky.

5.2  Základy automatů 

Schopnost používat převážně dvoustavové automaty je zásadní věc pro psaní jednoduchých M4 skriptů, protože převážná většina skriptů používá malé automaty.

» Testovací automat

Pořadí vstupních symbolů nebo jejich kontext lze otestovat automatem. Splňují-li vstupní symboly požadované vlastnosti, automat skončí v uzlu s dvojitým kroužkem, kterým se označuje akceptující stav.

 

Obrázek 2: Příklad automatu[9] akceptující sudý počet (žádný je také sudý) symbolů 0, ignorující symboly 1. Automat je shodný s regulárním výrazem (1*01*01*)*1*.

Předchozí automat lze zapsat jako ASCII art doprovázející M4 skript:

#          ____1
#         |   /
#      ___V__/   0    ____
# --->// S1 \\------>/ S2 \---.1
#     \\____//<------\____/<--'
#                0

» Generující automat

Vstupní symboly mění uzly automatu, čímž zároveň mění přepisovací pravidla pro generování kódu. Tento příklad naleznete v příloze:

#      _______      ___________
# --->/ ERROR \--->/ NEXT_ITEM \---.
#     \_______/    \___________/<--'

První symbol ve stavu ERROR vygeneruje záhlaví se závorkami a vloží první položku. Poté automat přejde do stavu NEXT_ITEM, ve kterém se pouze přidávají další položky. Automat zůstává v tomto stavu, dokud nejsou zpracována všechna data.

5.3  (GNU) make 

Dobře navržený generátor kódu se obvykle skládá z několika menších souborů, jejichž pořadí, závislosti a parametry se zapisují do souboru Makefile. Dobrá znalost tvorby Makefile je proto základním předpokladem pro zvládnutí M4. Čtení a údržba zdrojového kódu celkově zabere vždy více času než jeho tvorba. Dobře strukturovaný Makefile proto zásadním způsobem přispívá k celkové přehlednosti výsledného generátoru kódu.

Spouštění make[10] z editoru kódu pomocí vhodné klávesové zkratky zásadně urychluje vývoj M4 kódu. Soubor ~/.vimrc obsahuje nnoremap <c-j> :make<cr>.

5.4  Vim 

Zvládnutí editoru Vim[11] je důležitým předpokladem pro pohodlí a rychlost psaní kódu M4. Vim zkratky, definované klíčovým slovem iabbrev, ušetří velké množství zbytečně napsaného textu. Tyto zkratky také významně snižují výskyt téměř neviditelných chyb způsobených nepárovou závorkou, čímž šetří ztracený čas vynaložený na ladění kódu.

5.5  Talent a čas 

M4 obvykle nejde zvládnout přes víkend, zvláště chybí-li základy[12] teorie automatůformálních gramatik. Pro dokonalé zvládnutí M4 je nutné „odpřemýšlet si“ delší období a napsat určité množství špatného (složitého) M4 kódu, který z vlastní vůle přepíšete kvůli lepšímu nápadu. Tímto způsobem je možné postupně získat praxi.

6  Odkazy 

  1. Generování kódu v M4, vícejazyčná šablona s příklady pro www.root.cz
    http://github.com/jkubin/m4root
  2. A General Purpose Macro-generator, Computer Journal 8, 3 (1965), 225–41
    http://dx.doi.org/10.1093/comjnl/8.3.225
  3. RATFOR — A Preprocessor for a Rational Fortran, Brian W. Kernighan
    https://wolfram.schneider.org/bsd/7thEdManVol2/ratfor/ratfor.pdf
  4. The M4 Macro Processor, Bell Laboratories (1977)
    https://wolfram.schneider.org/bsd/7thEdManVol2/m4/m4.pdf
  5. Christopher Strachey, Computer Hope – Free computer help since 1998
    https://www.computerhope.com/people/christopher_strachey.htm
  6. Dennis Ritchie, Zomrel tvorca Unixu a jazyka C
    https://pc.zoznam.sk/novinka/zomrel-tvorca-unixu-jazyka-c
  7. Brian Kernighan, An Interview with Brian Kernighan
    https://www.cs.cmu.edu/~mihaib/kernighan-interview/
  8. GNU M4 - GNU macro processor, Free Software Foundation
    https://www.gnu.org/software/m4/manual/
  9. Teorie automatů, From Wikipedia, the free encyclopedia
    https://cs.wikipedia.org/wiki/Teorie_automat%C5%AF
  10. GNU Make Manual, Free Software Foundation
    https://www.gnu.org/software/make/manual/make.html
  11. Vim – všudypřítomný textový editor, který edituje text rychlostí myšlenky
    https://www.vim.org/
  12. Automaty a formální jazyky I, Učební text FI MU
    https://is.muni.cz/elportal/estud/fi/js06/ib005/Formalni_jazyky_a_automaty_I.pdf
  13. Automaty a gramatiky, Michal Chytil, 1. vydání, Praha, 331 s. 1984.
    https://is.muni.cz/publication/173173

A  Příklady generování kódu 

Znaky {`', [], ␂␆, 〖〗} v názvu příkladu řídí expanzi neterminálů.

A.1  [] Vstupní zdrojový kód

A.2  [] CSV – nejjednodušší příklad

A.3  [] CSV – počítadlo

A.4  💡 Úpravy speciálních znaků

A.5  [] C – výstupní fronta

A.6  [] INI – externí příkaz

A.7  [] .h – hex počítadlo

A.8  [] C – malý automat

A.9  [] C – malý automat 2

A.10  [] HTML – výstupní fronty

A.11  [] Větvení gramatikou

A.12  [] JSON – generující automat

A.12.1  [] JSON – pojmenované fronty

A.12.2  [] JSON – generované indexy front

A.13  [] INI – nespojitý index front

A.14  [] XML – smíšené zprávy

A.15  [] XML – oddělené zprávy

A.16  [] Bash ~]$ echo "řetězec"

A.17  [] Bash ~]$ echo 'řetězec'

Příklady v této příloze jsou složitější a jejich cílem je ukázat praktické použití jazyka M4. Podrobněji budou vysvětleny později.

A.1  [] Vstupní zdrojový kód 

Vstupní zdrojový kód je podobný CSV, který se převede na libovolně složitý cílový kód jiného jazyka pomocí CFG, automatů a výstupních front. Zásobníky v příkladech nejsou použity.

messages_raw.mc – vstupní zdrojový kód obsahuje speciální znaky ⚠

# 2018/05/15 Josef Kubin

ERROR([COMPLEX],     [!"#$%&'()*+ ,-./:;<=>?@[\]^_`{|}~])
QUERY([READABLE],    [Is badly written M4 code readable [N/y]?])
ERROR([SUCCESS],     [Complex M4 code failed successfully.])
WARNING([ADDICTIVE], [Programming in M4 is addictive!])
ERROR([NOFAULT],     [It's not a language fault!])
WARNING([NO_ERRORS], [No other errors detected.])
Vstupní soubor může také obsahovat poznámky, které nemusí být skryté v komentářích #, dnl, ifelse([…]) nebo [… někde uvnitř závorek …].

A.2  [] CSV – nejjednodušší příklad 

Tento příklad nepoužívá výstupní fronty, pouze generuje CSV oddělené znakem TAB.

# A → β
define([ERROR], [

	divert(0)dnl
[$1]	$2
divert(-1)
])
COMPLEX	!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
SUCCESS	Complex M4 code failed successfully.
NOFAULT	It's not a language fault!

A.3  [] CSV – počítadlo 

Příklad používá makro COUNT_UP z kořenového souboru, jehož β se zkopíruje do pravé strany makra COUNTER. Během první expanze COUNTER proběhne inicializace jeho startovací hodnoty. Další expanze vrátí číselný terminální symbol a proběhne zvýšení pomocného (globálního) symbolu o jedničku. COUNTER je malý automat.

# A → β
define([COUNTER], defn([COUNT_UP]))

# init counter
COUNTER(1)

# A → β
define([ERROR], [

	divert(0)dnl
ERR_[]COUNTER	[$1]	$2
divert(-1)
])
ERR_1	COMPLEX	!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
ERR_2	SUCCESS	Complex M4 code failed successfully.
ERR_3	NOFAULT	It's not a language fault!

A.4  💡 Úpravy speciálních znaků 

Každý typ výstupního kódu vyžaduje úpravu speciálních znaků. Klíčové slovo jazyka M4 patsubst() je nevhodné pro tento úkol. Všechny speciální znaky vstupního souboru napřed skryjeme do vhodně pojmenovaných maker pomocí regulárních výrazů.

» Upravený vstupní kód

messages.mc – všechny speciální znaky jsou skryty do maker

# 2018/05/15 Josef Kubin

ERROR([COMPLEX],     [[]EX()[]DQ()[#]$%[]AMP()[]AP()()*+[,]-./:;[]LT()=[]GT()?@[]LB()[]BS()[]RB()^_[]BQ(){|}~])
QUERY([READABLE],    [Is badly written M4 code readable []LB()N/y[]RB()?])
ERROR([SUCCESS],     [Complex M4 code failed successfully.])
WARNING([ADDICTIVE], [Programming in M4 is addictive[]EX()])
ERROR([NOFAULT],     [It[]AP()s not a language fault[]EX()])
WARNING([NO_ERRORS], [No other errors detected.])

Vytvoříme několik převodních souborů podle typu cílového kódu, makra pro hranaté závorky LB() a RB() jsou už definována v kořenovém souboru.

» [] XML, XSLT, HTML

markup.m4 – převodní soubor pro značkovací jazyky

# A → β
define([AMP], [&amp;])
define([AP], ['])
define([BQ], [`])
define([BS], [\])
define([DQ], ["])
define([EX], [!])
define([GT], [&gt;])
define([LT], [&lt;])

» [] C, JSON, INI – "řetězec"

code.m4 – převodní soubor pro zdrojový kód

# A → β
define([AMP], [&])
define([AP], ['])
define([BQ], [`])
define([BS], [\\])
define([DQ], [\"])
define([EX], [!])
define([GT], [>])
define([LT], [<])

» [] Bash – "řetězec"

doubleq.m4 – převodní soubor pro Bash řetězce v uvozovkách

# A → β
define([AMP], [&])
define([AP], ['])
define([BQ], [\`])
define([BS], [\\])
define([DQ], [\"])
define([EX], ["\!"])
define([GT], [>])
define([LT], [<])

» [] Bash – 'řetězec'

apost.m4 – převodní soubor pro Bash řetězce v apostrofech

# A → β
define([AMP], [&])
define([AP], ['\''])
define([BQ], [`])
define([BS], [\])
define([DQ], ["])
define([EX], [!])
define([GT], [>])
define([LT], [<])

» [] CSV, M4

unchanged.m4 – převodní soubor vrátí všechny speciální znaky zpátky

# A → β
define([AMP], [&])
define([AP], ['])
define([BQ], [`])
define([BS], [\])
define([DQ], ["])
define([EX], [!])
define([GT], [>])
define([LT], [<])

A.5  [] C – výstupní fronta 

Příklad používá jednu výstupní frontu na znaky }; pro uzavření pole na konci skriptu.

# A → β
define([ERROR], [

	divert(0)dnl
	"$2",
divert(-1)
])

divert(0)dnl
/*
 * DONTE()
 */

char *error[[]] = {
divert(1)dnl
};
divert(-1)
/*
 * DO NOT EDIT! This file is generated automatically!
 */

char *error[] = {
	"!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~",
	"Complex M4 code failed successfully.",
	"It's not a language fault!",
};

A.6  [] INI – externí příkaz 

Příklad spustí externí příkaz date a jeho výstup umístí do hranatých závorek. Výstupem externího příkazu jsou dvě položky oddělené čárkou. Makro FST() vybere první položku, protože druhá položka obsahuje nežádoucí znak nového řádku LF (0x0a).

# A → β
define([ERROR], [

	divert(0)dnl
[$1]="$2"
divert(-1)
])

divert(0)dnl
; DONTE()

LB()ARG1(esyscmd([date '+[hello_%Y%m%d],']))]
divert(-1)
; DO NOT EDIT! This file is generated automatically!

[hello_20200210]
COMPLEX="!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
SUCCESS="Complex M4 code failed successfully."
NOFAULT="It's not a language fault!"

A.7  [] .h – hex počítadlo 

Příklad používá makro COUNTER pro číslování výsledných CPP maker a jednu výstupní frontu. Fronta číslo 1 obsahuje direktivu preprocesoru #endif pro zakončení hlavičkového souboru. Převod dekadické hodnoty počítadla na dvoumístné hex-a číslo provádí klíčové slovo eval().

# A → β
define([COUNTER], defn([COUNT_UP]))

# init counter
COUNTER(0)

# A → β
define([ERROR], [

	divert(0)dnl
[#define $1		0x]eval(COUNTER, 16, 2)
divert(-1)
])

divert(0)dnl
/*
 * DONTE()
 */

#ifndef __ERROR_H
#define __ERROR_H

divert(1)
#endif /* __ERROR_H */
divert(-1)
/*
 * DO NOT EDIT! This file is generated automatically!
 */

#ifndef __ERROR_H
#define __ERROR_H

#define COMPLEX		0x00
#define SUCCESS		0x01
#define NOFAULT		0x02

#endif /* __ERROR_H */

A.8  [] C – malý automat 

Příklad používá malý automat NEW_LINE pro generování znaku nového řádku \n a jednu výstupní frontu číslo 1 do které se vloží znaky "; pro uzavření výsledného řetězce. Poprvé se NEW_LINE přepíše na ε, podruhé a dále se přepíše na \n.

#     NEW_LINE automaton
#      ___      ____
# --->/ ε \--->/ \n \---.
#     \___/    \____/<--'

# A → β
define([NEW_LINE], [define([$0], [\n])])

# A → β
define([ERROR], [

	divert(0)NEW_LINE[]$2[]dnl
divert(-1)
])

divert(0)dnl
/*
 * DONTE()
 */

char error[[]] =
"divert(1)";
divert(-1)
/*
 * DO NOT EDIT! This file is generated automatically!
 */

char error[] =
"!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~\nComplex M4 code failed successfully.\nIt's not a language fault!";

A.9  [] C – malý automat 2 

Tento příklad je podobný předchozímu, avšak každý řetězec je na novém řádku.

#      NEW_LINE automaton
#      ___      ________
# --->/ ε \--->/ \n"LF" \---.
#     \___/    \________/<--'

# A → β
define([NEW_LINE], [define([$0], [\n"
"])])

# A → β
define([ERROR], [

	divert(0)NEW_LINE[]$2[]dnl
divert(-1)
])

divert(0)dnl
/*
 * DONTE()
 */

char error[[]] =
"divert(1)";
divert(-1)
/*
 * DO NOT EDIT! This file is generated automatically!
 */

char error[] =
"!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~\n"
"Complex M4 code failed successfully.\n"
"It's not a language fault!";

A.10  [] HTML – výstupní fronty 

Příklad používá dvě výstupní fronty. Fronta číslo 1 obsahuje odstavce, fronta číslo 2 uzavírací značky HTML stránky. Navigační odkazy nemusí být nikde uloženy, jdou přímo na výstup. Zprávy typu QUERYWARNING jsou zpracovány stejně jako zprávy typu ERROR.

# vim:ft=m4

# A → β
# β
define([ERROR], [

	divert(0)dnl
		[<li>$0: <a href="#$1">$1</a></li>]
divert(1)dnl
	<p id="[$1]">$2</p>
divert(-1)
])

# A → β
define([QUERY], defn([ERROR]))
define([WARNING], defn([ERROR]))

divert(0)dnl
<!-- DONTE() -->
<!doctype html>
<html lang="en">
	<meta charset="utf-8">
	<title>__file__</title>
<body>
	<h1>The power of M4</h1>
	<ul>
divert(1)dnl
	</ul>
divert(2)dnl
</body>
</html>
divert(-1)
<!-- DO NOT EDIT! This file is generated automatically! -->
<!doctype html>
<html lang="en">
	<meta charset="utf-8">
	<title>messages.html.m4</title>
<body>
	<h1>The power of M4</h1>
	<ul>
		<li>ERROR: <a href="#COMPLEX">COMPLEX</a></li>
		<li>QUERY: <a href="#READABLE">READABLE</a></li>
		<li>ERROR: <a href="#SUCCESS">SUCCESS</a></li>
		<li>WARNING: <a href="#ADDICTIVE">ADDICTIVE</a></li>
		<li>ERROR: <a href="#NOFAULT">NOFAULT</a></li>
		<li>WARNING: <a href="#NO_ERRORS">NO_ERRORS</a></li>
	</ul>
	<p id="COMPLEX">!"#$%&amp;'()*+,-./:;&lt;=&gt;?@[\]^_`{|}~</p>
	<p id="READABLE">Is badly written M4 code readable [N/y]?</p>
	<p id="SUCCESS">Complex M4 code failed successfully.</p>
	<p id="ADDICTIVE">Programming in M4 is addictive!</p>
	<p id="NOFAULT">It's not a language fault!</p>
	<p id="NO_ERRORS">No other errors detected.</p>
</body>
</html>

A.11  [] Větvení gramatikou 

Příklad ukazuje větvení gramatikou, argumenty maker se ignorují. Vstupní neterminály se přepisují na terminály ERROR → 🐛, QUERY → 🐜, WARNING → 🐝.

# A → β
# β
define([ERROR], [

	divert(0)dnl
$0_INSECT[]dnl
divert(-1)
])

# A → β
define([QUERY],   defn([ERROR]))
define([WARNING], defn([ERROR]))
define([ERROR_INSECT],   [🐛])
define([QUERY_INSECT],   [🐜])
define([WARNING_INSECT], [🐝])
🐛🐜🐛🐝🐛🐝

A.12  [] JSON – generující automat 

Příklad používá dvě výstupní fronty a jeden generující automat. První chybová zpráva ERROR([…]) ve stavu ERROR vygeneruje záhlaví se závorkami a vypíše na výstup první záznam. Automat přejde do stavu NEXT_ITEM což je β pravidlo. Následující chybové zprávy ve stavu NEXT_ITEM pouze vypisují na výstup jednotlivé záznamy. Na závěr výstupní fronty číslo 12 vypíšou znaky ]}} čímž zakončí výsledný JSON.

#      _______      ___________
# --->/ ERROR \--->/ NEXT_ITEM \---.
#     \_______/    \___________/<--'

# A → β
define([ERROR], [

	# transition to the next node
	define([$0], defn([NEXT_ITEM]))

	divert(0),
	"error": LB()
		{"[$1]": "$2"}dnl
divert(1)
	RB()
divert(-1)
])

# β
define([NEXT_ITEM], [

	divert(0),
		{"[$1]": "$2"}dnl
divert(-1)
])

divert(0)dnl
{"generating_automaton": {
	"_comment": "DONTE()"dnl
divert(2)dnl
}}
divert(-1)
{"generating_automaton": {
	"_comment": "DO NOT EDIT! This file is generated automatically!",
	"error": [
		{"COMPLEX": "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"},
		{"SUCCESS": "Complex M4 code failed successfully."},
		{"NOFAULT": "It's not a language fault!"}
	]
}}

A.12.1  [] JSON – pojmenované fronty 

Příklad zpracovává další zprávy typu QUERYWARNING. Používá tři automaty a šest výstupních front. Generujeme-li složitější zdrojový kód, brzy narazíme na problém udržení konzistence indexů pro výstupní fronty. Abychom se vyhnuli zmatku, pojmenujeme si fronty a místo čísel používáme jména.

Abychom nemuseli definovat podobná pravidla, zkopírujeme si pravou stranu ERROR
(je to také β pravidlo) do pravé strany pravidel QUERYWARNING. Proměnná $0 se přepíše na jméno makra a zřetězí se s dalším symbolem. Nově vzniklý neterminál se přepíše na odpovídající terminální symbol (číslo fronty nebo jméno).

$0_QU → ERROR_QU → 2
$0_END → ERROR_END → 3
$0_NAME → ERROR_NAME → error
$0_QU → QUERY_QU → 0
$0_END → QUERY_END → 1
$0_NAME → QUERY_NAME → query
…
# DO NOT WRITE INDEXES MANUALLY, use counter!
define([QUERY_QU],     0)
define([QUERY_END],    1)
define([ERROR_QU],     2)
define([ERROR_END],    3)
define([WARNING_QU],   4)
define([WARNING_END],  5)
define([LAST_QUEUE],   6)

# names of message types
define([WARNING_NAME], [warning])
define([ERROR_NAME],   [error])
define([QUERY_NAME],   [query])

#      _________      ___________
# --->/  ERROR  \--->/ NEXT_ITEM \---.
#     |  QUERY  |    \___________/<--'
#     \_WARNING_/

# A → β
# β
define([ERROR], [

	# transition to the next node
	define([$0], defn([NEXT_ITEM]))

	divert($0_QU),
	"$0_NAME": LB()
		{"[$1]": "$2"}dnl
divert($0_END)
	RB()dnl
divert(-1)
])

# β
define([NEXT_ITEM], [

	divert($0_QU),
		{"[$1]": "$2"}dnl
divert(-1)
])

# A → β
define([QUERY],        defn([ERROR]))
define([WARNING],      defn([ERROR]))

divert(0)dnl
{"queue_names": {
	"_comment": "DONTE()"dnl
divert(LAST_QUEUE)
}}
divert(-1)
{"queue_names": {
	"_comment": "DO NOT EDIT! This file is generated automatically!",
	"query": [
		{"READABLE": "Is badly written M4 code readable [N/y]?"}
	],
	"error": [
		{"COMPLEX": "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"},
		{"SUCCESS": "Complex M4 code failed successfully."},
		{"NOFAULT": "It's not a language fault!"}
	],
	"warning": [
		{"ADDICTIVE": "Programming in M4 is addictive!"},
		{"NO_ERRORS": "No other errors detected."}
	]
}}

A.12.2  [] JSON – generované indexy front 

Během vývoje se často mění pořadí a počet výstupních front, což také vyžaduje častou změnu jejich indexů. Indexy je proto vhodné generovat. Můžeme pak používat prakticky neomezený počet front. Následující příklad ukazuje, jak se tyto indexy generují.

# defines a counter for output queues
# A → β
define([QUEUE_INDEX], defn([COUNT_UP]))

# initial index of the first output queue
QUEUE_INDEX(0)

# symbolic names for indices of output queues
# A → β
define([QUERY_QU],     QUEUE_INDEX)
define([QUERY_END],    QUEUE_INDEX)
define([ERROR_QU],     QUEUE_INDEX)
define([ERROR_END],    QUEUE_INDEX)
define([WARNING_QU],   QUEUE_INDEX)
define([WARNING_END],  QUEUE_INDEX)
# Keep it last!
define([LAST_QUEUE],   QUEUE_INDEX)

# names of message types
# A → β
define([WARNING_NAME], [warning])
define([ERROR_NAME],   [error])
define([QUERY_NAME],   [query])
#      _________      ___________
# --->/  ERROR  \--->/ NEXT_ITEM \---.
#     |  QUERY  |    \___________/<--'
#     \_WARNING_/

# A → β
# β
define([ERROR], [

	# transition to the next node
	define([$0], defn([NEXT_ITEM]))

	divert($0_QU),
	"$0_NAME": LB()
		{"[$1]": "$2"}dnl
divert($0_END)
	RB()dnl
divert(-1)
])

# β
define([NEXT_ITEM], [

	divert($0_QU),
		{"[$1]": "$2"}dnl
divert(-1)
])

# A → β
define([QUERY],   defn([ERROR]))
define([WARNING], defn([ERROR]))

divert(0)dnl
{"messages": {
	"_comment": "DONTE()"dnl
divert(LAST_QUEUE)
}}
divert(-1)
{"messages": {
	"_comment": "DO NOT EDIT! This file is generated automatically!",
	"query": [
		{"READABLE": "Is badly written M4 code readable [N/y]?"}
	],
	"error": [
		{"COMPLEX": "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"},
		{"SUCCESS": "Complex M4 code failed successfully."},
		{"NOFAULT": "It's not a language fault!"}
	],
	"warning": [
		{"ADDICTIVE": "Programming in M4 is addictive!"},
		{"NO_ERRORS": "No other errors detected."}
	]
}}

A.13  [] INI – nespojitý index front 

Příklad používá tři automaty a dvě výstupní fronty číslo 24 definované v odděleném souboru. Názvy INI sekcí jsou generovány řetězením symbolů. Příklad používá stejný soubor pro výstupní fronty jako příklad pro generování JSON.

#      _________      ___________
# --->/  ERROR  \--->/ NEXT_ITEM \---.
#     |  QUERY  |    \___________/<--'
#     \_WARNING_/

# A → β
# β
define([ERROR], [

	divert($0_QU)
BRAC($0_NAME)
[$1]="$2"
divert(-1)

	# transition to the next node
	define([$0], defn([NEXT_ITEM]))
])

# A → β
define([QUERY],   defn([ERROR]))
define([WARNING], defn([ERROR]))

# β
define([NEXT_ITEM], [

	divert($0_QU)dnl
[$1]="$2"
divert(-1)
])

divert(0)dnl
; DONTE()
divert(-1)
; DO NOT EDIT! This file is generated automatically!

[query]
READABLE="Is badly written M4 code readable [N/y]?"

[error]
COMPLEX="!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
SUCCESS="Complex M4 code failed successfully."
NOFAULT="It's not a language fault!"

[warning]
ADDICTIVE="Programming in M4 is addictive!"
NO_ERRORS="No other errors detected."

A.14  [] XML – smíšené zprávy 

Příklad používá jednu výstupní frontu číslo 1 pro uzavírací značku </messages>.

# A → β
# β
define([ERROR], [

	divert(0)dnl
	<$0_NAME>
		<name>[$1]</name>
		<value>$2</value>
	</$0_NAME>
divert(-1)
])

# A → β
define([QUERY], defn([ERROR]))
define([WARNING], defn([ERROR]))

divert(0)dnl
<!-- DONTE() -->
<?xml version="1.0" encoding="utf-8"?>
<messages>
divert(1)dnl
</messages>
divert(-1)
<!-- DO NOT EDIT! This file is generated automatically! -->
<?xml version="1.0" encoding="utf-8"?>
<messages>
	<error>
		<name>COMPLEX</name>
		<value>!"#$%&amp;'()*+,-./:;&lt;=&gt;?@[\]^_`{|}~</value>
	</error>
	<query>
		<name>READABLE</name>
		<value>Is badly written M4 code readable [N/y]?</value>
	</query>
	<error>
		<name>SUCCESS</name>
		<value>Complex M4 code failed successfully.</value>
	</error>
	<warning>
		<name>ADDICTIVE</name>
		<value>Programming in M4 is addictive!</value>
	</warning>
	<error>
		<name>NOFAULT</name>
		<value>It's not a language fault!</value>
	</error>
	<warning>
		<name>NO_ERRORS</name>
		<value>No other errors detected.</value>
	</warning>
</messages>

A.15  [] XML – oddělené zprávy 

Příklad seskupuje zprávy podle jejich typu pomocí výstupních front.

# A → β
# β
define([ERROR], [

	# transition to the next node
	define([$0], defn([NEXT_ITEM]))

	divert($0_QU)dnl
	<$0_NAME>
		<item>
			<name>[$1]</name>
			<value>$2</value>
		</item>
divert($0_END)dnl
	</$0_NAME>
divert(-1)
])

# β
define([NEXT_ITEM], [

	divert($0_QU)dnl
		<item>
			<name>[$1]</name>
			<value>$2</value>
		</item>
divert(-1)
])

# A → β
define([QUERY],        defn([ERROR]))
define([WARNING],      defn([ERROR]))

divert(0)dnl
<!-- DONTE() -->
<?xml version="1.0" encoding="utf-8"?>
<messages>
divert(LAST_QUEUE)dnl
</messages>
divert(-1)
<!-- DO NOT EDIT! This file is generated automatically! -->
<?xml version="1.0" encoding="utf-8"?>
<messages>
	<query>
		<item>
			<name>READABLE</name>
			<value>Is badly written M4 code readable [N/y]?</value>
		</item>
	</query>
	<error>
		<item>
			<name>COMPLEX</name>
			<value>!"#$%&amp;'()*+,-./:;&lt;=&gt;?@[\]^_`{|}~</value>
		</item>
		<item>
			<name>SUCCESS</name>
			<value>Complex M4 code failed successfully.</value>
		</item>
		<item>
			<name>NOFAULT</name>
			<value>It's not a language fault!</value>
		</item>
	</error>
	<warning>
		<item>
			<name>ADDICTIVE</name>
			<value>Programming in M4 is addictive!</value>
		</item>
		<item>
			<name>NO_ERRORS</name>
			<value>No other errors detected.</value>
		</item>
	</warning>
</messages>

A.16  [] Bash ~]$ echo "řetězec" 

# A → β
# β
define([ERROR], [

	divert(0)dnl
echo "$2"
divert(-1)
])

# A → β
define([QUERY], defn([ERROR]))
define([WARNING], defn([ERROR]))

divert(0)dnl
#!/bin/bash
#
[#] DONTE()

divert(-1)
#!/bin/bash
#
# DO NOT EDIT! This file is generated automatically!

echo ""\!"\"#$%&'()*+,-./:;<=>?@[\\]^_\`{|}~"
echo "Is badly written M4 code readable [N/y]?"
echo "Complex M4 code failed successfully."
echo "Programming in M4 is addictive"\!""
echo "It's not a language fault"\!""
echo "No other errors detected."

A.17  [] Bash ~]$ echo 'řetězec' 

# A → β
# β
define([ERROR], [

	divert(0)dnl
echo '$2'
divert(-1)
])

# A → β
define([QUERY], defn([ERROR]))
define([WARNING], defn([ERROR]))

divert(0)dnl
#!/bin/bash
#
[#] DONTE()

divert(-1)
#!/bin/bash
#
# DO NOT EDIT! This file is generated automatically!

echo '!"#$%&'\''()*+,-./:;<=>?@[\]^_`{|}~'
echo 'Is badly written M4 code readable [N/y]?'
echo 'Complex M4 code failed successfully.'
echo 'Programming in M4 is addictive!'
echo 'It'\''s not a language fault!'
echo 'No other errors detected.'

B  Příklady preprocesoru 

Znaky {`', [], ␂␆, 〖〗} v názvu příkladu řídí expanzi neterminálů.

B.1  `' Preprocesor jazyka C a M4

B.2  `' CSS – vložení souboru, komentář

B.3  ␂␆ Bash – netisknutelné znaky

B.1  `' Preprocesor jazyka C a M4 

Direktivy CPP jsou pro M4 jednořádkový komentář, což brání nežádoucí expanzi stejně pojmenovaných maker. Definujeme-li bezpečnější makro SAF(), stejně pojmenované makro SAF () nebude přepsáno. Jmenný prostor CPP tak může být zcela oddělen od jmenného prostoru M4. Problematický znak ` je skryt do makra LQ(). Apostrof ' ve zdrojovém kódu ničemu nevadí. Apostrof uvnitř makra ORD() je skryt do makra RQ(). Všimněte si jmen funkcí define () nebo ifelse () a kde je expandován SYMBOL.

# ORDinary and SAFe macros have different expansion:

# A → β
define(`ORD', `$0_M4 RQ()SYMBOL`'RQ()')
define(`SAF', `ifelse(`$#', `0', ``$0'', `($1) * ($1)	/* $0_M4 SYMBOL */')')

divert(0)dnl
/*
 * DONTE()
 */

#include <stdio.h>	/* CPP SYMBOL */

#define SYMBOL		/* CPP SYMBOL */
#define SAF(x)		((x) * ((x) - 1))	/* CPP SYMBOL */
#define ORD(x)		CPP SYMBOL x

int a = SAF (1 + 1);	/* CPP */
int b = SAF(2 + 2);	/* M4 */
char chr = 'x';
char foo[] = "Let's say: 'SYMBOL'";
char bar[] = "ORD (args, are, ignored)";

static void define (char *s) { puts(s);}
static void ifelse (char *s) { puts(s);}

int main(void)
{

#ifdef SYMBOL			/* SYMBOL */
	puts("LQ()SYMBOL'");	/* note: `LQ()SYMBOL' */
#endif

	define (foo);		/* SYMBOL */
	ifelse (bar);		/* SYMBOL() */

	return 0;
}
m4 -DSYMBOL='Hello, world!' rootq.m4 file.c.m4 file.c > preproc.file.c
/*
 * DO NOT EDIT! This file is generated automatically!
 */

#include <stdio.h>	/* CPP SYMBOL */

#define SYMBOL		/* CPP SYMBOL */
#define SAF(x)		((x) * ((x) - 1))	/* CPP SYMBOL */
#define ORD(x)		CPP SYMBOL x

int a = SAF (1 + 1);	/* CPP */
int b = (2 + 2) * (2 + 2)	/* SAF_M4 Hello, world! */;	/* M4 */
char chr = 'x';
char foo[] = "Let's say: 'Hello, world!'";
char bar[] = "ORD_M4 'Hello, world!' (args, are, ignored)";

static void define (char *s) { puts(s);}
static void ifelse (char *s) { puts(s);}

int main(void)
{

#ifdef SYMBOL			/* SYMBOL */
	puts("`Hello, world!'");	/* note: LQ()SYMBOL */
#endif

	define (foo);		/* Hello, world! */
	ifelse (bar);		/* Hello, world! */

	return 0;
}

B.2  `' CSS – vložení souboru, komentář 

CSS používá znak # pro kódy barev, což je také začátek jednořádkového M4 komentáře. Klíčové slovo changecom(/*,*/) nastaví víceřádkový komentář /* … */ a přepíše se na ε. Komentáře se vypínají stejným klíčovým slovem changecom bez parametrů.

foo.css – soubor vložený makro procesorem

.foo {
	border: WIDTH 2px 1px;
}
# CSS preprocessor

define(`WIDTH', `3px')
define(`TOP', `#f00')
define(`SIDES', `#0f0')
define(`BOTTOM', `#00f')
define(`SITE', `www.root.cz')
define(`IMAGE', `m4tux.png')
define(`PATH', `https://SITE/IMAGE')

divert(0)dnl
/* DONTE() */changecom(/*,*/)
/* DONTE() */

include(`foo.css')dnl

.bar {
	border-width: WIDTH;
	border-color: TOP SIDES BOTTOM; 
	background-image: url('PATH');
}

/* DONTE() */
changecom/* DONTE() */changecom(/*,*/)
m4 -DSYMBOL='Hello, world!' rootq.m4 file.css.m4 file.css > preproc.file.css
/* DO NOT EDIT! This file is generated automatically! */
/* DONTE() */

.foo {
	border: 3px 2px 1px;
}

.bar {
	border-width: 3px;
	border-color: #f00 #0f0 #00f; 
	background-image: url('https://www.root.cz/m4tux.png');
}

/* DONTE() */
/* DO NOT EDIT! This file is generated automatically! */

B.3  ␂␆ Bash – netisknutelné znaky 

Bash používá oba znaky, ` a [. Nechceme-li je skrývat do makra LQ() nebo LB(), můžeme použít pro řízení expanze neterminálů netisknutelné znaky, viz. příklad:

# vim:mps+=\:

# A → β
define(LEFT, $#)
define(OP, -eq)
define(RIGHT, 0)

divert(0)dnl
#!/bin/bash
#
# DONTE()

HELLO=`echo 'SYMBOL'`

if [[ LEFT OP RIGHT ]]
then
	echo $HELLO
fi
m4 -DSYMBOL='Hello, world!' rootn.m4 file.sh.m4 file.sh > preproc.file.sh
#!/bin/bash
#
# DO NOT EDIT! This file is generated automatically!

HELLO=`echo 'Hello, world!'`

if [[ $# -eq 0 ]]
then
	echo $HELLO
fi

C  M4 – příklady 

Znaky {`', [], ␂␆, 〖〗} v názvu příkladu řídí expanzi neterminálů.

C.1  [] JSON – levá závorka [

C.2  [] Bash – počítadla

C.3  [] .h – závorky [], [,], [#], [dnl]

C.4  [] AWK – příklady bezpečnějších maker

C.1  [] JSON – levá závorka [ 

Uvnitř hranatých závorek [… se neterminály neexpandují …]. Proto je levá hranatá závorka [ nahrazena makrem LB() z kořenového souboru.

# JSON

divert(0)dnl
{"foo": {
	"_comment": "DONTE()",
	"bar": LB()
		{"baz": "SYMBOL"}
	]
}}
m4 -DSYMBOL='Hello, world!' rootb.m4 json.m4 > hello_world.json
{"foo": {
	"_comment": "DO NOT EDIT! This file is generated automatically!",
	"bar": [
		{"baz": "Hello, world!"}
	]
}}

C.2  [] Bash – počítadla 

Počítadla COUNT_UPCOUNT_DOWN jsou definována v kořenovém souboru. Neterminály
[… uvnitř závorek …] nebudou expandovány, pouze se odeberou vnější závorky. Nutno použít makro LB() z kořenového souboru.

# A → β
define([LEFT], [$[#]])
define([OP], [-eq])
define([RIGHT], [0])

# define two counters
# A → β
define([__COUNTUP__], defn([COUNT_UP]))
define([__COUNTDN__], defn([COUNT_DOWN]))

# init counters
__COUNTUP__(10)
__COUNTDN__(10)

divert(0)dnl
#!/bin/bash
#
[#] DONTE()

if [ LEFT OP RIGHT ]
then
	echo '__COUNTUP__] SYMBOL LB()__COUNTDN__'
fi

if test LEFT OP RIGHT
then
	echo '__COUNTUP__] SYMBOL LB()__COUNTDN__'
fi

if LB()LB() LEFT OP RIGHT ]]
then
	echo '__COUNTUP__] SYMBOL LB()__COUNTDN__'
fi
m4 -DSYMBOL='Hello, world!' rootb.m4 sh.m4 > hello_world.sh
#!/bin/bash
#
# DO NOT EDIT! This file is generated automatically!

if  LEFT OP RIGHT 
then
	echo '10] Hello, world! [10'
fi

if test $# -eq 0
then
	echo '11] Hello, world! [9'
fi

if [[ $# -eq 0 ]]
then
	echo '12] Hello, world! [8'
fi

C.3  [] .h – závorky [], [,], [#], [dnl] 

Prázdný pár [] (nebo prázdný symbol v závorkách [ε]) slouží jako oddělovač symbolů. Závorky kolem znaku komentáře [#] vypnou jeho původní význam, stejně jako vypnou význam silnějšího M4 komentáře [dnl]. Vypnou také původní význam čárky [,] jako oddělovače argumentů maker. Tyto symboly se stanou obyčejnými terminálními symboly bez jakéhokoliv vedlejšího efektu.

# A → β
define([HELLO], [HELLO_WORLD])

divert(0)dnl
/*
 * [dnl] DONTE()
 */

[#]ifndef __[]HELLO[]_H
[#]define __[]HELLO[]_H

[#]define HELLO	SYMBOL

[#endif	/* __]HELLO[_H */]
m4 -DSYMBOL='Hello, world!' rootb.m4 h.m4 > hello_world.h
/*
 * dnl DO NOT EDIT! This file is generated automatically!
 */

#ifndef __HELLO_WORLD_H
#define __HELLO_WORLD_H

#define HELLO_WORLD	Hello, world!

#endif	/* __HELLO_WORLD_H */

C.4  [] AWK – příklady bezpečnějších maker 

Univerzální výstraha DONTE se ignoruje bez závorek, stejně jako LB, RB, … Taková makra explicitně vytváří vývojář skriptů, prohlédněte si kořenový soubor.

MIF20_tip3

# AWK

divert(0)dnl
#!/bin/awk -f
#
[# DONTE()] ---> "DONTE()"

BEGIN { print "DONTE[]() LB () LB() SYMBOL ]" }
m4 -DSYMBOL='Hello, world!' rootb.m4 awk.m4 > hello_world.awk
#!/bin/awk -f
#
# DONTE() ---> "DO NOT EDIT! This file is generated automatically!"

BEGIN { print "DONTE() LB () [ Hello, world! ]" }

D  Proč používat M4 a proč ne? 

D.1  👍 Proč generovat kód v M4

D.2  👎 Proč se vyhnout M4

D.1  👍 Proč generovat kód v M4 

  • přímé použití bezkontextové gramatiky (rekurze zdarma)
    • pro transformaci dat stačí napsat minimum M4 kódu
  • přímé použití automatů
    • možnost vymodelovat si potřebné algoritmy (M4 nepotřebuje verze)
  • přímé použití zásobníků
    • zásobníky propojené s automaty rozšiřují možnosti generátoru kódu
  • přímé použití výstupních front pro dočasné uložení výsledných částí kódu
    • jednotlivé fronty jsou na závěr vypsány na výstup ve vzestupném pořadí
  • vyšší rychlost generování kódu (ve srovnání s XSLT)
    • nízké nároky na výpočetní zdroje

D.2  👎 Proč se vyhnout M4 

  • univerzální jazyk nízké úrovně (podobně jako jazyk C)
    • nemůže konkurovat úzce specializovaným jazykům
  • neexistující komunita vývojářů (podzim 2019)
    • M4 je zapomenutý jazyk, málo existujících projektů
  • neobvyklé programovací paradigma vyžadující splnění několika předpokladů
    • M4 je proto náročný jazyk
  • produktivita značně závisí na zkušenostech (možný problém s termíny)
    • schopnost myslet v M4 je nutnost
  • údržba špatně napsaného M4 kódu je těžká
    • existující M4 kód je snadné proměnit ve zmatek (nutný dohled!)