přejít na obsah přejít na navigaci

Linux E X P R E S, Makroprocesor m4

Makroprocesor m4

Při práci v Linuxu a jiných systémech, ať již unixových nebo jiných, potřebujeme často automatizovat stále se opakující procesy. K tomuto účelu slouží mnoho nástrojů, některé poskytuje přímo shell, jindy je vhodné využít například některý skriptovací jazyk. Mezi nejstarší programy v unixovém světě patří makroprocesor m4, kterému se bude tento článek věnovat.


Jazyk m4 je nesmírně jednoduchý. Umožňuje vytvářet makra pro nejrůznější činnosti. Mezi nejoblíbenější způsoby použití patří:

  • generování statických (X)HTML stránek;
  • generování stále se opakujícího zdrojového kódu;
  • generování skriptů (viz automake/autoconf).

Makroprocesor m4 vznikl jako rozšíření makroprocesoru GPM (General Purpose Macro-generator), který zase vznikl jako rozšíření programu TRAC (Text Reckoning and Compiling). Všechny tyto systémy měly společné to, že pracovaly na základě nahrazování textu. M4 vznikl mimo jiné jako preprocesor pro jazyk ratfor, což je nadstavba jazyka FORTRAN.

Pokusím se zaměřit na první dva, neboť automake a autoconf jsou velmi komplexní balíčky a pro naše účely jsou nezajímavé.

Omezení

Jak bývá u tradičních unixových nástrojů zvykem, byly navrženy ještě na systémech se sedmibitovými terminály a tvůrci nepočítali s tím, že by se mohla v počítačovém světě používat čeština. Program m4 se češtině nebrání, až na jednu výjimku – definice maker. Proto veškeré názvy maker musí být bez háčků a čárek.

Dnes existuje m4 v každém unixovém systému a je pravděpodobné, že jej máte nainstalovaný. V linuxových distribucích je přítomen zpravidla GNU m4, v komerčních Unixech bývá implementace trochu jiná. Naštěstí je m4 standardizován v normě IEEE 1003.1 (POSIX), takže každý systém, který se k této normě aspoň trochu blíží, by měl být schopen prezentované příklady zpracovat. GNU rozšířením se pokusím co nejvíce vyhnout, případně na ně důrazně upozorním.

Uvozené řetězce

Standardně m4 pro každé nalezené slovo prohledává interní slovník a hledá, zda je možné jej něčím nahradit. Pokud opravdu slovo ve slovníku nalezne, nahradí jej obsahem definice a pokusí se jej znovu zpracovat. To se nám nemusí vždy hodit, proto jedna ze základních konstrukcí m4 jsou tzv. „uvozené řetězce“ (v originále „quoted strings“). Řetězec se uvozuje takto:

Toto je `pokus'.

Na české klávesnici má klávesa s vlnkou potisk se stříškou a kroužkem, klávesa s apostrofem je označena paragrafem a vykřičníkem. V Linuxu je obvyklé psát tyto znaky s pravým Altem, takže např. obyčejný apostrof napíšeme jako [AltGr-§].

Slovo pokus je uvozeno na začátku zpětným apostrofem (ASCII 96, je na anglické klávesnici na klávese vlnka/tilda vedle jedničky); konečný znak je obyčejný apostrof (ASCII 39, na anglické klávesnici na klávese s dvojitými uvozovkami).

Spouštíme m4

Obvyklá syntaxe je:

m4 [-Dmakro1=obsah1] [] [] ...

Makroprocesor m4 umožňuje i na příkazové řádce definovat makra. Potom zpracuje makrosoubor, případně standardní vstup, pokud není soubor uveden, zpracuje vstupní data a výsledek zapíše na standardní výstup. Pokud chceme výsledek zapsat do souboru (obvykle ano), musíme standardní výstup přesměrovat.

Obrázek:  1.jpg

Nasazujeme m4 do praxe

Nerad chodím okolo horké kaše, takže zkusíme rovnou nasadit m4 na nějaký praktický programátorský problém. Jednoduché nahrazování textu je pro většinu z nás naprosto bezpředmětné, protože jej umí každý normální editor (včetně mého oblíbeného ViMu). Zkusme tedy použít makra m4 pro usnadnění práce s architekturou JavaBeans.

Technologie JavaBeans

Tato technologie firmy Sun Microsystems je jistou obdobou ActiveX prvků firmy Microsoft. Z programátorského hlediska je známá svými manipulačními metodami ve tvaru getProperty/setProperty. Existuje ještě technologie Enterprise JavaBeans, která umožňuje oddělit klientskou a serverovou část. Většina postupů z následujících příkladů je aplikovatelná i na ni.

První pokus

Pro začátek začneme s tím, že definujeme strukturu vstupu. Nejjednodušší bude tvar podobný tomuto:

property(`typ',`jméno')

Budeme od makroprocesoru chtít, aby toto makro expandovalo na následující tři řetězce:

private typ jméno;
public void setJméno (typ x) {
this.jméno = x;
}
public typ getJméno () {
return this.jméno;
}

Po prvním shlédnutí je jasné, že takové zadání bude vyžadovat trojí definici jednoho makra. Vytvoříme tedy soubory makro1.m4, makro2.m4 a makro3.m4 a naplníme je následujícím obsahem:

makro1.m4:
define(`property',`private $1 $2;')
makro2.m4:
define(`property',`public void set$2 ($1 x) {
this.$2 = x;
}')
makro3.m4:
define(`property',`public $1 get$2 () {
return this.$2;
}')

Pokud tedy vytvoříme soubor Osoba.m4 s obsahem

property(`String',`jmeno')
property(`String',`prijmeni')
property(`Date',`datumnarozeni')

a tento soubor zpracujeme pomocí m4 a výše vypsaných souborů, zjistíme, že jsme si práci příliš neulehčili. Naše řešení má pár chyb:

  • musíme výsledek manuálně poskládat do souboru Osoba.java;
  • nerespektujeme zásadu, že jednotlivá slova ve jménu proměnné jsou oddělená podtržítkem;
  • obdobně nerespektujeme, že jednotlivá slova ve jménech metody by měla začínat velkým písmenem.

Refaktorizace, část první

Nejprve uhasíme to, co nás pálí nejvíce, v našem případě tedy první problém. Zkusíme vytvořit soubor makro4.m4 takového obsahu:

define(`property',`
private $1 $2;
public void set$2 ($1 x) {
this.$2 = x;
}
public $1 get$2 () {
return this.$2;
}
')

Pokud s tímto souborem zpracujeme soubor Osoba.m4 (tedy takto: m4 makro4.m4 Osoba.m4), získáme výstup, ve kterém jsou proměnné a metody pěkně pohromadě.

Obrázek:  2.jpg

Refaktorizace, část druhá

Přesto by nás tento výsledek neměl nechat zcela spokojenými. Stále ještě nezačínají jednotlivá slova ve jméně metody velkým písmenem a v názvu proměnné nejsou oddělena podtržítkem. Nejprve přijmeme dohodu, že budeme jednotlivé proměnné zapisovat např. takto (je to jen drobná úprava):

property(`Date',`Datum Narozeni')
property(`String',`Trvale Bydliste')

Každé slovo tedy začíná velkým písmenem a je odděleno mezerou. Přepíšeme naši definici makra:

define(`property',`
private $1 translit($2,` A-Z',`_a-z');
public void set`'translit($2,` ') ($1 x) {
this.`'translit($2,` A-Z',`_a-z') = x;
}
public $1 get`'translit($2,` ') () {
return this.`'translit($2,` A-Z',`_a-z');
}
')

Makro translit změní znaky v řetězci předaném jako první parametr znaky z množiny ve druhém parametru na znaky v množině ve třetím parametru. Nezapomeňme, že před A-Z je ve výrazu translit mezera, která je potom ve výsledku nahrazena podtržítkem. Dále se v textu vyskytuje kombinace `' (prázdný řetězec), která umožňuje oddělit od sebe text a makro, protože pokud by m4 narazil na kombinaci settranslit, pokusil by se najít toto makro, které nikde nebylo definováno a výsledek by byl nepoužitelný.

Vylepšování

Nyní máme již výstup téměř dokonalý. Přesto bychom si přáli, aby byly v souboru nejprve proměnné, potom metody pro přístup, a to nejprve set a potom get, a teprve nakonec ostatní, ručně definované metody. Abychom dosáhli požadovaného výsledku, využijeme toho, že m4 má standardně devět bufferů, do kterých můžeme zapisovat a které posléze můžeme vypsat. K realizaci slouží makro divert, jemuž jako parametr předáme číslo bufferu, do kterého budeme chtít zapisovat. Makro divert bez parametru nastaví jako buffer přímo standardní výstup. Pro definici proměnných použijeme buffer číslo 1, pro metody set č. 2, pro metody get č. 3 a pro zbytek číslo 4. Doplníme naše makro na takovýto tvar:

define(`property',`
divert(1)dnl
private $1 translit($2,` A-Z',`_a-z');
divert(2)
public void set`'translit($2,` ') ($1 x) {
this.`'translit($2,` A-Z',`_a-z') = x;
}dnl
divert(3)
public $1 get`'translit($2,` ') () {
return this.`'translit($2,` A-Z',`_a-z');
}dnl
divert(4)dnl
')

Makro dnl slouží pro ignorování všeho až do konce řádku včetně, čímž se bráníme tomu, aby v souboru nebylo více prázdných řádků, než je nutné. Nyní můžeme makro otestovat tak, aby výsledný kód byl opravdu kompilovatelný: vytvořme soubor Osoba.java.m4:

/* Osoba.java */
import java.util.Date;
public class Osoba {
property(`String',`Jmeno')dnl
property(`String',`Prijmeni')dnl
property(`Date',`Datum Narozeni')dnl
property(`String',`Adresa')dnl
public void vypis () {
System.out.println("Jmeno: "+this.jmeno);
System.out.println("Prijmeni: "+this.prijmeni);
System.out.println("Datum narozeni: "+this.datum_narozeni.toString());
}
public void vypis2 () {
System.out.println("Jmeno: "+getJmeno());
System.out.println("Prijmeni: "+getPrijmeni());		
System.out.println("Datum narozeni: "+getDatumNarozeni().toString());
}
}

Obrázek:  3.jpg

Po zpracování makroprocesorem m4 dostaneme následující výstup:

/* Osoba.java */
import java.util.Date;
public class Osoba {
private String jmeno;
private String prijmeni;
private Date datum_narozeni;
private String adresa;
public void setJmeno (String x) {
this.jmeno = x;
}
public void setPrijmeni (String x) {
this.prijmeni = x;
}
public void setDatumNarozeni (Date x) {
this.datum_narozeni = x;
}
public void setAdresa (String x) {
this.adresa = x;
}
public String getJmeno () {
return this.jmeno;
}
public String getPrijmeni () {
return this.prijmeni;
}
public Date getDatumNarozeni () {
return this.datum_narozeni;
}
public String getAdresa () {
return this.adresa;
}
public void vypis () {
System.out.println("Jmeno: "+this.jmeno);
System.out.println("Prijmeni: "+this.prijmeni);
System.out.println("Datum narozeni: "+this.datum_narozeni.toString());
}
public void vypis2 () {
System.out.println("Jmeno: "+getJmeno());
System.out.println("Prijmeni: "+getPrijmeni());
System.out.println("Datum narozeni: "+getDatumNarozeni().toString());
}
}

Nyní již známe základy makroprocesoru m4, nejdůležitější konstrukce a měli bychom být schopni makroprocesor aplikovat na praktické problémy. V příštím díle bychom mohli m4 a jeho přátele sed a awk nasadit na podstatně složitější problém, a to tvorbu domovských stránek.

Nahoru

Přidat téma diskuse

Nejsou podporovány žádné značky, komentáře jsou jen čistě textové. Více o diskuzích a pravidlech najdete v nápovědě.
Diskuzi můžete sledovat pomocí RSS kanálu rss



 
 

Top články z OpenOffice.cz

Ondřej Jakubčík

Ondřej Jakubčík je absolventem gymnázia v Ostrově, v současnosti je studentem Fakulty elektrotechnické ČVUT v Praze. Svobodný software používá od roku 1997, zajímá se zejména o programování v jazycích C, C++ a Java, dále o internetové technologie a zabezpečení počítačových sítí. Pro svou práci využívá téměř výhradně operační systém Linux, ovšem aktivně se zajímá i o jiné systémy, např. FreeBSD nebo Solaris.


  • Distribuce: Linux From Scratch + vlastni modifikace


Tagy