EXPERIMENTÁLNÍ BUILD MANAGER verze 0.1.0
příručka pro uživatele
Obsah 1. Úvod.............................................................................................................................. 4 1.1 Co je EBM?............................................................................................................. 4 1.2 Obsah příručky........................................................................................................ 4 1.3 Konvence ................................................................................................................ 5 2. Principy programu ........................................................................................................ 6 2.1 Cíle a graf závislostí ............................................................................................... 6 2.2 Tvorba grafu závislostí ........................................................................................... 7 2.3 Aktualizační proces................................................................................................. 9 2.4 Varianty překladu ................................................................................................... 9 3. Jazyk EBM .................................................................................................................. 10 3.1 Lexikální pravidla ................................................................................................. 10 3.1.1 Velikost písmen ............................................................................................. 10 3.1.2 Bílé znaky a komentáře.................................................................................. 10 3.1.3 Identifikátory ................................................................................................. 11 3.1.4 Symboly ......................................................................................................... 11 3.1.4 Řetězcové literály .......................................................................................... 12 3.2 Typy proměnných ................................................................................................. 13 3.3 Syntaxe jazyka ...................................................................................................... 14 3.3.1 Strukturování příkazů .................................................................................... 14 3.3.2 Deklarační příkazy......................................................................................... 14 3.3.3 Práce s atributy cílů........................................................................................ 18 3.3.4 Nebooleovské výrazy a volání funkcí............................................................ 19 3.3.5 Booleovské výrazy......................................................................................... 22 3.3.6 Přiřazovací operátory..................................................................................... 23 3.3.7 Konstrukce on ............................................................................................... 24 3.3.8 Přerušení vykonávání příkazů........................................................................ 25 3.3.9 Podmíněné větvení......................................................................................... 25 3.3.10 Smyčky ........................................................................................................ 27 4. Používání programu.................................................................................................... 29 4.1 Inicializační soubory............................................................................................. 29 4.2 Argumenty programu............................................................................................ 31 5. Standardní funkce ....................................................................................................... 34 5.1 Základní funkce a proměnné................................................................................. 34 5.2 Vestavěné atributy ................................................................................................ 37 5.3 Standardní funkce a proměnné ............................................................................. 40 5.3.1 Namespace Action...................................................................................... 40 5.3.2 Namespace Core .......................................................................................... 41 2
5.3.3 Namespace File .......................................................................................... 44 5.3.4 Namespace FileSystem ............................................................................ 46 5.3.5 Namespace Paths ........................................................................................ 47 5.3.6 Namespace Strings ................................................................................... 50 5.3.7 Namespace System...................................................................................... 53 5.3.8 Namespace Targets ................................................................................... 56 6. Knihovna pravidel....................................................................................................... 60 6.1 Organizace pravidel .............................................................................................. 60 6.2 Organizace projektů.............................................................................................. 61 6.3 Obecné utility........................................................................................................ 62 6.4 Prototypy a implementace .................................................................................... 63 6.5 Ukázka: Hello, World!.......................................................................................... 64 Příloha: Gramatika jazyka .............................................................................................. 68
3
1. Úvod 1.1 Co je EBM? Experimentální Build Manager (EBM) je program pro automatizovaný překlad jiných programů a pro správu jejich překladu. Program je určen zejména vývojářům software, jimž má pomoci s tvorbou a údržbou softwarových projektů, zvláště pokud jsou určeny pro více platforem. Hlavní rysy programu lze shrnout do následujících bodů: • jednoduchý strukturovaný jazyk pro popis projektů, • možnost velmi jemného nastavování parametrů pro překlad, • snadné rozšiřování podpory různých překladačů a nástrojů, • podpora koexistence více variant projektu, aniž by se ovlivňovaly, • přenositelnost konfigurace mezi překladači i platformami, • možnost paralelního spouštění více překladačů, • robustní i efektivní systém aktualizací.
1.2 Obsah příručky Tato příručka představuje stručný uživatelský manuál, který by měl čtenáře seznámit s možnostmi EBM, s logikou jeho fungování a s jazykem, kterým se EBM ovládá. První část příručky je věnována nástinu principů programu, z nichž vychází i jazyk pro jeho obsluhu, který je popsaný ve druhé části textu. Třetí část je pak věnována výčtu argumentů programu a jeho konfiguraci. Čtvrtá část představuje referenční příručku k vestavěným a standardním funkcím a pátá část pak stručný výčet zásad pro tvorbu předdefinovaných pravidel a náhled na pravidla, která byla vytvořena pro demonstrační účely. Pokud čtenář chce nejdříve získat lepší představu o jazyku EBM i o jeho používání, lze mu jen doporučit, aby nahlédl do kapitoly 6 na ukázku malého demoprojektu.
4
1.3 Konvence Metaznaky V této i v následujících kapitolách budou při výkladu syntaxe používány obvyklé metaznaky, např. [ ] uzavírají nepovinnou část syntaxe, | značí alternativní možnosti, … značí opakování předchozího atd.
Jména souborů Ve všech souborech, které uživatel v EBM má editovat či vytvářet, se používá pro zápis cest k souborům a adresářům formát cest označovaný jako logický formát. Logický formát se snaží být platformně co nejvíce nezávislý a umožnit jednotnou manipulaci s cestami z různých platforem (naproti tomu platformně závislý zápis cest se bude označovat jako fyzický nebo nativní). Jako oddělovač adresářové hierarchie logických cest se používá obyčejné (nikoliv zpětné) lomítko a kořenový adresář musí také vždy začínat lomítkem, jinak je považován za relativní a bude s ním tak nakládáno. Důležité je podotknout, že logické cesty jsou vždy citlivé na velikost písmen, i když na aktuální platformě nemusí být velikost písmen souborovým systémem rozlišována. Přesto není oddělení od platformy dokonalé, jak ukazuje příklad přepisu nativní absolutní cesty X:\EBM\rules pro platformu Windows, která se do logického formátu přeloží jako /X:/EBM/rules. Proto je vhodné se absolutním cestám zcela vyhýbat, což však lze velmi snadno a to i v případě konfigurace programu pomocí inicializačních souborů. Až na případy, kdy to bude explicitně řečeno, se bude dále předpokládat, že cesta k souboru či adresáři je v logickém formátu. Jedním z těchto výjimečných případů jsou jména souborů zadávaná na příkazové řádce, kde je respektování platformních zvyklostí nejvýš vhodné.
5
2. Principy programu Cílem EBM je, aby uživatel mohl pomocí textového souboru snadno popsat jednoduché závislosti mezi soubory, které tvoří projekt; např. aby mohl zadat typ projektu a jeho vstupy, jak ukazuje následující ukázka: Projects.executable "hello" : "main.c" "lexer.l" ;
Vhodné také je, když uživatel může ovlivnit výchozí nastavení celého projektu, nebo dokonce i nastavení konkrétních souborů ve stylu, který připomíná nastavování v grafických integrovaných prostředích: on "main.c" { CC/Define += "MY_FLAG=1" CC/Undef += "THEIR_FLAG" }
Obsahem této kapitoly je seznámit uživatele s mechanismem, na kterém je celé fungování EBM postaveno. Pochopení aspoň základních postupů pomůže uživateli ve snadnějším používání programu a dovolí mu využívat plně možnosti, které jazyk EBM nabízí.
2.1 Cíle a graf závislostí Ústředním pojmem pro EBM je pojem cíl (target). Cíl je abstraktní entita, která může a nemusí být spjata s diskovým souborem; pokud k cíli není přiřazen žádný soubor, cíl se nazývá virtuální. Mezi cíli mohou existovat závislosti. Tyto závislosti tvoří orientovaný graf: vrcholy grafu jsou cíle a hranami jsou závislosti mezi cíli. Tento graf se nazývá graf závislostí. Pomocí grafu závislostí se modelují závislosti mezi soubory a způsob, kterým musí být soubory zpracovány např. při překladu zdrojových textů až do binárního spustitelného souboru nebo knihovny. Cíle v takovém případě reprezentují diskové soubory a jejich zpracování musí respektovat uspořádání zadané závislostmi mezi nimi. EBM řídí tedy svou práci podle grafu závislostí. Cíle v EBM jsou jednoznačně pojmenovány; jména cílů se nazývají virtuální jména (resp. virtuální cesty) a jejich zápis se řídí stejnými pravidly, jakými se řídí jména logická zmiňovaná v úvodní kapitole. Pojmenování cílů tedy připomíná adresářovou strukturu, což usnadňuje orientaci mezi cíli a umožňuje je snadněji organizovat. Je
6
nutné mít však na paměti základní fakt, že virtuální jména jsou nezávislá na konkrétní adresářové struktuře na disku a že cíle jsou abstraktní entity. Každý cíl nese sadu hodnot, tzv. atributů. Atributy cíle představují jeho vlastnosti a ovlivňují nakládání s cílem. Pomocí atributů se např. i určují závislosti mezi cíli a tvoří graf závislostí. Řada atributů je předdefinovaná a má pevný význam, ale je možné snadno přidávat další atributy.
2.2 Tvorba grafu závislostí Jak bylo řečeno výše, cíle mohou reprezentovat diskové soubory a závislosti mezi cíli modelují závislosti mezi reprezentovanými soubory. Klíčové pro určení závislostí a pro nakládání s cíli jsou hodnoty atributů cílů. Příklad v úvodu této kapitoly neudělá nic jiného, než že spustí předdefinovanou proceduru (zapsanou ve skriptovacím jazyce EBM), která vytvoří cíle, korespondující se zadanými soubory, a nastaví jejich atributy tak, aby vznikl jednoduchý graf závislostí, který ukazuje obrázek 1. Druhá část úvodní ukázky pak u jednoho cíle ještě upraví některé atributy, které ovlivňují jeho zpracování.
main.c
lexer.l
myapp (executable) Obrázek 1: Primární graf. Schéma ukazuje primární graf v podobě, ve které je zkonstruován pravidlem z příkladu. Pravidlo kromě závislostí nastaví i typ výsledného cíle, který nelze odvodit automaticky.
Pro většinu překladačů není dostatečné zadání pouze vstupů a výstupů, resp. může být dostatečné pouze v jednoduchých případech a může vynutit zpracování zbytečně mnoha souborů. Při respektování atributů, které uživatel zadá, ať již přímo nebo pomocí různých procedur, je tedy nutné graf popisující jednoduché závislosti rozšířit, aby popsal všechny kroky, které je nutné vykonat, a pokryl i složitější případy. V našem příkladu to bude spuštění překladače na jednotlivé vstupy a následně linkeru na výstupy překladače. Primární graf z obrázku 1 se změní na graf sekundární zobrazený na obrázku 2.
7
main.c
lexer.l
lexer.c
main.o
lexer.o
myapp (executable) Obrázek 2: Sekundární graf. Tento graf vznikne aplikací odvozovacích pravidel na primární graf, který je uveden na obrázku 1. Odvozovací pravidla se mohou řetězit, takže z cíle lexer.l bude odvozen prvně cíl lexer.c a aplikací stejného pravidla, které zpracovává cíl main.c, bude odvozen i lexer.o.
Pochopitelně o toto rozšíření grafu se nestará uživatel přímo, nýbrž algoritmy zabudované v EBM s pomocí pravidel, která již jsou v distribuci EBM připravena, nebo která si uživatel může sám také vytvořit, pokud mu předdefinovaná pravidla nevyhovují. Při vytváření sekundárního grafu se všechny cíle zpracovávají v pořadí, které respektuje závislosti primárního jednoduchého grafu; primární ani sekundární graf závislostí nesmí nikdy obsahovat cykly, neboť pak není snadné určit, jaké pořadí jeho zpracování je to správné. Při zpracovávání primárního grafu, kdy je vytvářen graf sekundární, se na každý zpracovávaný cíl aplikuje následující jednoduchý algoritmus. 1. Určí se typ cíle, pokud není již nastavený v příslušném atributu. 2. Určí se procedura, která se postará o rozšíření grafu závislostí vzhledem k atributům zpracovávaného cíle. Tato procedura, nazývaná odvozovací pravidlo, je buď nastavena opět v atributech cíle, nebo se určí podle jeho typu. 3. Vyvolá se odvozovací pravidlo, které může změnit atributy cíle a vytvořit i nové cíle, a tak graf rozšířit. Nově vzniklé cíle jsou také zpracovány tímto algoritmem a pořadí jejich zpracování je stejné, jako kdyby tyto nové cíle nevznikly až během zpracování primárního grafu, ale jako kdyby byly vytvořeny dříve; jsou tedy respektovány závislosti nových cílů nastavené po jejich vytvoření. Po zpracování všech cílů vznikne zúplněný sekundární graf. V něm by již měly všechny cíle mít nastavené všechny potřebné atributy a podle těchto atributů se průchodem sekundárního grafu vyvolávají ve správném pořadí příkazy, které se postarají o aktualizaci souborů spjatých s jednotlivými cíli. Je nutné mít vždy na paměti, že EBM se řídí grafem závislostí – řídí se jeho průchodem a atributy cílů, které při průchodu zpracovává.
8
2.3 Aktualizační proces Má-li být projekt aktualizován s přihlédnutím ke změnám, které byly učiněny ve zdrojových textech, je potřeba vykonat některé operace, např. spustit překladač na změněné soubory a zohlednit výsledek překladu v souborech, které na změněných vstupech závisí, tedy zařídit se přesně podle grafu závislostí. EBM postupuje podle směru závislostí a vyhodnotí u každého cíle, zda je aktuální. Pokud aktuální není, spustí aktualizační akce, které na základě atributů cíle zjednají nápravu. Vyhodnocení aktuálnosti bere v úvahu jednak výsledky předchozích operací při průchodu grafem (takže jedna změna může vynutit kaskádu dalších změn) a jednak čas změny souboru. Vyhodnocován může být i obsah souborů – např. v jazycích C a C++ existuje závislost programových modulů na hlavičkových souborech a změna hlavičkového souboru ovlivní všechny soubory, které jej (i nepřímo) zanořují. Informace získané z obsahu souborů pomocí tzv. scanneru (funkce EBM, lze zapojit i externí program) a údaje o stavu cílů jsou ukládány do pomocných souborů, tzv. statefiles, odkud mohou být při dalším spuštění rychleji získány a pomoci přesnějšímu vyhodnocení změn, které se mezitím staly.
2.4 Varianty překladu Je výhodné, když může vedle sebe existovat několik variant projektu – přinejmenším vývojář ocení variantu pro ladění a variantu, která představuje finální výsledek. EBM sice nechává většinu podpory variant na skriptech, ale poskytuje jim konstrukce, které programování variant usnadňují. Většina rysů jednotlivých variant bývá shodná, je tedy podporováno přirozené odvozování variant od sebe, kdy odvozená varianta má všechny rysy původní varianty, ale přidá k nim vlastní, nebo některé zděděné rysy změní. Varianty mají jednoduchá symbolická pojmenování, která jsou hierarchická a sledují přirozenou hierarchii variant, která vzniká odvozováním variant. Uživatel si vybírá variantu, která se má zpracovat, pouze pomocí těchto jednoduchých jmen; není podporováno nastavování jednotlivých vlastností varianty např. argumenty programu, neboť je to jednak méně pohodlné a hlavně hrozí obtížnou replikací takové ad hoc sestavené varianty. Příkladem jmen variant mohou být např. win32.release.static nebo linux.debug.shared. Nelze vybrat variantu, která není skriptem schválena jako legální, takže např. linux.debug.release nemusí být povoleno, neboť by nedávalo dobrý smysl.
9
3. Jazyk EBM Skriptovací jazyk EBM slouží nejen k zápisu konfigurace projektu uživatelem, ale také k vytvoření předdefinovaných procedur, které uživatel běžně používá. Ačkoliv jednoduché běžné použití předdefinovaných pravidel připomíná deklarace, jimiž uživatel pouze definuje vztahy mezi cíli a případně jejich atributy, je jazyk EBM typovaným silným procedurálním jazykem, jehož charakter je plně využit až při vytváření pravidel. Tato kapitola probere a vyloží lexikální i syntaktické konstrukce jazyka a jejich sémantiku. Konstrukcí, které jsou spjaty s fungováním EBM, obsahuje jazyk relativně málo, a podpora fungování EBM je soustředěna zejména do vestavěných procedur, které jsou popsány ve stylu referenční příručky v páté kapitole. Tamtéž budou popsány i vestavěné atributy; jazyk samotný totiž vyžaduje definici pouze jediného atributu a na zbytek atributů lze pohlížet jako na dodatečně definované pro účely vestavěných procedur.
3.1 Lexikální pravidla Jazyk je založený na tokenizaci textu a nepoužívá se žádný preprocesor. Zpracování skriptu z jiného souboru je záležitostí vestavěných funkcí a podmíněné zpracování části zdrojového textu není potřeba, protože deklarace se chápe také jako příkaz, a je tedy možné podmíněnost deklarací svěřit podmíněným příkazům jazyka samotného.
3.1.1 Velikost písmen Velká a malá písmena jsou důsledně rozlišována ve všech symbolech. Identifikátory myIdentifier a MyIdentifier nejsou totožné, symbol While není klíčové slovo, zatímco while ano.
3.1.2 Bílé znaky a komentáře Bílé znaky (mezera, horizontální i vertikální tabulátor a konec řádku) mají význam pouze v případech, kdy by jejich vynecháním došlo ke splynutí tokenů, a v řetězcových konstantách. V ostatních případech jsou ignorovány. Konec řádku respektuje konvence Windows (CR LF), Mac (CR) nebo Unix (LF) a interně se převádí na znak LF. 10
Komentáře jsou považovány za bílé znaky. Povoleny jsou klasické víceřádkové komentáře jazyka C ohraničené /* a */ a jednořádkové komentáře počínající znakem # a ukončené koncem řádku, což je obvyklý druh komentářů pro unixovské skripty. Na rozdíl od jazyka C jsou víceřádkové komentáře vnořované.
3.1.3 Identifikátory Identifikátory mohou na první pozici obsahovat pouze znaky A–Z, a–z a _ (podtržítko), na jiné než první pozici se mohou vyskytovat navíc znaky 0–9, +, - a / (lomítko). Identifikátor také nesmí být shodný s některým klíčovým slovem nebo jménem vestavěného typu (na ta lze prakticky pohlížet také jako na klíčová slova).
3.1.4 Symboly Vyhrazené symboly spadají do několika kategorií: klíčová slova, jména vestavěných typů, modifikátory, symboly operátorů, závorky a oddělovače a ostatní symboly.
Klíčová slova attributes break case common config configurations continue
do else empty for-each for-follow function global
if in like local namespace not-in null
on return switch while
Jména vestavěných typů string string-list string-set target targets unknown
Modifikátory Modifikátory mají tvar identifikátorů uzavřených bezprostředně (bez mezer) mezi znaky < a >. Neznámé modifikátory nejsou chyba, budou jen s varováním ignorovány. Dosud definované modifikátory jsou:
<equal> <except>
<scanning>
Symboly operátorů Některé operátory se mohou objevit i v seznamu oddělovačů – záleží na kontextu jejich výskytu.
11
*= =* -= == !=
= ?= := += =+
$@ ^ | & !
? %
Závorky { }, &{ }, *{ }, ${ }, ( ), $( ), @( )
Oddělovače a ostatní symboly : ; . $ @ |
(dvojtečka) (středník) (tečka) (dolar) (zavináč) (svislá čára)
3.1.4 Řetězcové literály Řetězcové literály mohou být uvedeny ve dvou tvarech, které však nejsou ekvivalentní. Společné oběma zápisům jsou následující věci. • Jedním z řídících znaků uvnitř řetězce je ~ (tilda), který přeruší literál do svého dalšího výskytu; mezi oběma znaky ~ by měly být pouze bílé znaky. Využití spočívá v možnosti snadno zalamovat a odsazovat příliš dlouhé řetězce. která uvozuje znak zadaný • Dalším řídícím znakem je # (mřížka), hexadecimálním ASCII kódem ukončeným středníkem, např. #9; vyjadřuje znak horizontálního tabulátoru (doporučeno je ostatně používat v řetězcových konstantách z bílých znaků pouze mezery a jiné znaky kódovat ASCII kódem). • Znak \ (zpětné lomítko) slouží jako escape znak; znak za ním následující bude vložen do řetězce bez ohledu na svůj speciální význam. Zpětné lomítko se tak např. zapíše svým zdvojením. • Řetězec by neměl obsahovat konec řádku. Pro vložení konce řádku (jako konec řádku se počítá jednotně pro všechny platformy LF) do řetězce se užije sekvence #A; a pro zalomení řetězce kvůli jeho délce se užije ~.
Pevný literál Prvním tvarem řetězcových literálů je pevný literál, který je uzavřen do uvozovek. Mezi řídící znaky se tedy počítají kromě výše zmíněných i uvozovky a jejich výskyt v řetězci musí být escapován zpětným lomítkem. Příkladem může být: "pevný řetězec, který obsahuje \"uvozovky\" a #9; tabulátor"
12
Parametrizovaný literál (řetězcová šablona) Tento druhý tvar nepředstavuje konstantní řetězec, ale šablonu řetězcového výrazu, ve které se mohou vyskytovat výrazy jako např. volání funkce, jejíž výsledek bude na příslušné místo výrazu vložen. Šablona je uzavřena v rovných apostrofech; uvozovky speciální význam nemají. Oproti společným řídícím znakům s pevnými literály se řídícími symboly stávají navíc &{, ${ a $(. Při výskytu těchto symbolů je hledána jejich pravá párová závorka (viz dále popis syntaxe výrazů) a celý tento úsek včetně obou ohraničujících symbolů se zpracuje jako výraz jazyka; dochází tu tedy k prolnutí syntaktické a lexikální analýzy. Více o parametrizovaných literálech bude uvedeno v odstavcích věnovaných syntaxi jazyka.
3.2 Typy proměnných Jazyk umožňuje používání proměnných, které jsou typované jedním z šesti vestavěných typů. Všechny vestavěné typy se chovají velmi podobně a existují mezi nimi automatické konverze, takže lze na ně nahlížet jako na varianty jediného typu. Důvod typování je však ve zmíněných automatických konverzích, které nemusí být zcela triviální, a také ve využití deklarativní role typování – podle typu proměnné se dá očekávat její obsah. Numerické nebo logické typy jazyk neobsahuje, všechny typy jsou založené na řetězcích a lze je chápat jako seznamy řetězců, které mají následující vlastnosti. • Je důležité pořadí výskytu řetězce v seznamu, přidání řetězce na začátek seznamu poskytne odlišný výsledek oproti přidání řetězce na konec seznamu. • Seznam může buď něco obsahovat, nebo může být zcela prázdný; seznam obsahující prázdný řetězec je něco jiného než prázdný seznam. • Seznam si pamatuje, zda do něj bylo něco přiřazeno. „Nepřiřazený“ seznam (seznam do nějž nebylo přiřazeno) je prázdný, naopak to však neplatí. Typy lze rozdělit podle jednoho kritéria na typy, které obsahují jako prvky jména cílů (virtuální jména), a na typy, které obsahují libovolné řetězce. Druhým kritériem je počet prvků, které může proměnná daného typu obsahovat: maximálně jeden, libovolné množství, nebo libovolné množství s podmínkou, že všechny hodnoty jsou různé. Typy podle těchto dvou kritérií rozděluje následující tabulka. nejvýš jeden prvek libovolné množství unikátních prvků libovolné množství libovolných prvků
obsahuje řetězce
obsahuje virtuální jména
string
target
string-set
targets
string-list
není k dispozici
Konverze typů Podle vlastností jednotlivých typů je zřejmé, že konverze z typů s virtuálními jmény na odpovídající řetězcový typ je přímočará a stejně tak je přímočará konverze od typů
13
uvedených výše ve sloupcích tabulky k typům uvedeným v tabulce níže – typ stringlist lze chápat jako nejobecnější typ, na nějž se všechny ostatní mohou konvertovat bez dalších operací. Při konverzi „zdola nahoru“ mezi typy v tabulce se redukuje počet prvků. Při odstraňování duplicitních výskytu jsou odstraněny pozdější výskyty, při redukci na jediný prvek se bere jako výsledek první prvek v seznamu. Konverze řetězců na virtuální jména je spojena s doplněním řetězce o potřebná lomítka, aby výsledek vypadal jako absolutní virtuální jméno (virtuální jména jsou totiž skladována vždy ve formě absolutních cest), a výsledek je zredukován o přebytečné metasymboly . a .. používané pro aktuální a nadřazenou úroveň v hierarchii virtuálních jmen. Využití této konverze je patrné při zapojení vestavěných funkcí.
Univerzální typ Kromě zmíněných pěti typů ještě existuje typ unknown, který je spíš pseudotypem. Svůj skutečný typ totiž přizpůsobuje přiřazené hodnotě. Vhodné je použít tento typ na místě, kde není předem známo, jaký typ bude skutečně použit a je třeba zabránit konverzi. Využit je např. některými vestavěnými funkcemi.
3.3 Syntaxe jazyka Skript v jazyce EBM je vykonáván od začátku do konce jako sekvence příkazů; i na deklarace se lze dívat jako na příkazy, které mohou a nemusí být vykonány, a proto také roli preprocesoru v podmíněnosti deklarací může snadno zastoupit obyčejný podmíněný příkaz jazyka.
3.3.1 Strukturování příkazů Příkazy lze sdružovat v bloky příkazů. Blok příkazů je tvořen jednoduchými příkazy nebo jinými bloky příkazů a je ohraničen složenými závorkami. Sdružování příkazů v bloky je zpravidla vynuceno řídícími konstrukcemi jako jsou podmíněná větvení nebo smyčky. Lze je však využít i pro omezení rozsahu lokálních proměnných, jak je popsáno níže v odstavci věnovaném deklaraci lokálních proměnných.
3.3.2 Deklarační příkazy Deklarovat lze proměnné, atributy a funkce. Prostor jmen je hierarchický, hierarchii zabezpečují namespaces. Jména všech objektů musí mít tvar identifikátorů; tento požadavek nebude v následujících odstavcích již opakován.
Proměnné Proměnné jsou globální nebo lokální. Deklarace obou typů proměnných je velmi podobná a je možné ji spojit s přiřazením iniciální hodnoty proměnné pomocí výrazu (viz syntaxe). Chování lokálních a globálních proměnných je však již rozdílné.
14
Lokální proměnné local jméno-typu jméno-proměnné [ = výraz ] ;
Lokální proměnné jsou platné jen v bloku příkazů, ve kterém jsou deklarovány. V tomto bloku také jejich jméno zastiňuje shodná jména všech ostatních proměnných a zastíněné lokální proměnné nejsou žádným způsobem dostupné. Způsob zastínění jmen ukazuje následující příklad:1 # blok 0. úrovně local string my-var ; { # blok 1. úrovně local string-list my-var ; { # blok 2. úrovně # zde bude použita deklarace z bloku 1. úrovně my-var = "ABC" ; } } { # další blok 1. úrovně # zde bude použita deklarace z bloku 0. úrovně my-var = "ABC" ; }
Globální proměnné Globální proměnné mají plně kvalifikované jméno, kterým je lze zpřístupnit i v případě, že jsou zastíněny deklarací lokální proměnné. Důležitým rozdílem je, že globální proměnné existují od vykonání příkazu, který je deklaruje, a pak již nezaniknou jako proměnné lokální. Tento rozdíl nemusí být patrný zvlášť při použití lokálních proměnných deklarovaných na úrovni souboru, vně všech bloků. Takové lokální proměnné existují jen po dobu vykonávání příkazů daného souboru při jeho zpracování, ale neexistují již při volání funkcí, které v něm jsou deklarované. Globální proměnné, ačkoliv mohou být deklarované uvnitř některého bloku, existují stále – důležitý je pouze fakt, že jejich deklarace byla vykonána. Deklarační příkaz, který zkonstruuje globální proměnnou má následující syntaxi: global [] jméno-typu jméno-proměnné [ = výraz ] ;
Modifikátor je nepovinný a indikuje, že do proměnné smí být přiřazeno pouze jednou – funguje tedy jako konstanta. Přiřazení není nutné provést hned při deklaraci; je tedy možné deklarovat konstantu a naplnit ji až později.
Namespace Namespaces (prostory jmen) představují prostředek, kterým se vytváří hierarchická jména a zabraňuje se konfliktům globálních jmen, za která jsou považována jména globálních proměnných, namespaces a funkcí. Deklarace namespace má následující syntaxi: namespace jméno-namespace { [příkazy] }
Namespaces mohou být libovolně vnořované, oddělovačem hierarchie jmen je tečka. Globální namespace se jmenuje $ (dolar). Jména začínající odkazem na globální 1
V příkladech bude trošku předběhnut výklad – přiřazovací operátor = sice bude zmíněn důkladně později, ale aby bylo vůbec co do příkladů uvádět, bude v příkladech používán už nyní.
15
namespace jsou plně kvalifikovaná a jednoznačná. Ostatní jména, která nejsou plně kvalifikovaná, se doplňují způsobem obvyklým u ostatních jazyků s hierarchickým jmenným prostorem: použité jméno je vyhledáváno postupně od aktuálního namespace, ve kterém je použito, přes nadřazené namespaces až ke globálnímu. Namespace může být deklarován vícekrát. Všechny globální deklarace v něm uvedené, které se objeví v jeho jednotlivých výskytech, jsou umístěny na stejné místo v hierarchii jmen. namespace A { global string A ; # deklarace globální proměnné (viz dále) } namespace B { global string A ; namespace C { global string B ; }
B = "RST" ; # kvalifikované jméno $.B.C.B
A = "ABC" ; # kvalifikované jméno $.B.A A.A = "DEF" ; # kvalifikované jméno $.A.A # navázání na předchozí deklaraci namespace C { global string C ; } }
C.C = "XYZ" ; # kvalifikované jméno $.B.C.C
Atributy Atributy se deklarují v bloku attributes a jejich deklarace se podobá deklaraci proměnných, jak je patrné předpisu syntaxe: attributes { [[modifikátory] typ-atributu jméno-atributu ;] ... }
Typ atributu je některý z vestavěných typů. Modifikátory, které mohou být použity u deklarací atributů jsou včetně svého vysvětlení uvedeny v následující tabulce.
Atributy označené tímto modifikátorem budou ukládány do statefile a na jejich porovnání s hodnotou ve statefile bude záviset aktuálnost cíle: pokud hodnota uložená ve statefile v předchozím běhu programu je různá od hodnoty atributu při vyhodnocování aktuálnosti cíle, bude cíl považován za neaktuální. Tento modifikátor zajistí pouze uložení atributu do statefile. Při použití je již zbytečné jej uvádět. Hodnoty uložených atributů mohou být vestavěnou funkcí skriptem načteny a použity.
16
<scanning>
Cíl může mít přiřazený scanner pro vyhledávání závislostí na dalších souborech podle obsahu souboru s cílem spojeného. Pokud několik cílů závisí na stejných souborech, stačí zpravidla prohledat tyto soubory pouze jednou. Jenže scanner se také řídí atributy a některé atributy mohou mít na jeho činnost zásadní vliv (např. atributy obsahující seznam adresářů, v nichž se mají vyhledávat soubory, na kterých cíl závisí); prohledání stejného souboru s jinými atributy může vést k odlišným výsledkům. Aby se zabránilo tomuto nežádoucímu efektu, musí být atributy ovlivňující (některý) scanner označeny tímto modifikátorem – EBM pak prohledá v případě rozdílných scannerových atributů jeden soubor i vícekrát, bude-li to potřeba.
Funkce a procedury Procedura je funkce, která nemá specifikovaný návratový typ. Jazyk EBM však funkce a procedury prakticky nerozlišuje, a tak není chybou požadovat výsledek volání procedury, jako kdyby to byla funkce – procedury pro tento případ vracejí vždy nepřiřazenou proměnnou typu unknown. Proto budou v dalším textu procedury zahrnuty pod označení funkce. Funkce jsou definovány příkazem s touto syntaxí: function jméno-funkce ( [argumenty-funkce] ) [: návratový-typ] { [příkazy] }
kde argumenty-funkce mají následující tvar:
[modifikátor] typ-argumentu jméno-argumentu [: ...]
Počet argumentů funkce není omezen. Argumenty se uvnitř funkce chovají podobně jako lokální proměnné bloku obklopujícího tělo funkce, alespoň z hlediska zastiňování jmen ostatních proměnných. Platí pro ně však trochu odlišná pravidla, která se odvíjejí od použitého modifikátoru. bez modifikátoru
Argumenty bez modifikátorů jsou konstantní, nelze je uvnitř funkce měnit. Pro běžné zacházení s argumenty není toto chování nijak zvlášť omezující a EBM to dovoluje optimalizovat předávání těchto argumentů mezi funkcemi. Použití tohoto modifikátoru způsobí, že hodnotu argumentu lze měnit, a jeho hodnota při ukončení funkce může být vrácena do proměnné použité volající funkcí na místě tohoto argumentu. Jedná se o podobný případ jako u modifikátoru . Rozdíl je pouze v tom, že proměnná s modifikátorem je při vstupu do funkce nepřiřazená bez ohledu na hodnotu předanou voláním funkce. Argument slouží tedy pouze k návratu další hodnoty z funkce.
V případě modifikátorů a se momentální hodnota argumentu do volající funkce vrátit může, ale také nemusí – záleží, zda parametr byl předán pomocí reference (viz volání funkcí v pasáži věnované výrazům). Použití těchto modifikátorů také může znamenat větší režii při předávání argumentů.
17
Kromě definování funkcí je také možné již definované funkce zanést pod novým jménem třeba i do jiného namespace. Nové jméno je (téměř) rovnocenné se jménem původním a je možné na takto vzniklý alias vytvořit další alias. Alias lze zavést následujícím způsobem: function jméno-aliasu = jméno-funkce ;
Varianty konfigurace Skript by měl definovat jména variant, které může uživatel vybírat. Jiná jména variant, než která skript deklaruje jako legální, nebude moci uživatel vybrat. Deklarace se provádí následujícím příkazem: configurations [část-jména ...] [: [část-jména ...] ...] ;
Deklarace se tedy sestává ze seznamů částí jména oddělených dvojtečkami. Seznam částí jména může být i prázdný. Části jména musí splňovat podmínky kladené na identifikátor a navíc nesmí obsahovat lomítko. Správné použití je patrné z příkladů: configurations linux win32 : debug release ; configurations : : static shared ;
Dvojtečka odděluje části jména, které je možné použít společně; neoddělené jsou části jména, které představují vylučující se alternativy. Části jména se při použití spojují do jednoho celku tečkou, přičemž je nutné zachovat pořadí z deklarace (platným jménem z příkladu je např. linux.debug.shared). Vynechávky v deklaraci nepřidávají nové alternativy jména na příslušnou pozici, ale umožňují přidat další podvarianty – výše uvedený příklad lze tedy ekvivalentně zapsat jediným příkazem: configurations linux win32 : debug release : static shared ;
Rozdělení deklarací konfigurací a postupné přidávání podvariant je výhodné pro podmíněné přidávání podvariant; např. pokud by podvarianty static a shared měly být dostupné jen pro varianty win32, lze to snadno zařídit pomocí konstruktu config, který bude zmíněn později. Kombinací všech částí jména povolených v příkladu lze sestavit celkem 8 různých variant. Kromě těchto variant jsou povoleny i jejich prefixy (např. linux.release); případné zakázání konkrétních prefixů je nutné provést ve skriptu explicitní akcí, např. že skript na takovou konfiguraci zareaguje chybovou hláškou a ukončí se.
3.3.3 Práce s atributy cílů K atributům cílů je pochopitelně nutné mít přístup. První možností přístupu je používat jednotlivé atributy cílů podobně jako proměnné, kterou využijí zejména složitější skriptové konstrukce, druhou pak nastavování sady hodnot „deklarativnějším“ stylem vhodným zvlášť pro běžného uživatele.
Atributy jako proměnné Aby bylo možné se na atributy odkázat jako na proměnné, je nutné mít cíl nebo cíle, s jejichž atributy se má manipulovat, uložené v nějaké obyčejné proměnné – nejlepší je pro opakované manipulace použít proměnnou typu target nebo targets, aby se zabránilo zbytečným a drahým konverzím v každém přístupu k atributům. Nelze použít
18
v tomto případě pro specifikaci cíle řetězec s jeho jménem. Na atributy cílů uložených v proměnné se lze odkázat operátorem @ následujícím způsobem: proměnná @ jméno-atributu [@ jméno-atributu ...]
Jak je patrné, odkaz může být zřetězen; při zřetězení je vyhodnocena levá část výrazu a zkonvertována na typ targets a tento mezivýsledek je použit pro další přístup; tedy zápis např. variable@attribute1@attribute2 = "ABC" ;
je možné přepsat s pomocnou proměnnou na {
}
# operátor $( ) použije hodnotu proměnné local targets temporary = $(variable@attribute1) ; temporary@attribute2 = "ABC" ;
Vhodné je upozornit na formu přepisu, která pomocí bloku naznačuje fakt, že pomocná proměnná se použije pouze pro jeden přístup k atributům. Pokud by tedy mělo být přistupováno opakovaně přes neměnné zřetězení, je lepší explicitně užít pomocnou proměnnou, a ušetřit tak poměrně drahé konverze na jména cílů. Při přístupu k atributům více cílů jsou získané hodnoty řetězeny za sebe v pořadí odpovídajícím pořadí cílů. Pokud není žádoucí hromadný přístup, je nutné operovat pouze s jedním cílem (tj. nejlépe prostřednictvím proměnné typu target).
3.3.4 Nebooleovské výrazy a volání funkcí Gramatika jazyka rozlišuje tři druhy nebooleovských výrazů: atomické výrazy, seznam atomických výrazů a obecný výraz. obecný výraz Obecným výrazem je seznam atomických výrazů nebo volání funkce. seznam atomických výrazů Seznam atomických výrazů není nic jiného než atomické výrazy položené vedle sebe bez jakýchkoliv oddělovačů. Hodnota seznamu odpovídá zřetězení hodnot jeho prvků. atomický výraz Představuje elementární výraz; mezi atomické výrazy patří použití hodnoty proměnné, použití hodnoty obecného výrazu, řetězcová konstanta, řetězcová šablona, iterativní výraz, rozdělovací výraz a spojovací výraz.
Použití hodnoty proměnné $( proměnná )
Na místě použití proměnné je vyhodnocen její obsah a použit jako dílčí výsledek pro vyhodnocení složitějšího výrazu nebo části příkazu. Proměnná může být lokální, globální, ale i odkaz na atribut cíle pomocí proměnné.
19
Použití hodnoty obecného výrazu ${ obecný-výraz }
Představuje uzávorkování obecného výrazu do podoby výrazu atomického, a umožňuje tak jeho použití v místech, kde nemůže být použit přímo obecný výraz a je očekáván pouze atomický výraz. Typicky se toto uzávorkování používá na volání funkce, jehož výsledek má být předán jako parametr volání jiné funkce, případně se užívá pro zvýšení čitelnosti kódu.
Řetězcová konstanta Řetězcová konstanta byla zmíněna již v lexikálních pravidlech. Tvoří ji řetězcový literál uzavřený v uvozovkách. Literál má hodnotu typu string.
Řetězcová šablona Také řetězcová šablona byla již zmíněna v lexikálních pravidlech. Řetězcový literál může obsahovat vložené výrazy, které se při vyhodnocení hodnoty řetězcové šablony také vyhodnotí a jejich hodnota vloží na místo jejich výskytu. Vložený výraz může opět obsahovat další výrazy (třeba i další řetězcové šablony), které se vyhodnocují pouze v rámci vloženého výrazu. Jako vložený výraz se rozpozná výraz v ${ } (ve smyslu použití jiného výrazu i jako iterativní výraz) a výraz spojovací. Výsledek vyhodnocení řetězcové šablony je typu string. Řetězcové šablony jsou velmi silným prostředkem pro spojování řetězců. Další možností jejich spojení je spojovací výraz; pro většinu případů však postačuje plně řetězcová šablona. Příkladem jejího použití je jednoduché spojování proměnných: # spojí proměnné RootDir a SubDir a oddělí je lomítkem newDir = '$(RootDir)/$(SubDir)' ;
Iterativní výraz ${ typ-iterátoru jméno-iterátoru in obecný-výraz [| selektor-iterátoru] [; generátor-výrazu] }
Iterativní výraz představuje efektivní alternativu k cyklu for-each, která pro mnohé případy plně postačuje. První (povinná) část výrazu deklaruje proměnnou iterátoru, která je lokální a platná pro celý výraz; typ iterátoru může být string nebo target. Iterátor postupně nabývá hodnot jednotlivých prvků výrazu, který je před iterací vyhodnocen a jehož hodnota je po dobu iterace konstantní. Selektor iterátoru je booleovský výraz, v němž lze použít proměnnou iterátoru; generátor výrazu se použije pro danou hodnotu iterátoru pouze tehdy, když je pravdivá podmínka představovaná selektorem. Pokud selektor není použit, je aplikován generátor na každou hodnotu iterátoru. Generátor výrazu je tvořen obecným výrazem, který opět smí použít iterátor. Hodnoty poskytnuté generátorem jsou řetězeny za sebe a vráceny jako výsledek celého výrazu. Pokud generátor chybí, je implicitní hodnotou generátoru proměnná iterátoru.
20
Následující příklad ukazuje jednoduché použití iterativního výrazu, které každý řetězec v seznamu uzavře do uvozovek: # předpokládejme, že variable je proměnná typu string-list; # její obsah bude následujícím výrazem nahrazen variable = ${ string it in $(variable) ; '"$(it)"' } ;
Rozdělovací výraz *{ obecný-výraz [; obecný-výraz] }
Tento typ výrazu umožňuje rozdělit řetězce zadané povinným obecným výrazem v první části výrazu podle seznamu oddělovačů zadaného nepovinným obecným výrazem v druhé části výrazu. V seznamu oddělovačů se bere ohled na jejich pořadí – později uvedený oddělovač se použije pouze tehdy, pokud nelze použít některý z dříve uvedených. Pokud seznam oddělovačů není zadán, je každý řetězec rozdělen na jednotlivé znaky. Ukázka demonstruje typické a vhodné použití tohoto typu výrazu: # nechť proměnná path obsahuje systémovou proměnnou PATH na Unixu local string-list path-list = *{ $(path) ; ":" } ;
Spojovací výraz &{ obecný-výraz [; obecný-výraz] }
Spojovací výraz je opakem výrazu rozdělovacího. Prvky povinného výraz u zadaného v první části jsou spojeny řetězcem z nepovinného výrazu z druhé části (použije se tedy konverze výrazu na typ string!). Pokud chybí spojovací řetězec zadaný druhým výrazem, je použit pro spojení prázdný řetězec. Výsledek celého výrazu je typu string. Výhodné je použít tohoto typu výrazu např. při vkládání seznamů řetězců do jednoduchého řetězce předepsaného řetězcovou šablonou. Častým použitím pak bývá vytváření skriptů pro shell operačního systému: # řetězce proměnné args obalí uvozovkami (pomocí iterativního výrazu) # a následně spojovacím výrazem spojí do jednoho řetězce; výsledek # bude začínat příkazem hello a mezi příkaz a jeho argumenty i mezi # jednotlivé argumenty bude vložena po jedné mezeře local string cmd = &{ "hello" ${ string it in $(args) ; '"$(it)"' } ; " " } ;
Volání funkcí Volání funkcí může být buď samostatným příkazem (který musí být pak ukončen středníkem), nebo součástí výrazu, který použije návratovou hodnotu funkce. Funkci lze volat přímo i nepřímo. Nepřímé volání bude zmíněno na závěr této pasáže. Přímé volání se řídí následující syntaxí: jméno-funkce [argument1] [: [argument2] ...]
Argumenty jsou seznamy atomických výrazů nebo reference na proměnnou (viz dále). Počet ani typ argumentů není omezen a argumenty lze vynechávat. Chybějící argumenty jsou předány funkci jako nepřiřazené proměnné a argumenty nevyhovujícího 21
typu jsou konvertovány typ příslušného argumentu funkce. Přebytečné argumenty jsou sice vyhodnoceny (mohou totiž obsahovat volání funkcí s vedlejšími efekty), ale nejsou předány. Argumenty jsou vyhodnocovány vždy zleva doprava. Reference na proměnnou Místo celého seznamu atomických výrazů lze předat jako argument (jednu) referenci na proměnnou (proměnná musí být globální či lokální, nesmí to být odkaz na atribut cíle). Pokud je příslušný argument funkce nebo , bude při návratu z funkce do této proměnné uložen výsledek (zkonvertovaný na požadovaný typ); jestliže však argument funkce příslušným modifikátorem neoplývá, proměnná změněna nebude. Reference na proměnnou se předává pomocí operátoru @( ), jak ukazuje příklad: myFunction $(varByVal) : @(varByRef) ;
Nepřímé volání funkce Nepřímé volání funkce je analogií volání funkce přes ukazatel v jiných procedurálních jazycích. „Ukazatel“ na funkci se získá pomocí operátoru $( ), který se jinak slouží pro použití hodnoty proměnné; je-li však aplikován na jméno funkce, vrátí „ukazatel“ na ni – ve skutečnosti je „ukazatelem“ vhodně zformátované úplné jméno funkce. „Ukazatel“ na funkci lze použít k jejímu zavolání pomocí operátoru nepřímého volání $@. Syntaxe použití operátoru je následující: $@ atomický-výraz [: [argument1] [: [argument2] ...]
Oproti přímému volání funkce je tedy rozdíl pouze v předsazení operátoru nepřímého volání následovaného atomickým výrazem, který vrátí „ukazatel“ na funkci; pokud volání předává volané funkci nějaké argumenty, jsou odděleny další dvojtečkou.
3.3.5 Booleovské výrazy Jazyk sice nemá prostředky pro uložení booleovských hodnot, ale booleovské výrazy v něm existují, neboť jsou používány v řídících příkazech. Pro znázornění vztahů a priorit operátorů, které figurují v booleovských výrazech, je uvedena následující zjednodušená gramatika. booleovský-výraz : booleovský-term | booleovský-term | booleovský-výraz | booleovský-term ^ booleovský-výraz booleovský-term : booleovský-atom | booleovský-atom & booleovský-term booleovský-atom : ! booleovský-atom | ( booleovský-výraz ) | konvertovaný-výraz konvertovaný-výraz : [ % | ? ] obecný-výraz | obecný-výraz nebooleovský-binární-operátor obecný-výraz
22
nebooleovský-binární-operátor : common | in | like | not-in | == | !=
Z gramatiky je patrné, že nebooleovské výrazy jsou konvertovány na booleovské hodnoty buď přímo nebo pomocí unárních a binárních operátorů. Booleovské hodnoty již jsou spojovány dohromady pomocí běžných booleovských operátorů.
Booleovské operátory Výčet booleovských operátorů je uveden v tabulce, která také zachycuje jejich prioritu při vyhodnocování; nejvyšší prioritu mají závorky, nejnižší obyčejný a výlučný logický součet. ( ) ! & ^ |
Závorkování booleovských výrazů. Má nejvyšší prioritu, a umožňuje tak sdružovat výrazy s operátory s nižší prioritou vyhodnocení. Unární operátor negace (NOT). Logický součin (AND); používá zkrácené vyhodnocování. Výlučný logický součet (XOR). Logický součet (OR); používá zkrácené vyhodnocování.
Operátory nad nebooleovskými hodnotami % ?
common in
like not-in == !=
Vrací true, právě když výraz obsahuje právě jeden prvek. Vrací true, právě když výraz reprezentuje nepřiřazenou proměnnou. Nepřiřazenou proměnná může být reprezentována i mezivýsledkem v podobě hodnoty vrácené voláním funkce. Vrací true, právě když oba jeho argumenty mají společný aspoň jeden prvek. Vrací true, právě když všechny prvky levého argumentu jsou obsaženy v pravém argumentu. Pozor: pokud je levý argument prázdný, vrátí tedy vždy true! Vrací true, právě když pravý i levý argument obsahují tytéž prvky. To ovšem neznamená jejich rovnost (viz operátor ==). Negace operátoru in. Vrací true, právě když oba argumenty obsahují tytéž prvky ve stejném pořadí za sebou (tedy jsou si zcela rovny). Negace operátoru ==.
Při porovnávání dvou výrazů se nebere v úvahu jejich typ; hodnoty seznamu se porovnávají jako řetězce bez dalších konverzí – to má význam pro porovnávání typů target a targets. Pokud není použit pro konverzi nebooleovského výrazu na booleovskou hodnotu některý z operátorů uvedených v předchozí tabulce, je výraz konvertován přímo: výraz odpovídající prázdnému seznamu bude mít logickou hodnotu false, jinak bude jeho hodnotou true.
3.3.6 Přiřazovací operátory Přiřazení představuje samostatný příkaz, podobně jako v jazyce Pascal: 23
cílová-proměnná operátor obecný-výraz ;
Cílovou proměnnou smí být jak lokální či globální proměnná, tak i řetězený odkaz na atribut cíle pomocí proměnné. V případě, že se odkazuje na atributy více cílů naráz, je vyhodnocení výrazu přiřazeno naráz atributům všech zadaných cílů. Následující tabulka obsahuje výčet přiřazovacích operátorů. Některé operátory přiřazují hodnotu výrazu pouze podmíněně; ať již však bude hodnota přiřazena či nikoliv, je vždy vyčíslena kvůli možným vedlejším efektu funkcí, které ve výrazu mohou být volány. = := ?= += =+ *= =* -=
Přímé přiřazení hodnoty výrazu. Přiřadí pouze do prázdné proměnné. Přiřadí pouze do nepřiřazené proměnné. Na konec seznamu v cílové proměnné připojí hodnotu výrazu. Hodnotu výrazu přidá na začátek seznamu v cílové proměnné. Do cílové proměnné přiřadí prvky v průniku cílové proměnné a přiřazovaného výrazu. Zachová přitom pořadí prvků z cílové proměnné. Analogie operátoru *=, která respektuje pořadí prvků přiřazovaného výrazu. Odebere z cílové proměnné prvky obsažené v přiřazované hodnotě.
Přiřazení prázdné hodnoty Prázdnou hodnotu symbolizuje klíčové slovo empty. Jeho přiřazením bude proměnná uvedena do stejného stavu, jako kdyby byla přiřazena prázdná proměnná. Jsou povolena následující přiřazení: cílová-proměnná = empty ; cílová-proměnná ?= empty ;
Vyčištění proměnné Převést proměnnou zpět do nepřiřazeného stavu (s výjimkou konstantních proměnných) lze přiřazením klíčového slova null: cílová-proměnná = null ;
3.3.7 Konstrukce on Různým atributům skupině cílů lze přiřadit hodnoty buď pomocí obyčejného přiřazení nebo také pomocí konstruktu on. Použití tohoto konstruktu je efektivnější pro přiřazení do více různých atributů a může být i pohodlnější a lépe přehledné. Konstrukt on používá syntaxi: on obecný-výraz { [cílový-atribut přiřazovací-operátor přiřazovaná-hodnota ;] ... }
Obecný výraz uvedený v záhlaví konstrukce je vyhodnocen pouze jednou a zkonvertován na typ targets. Následně jsou aplikována přiřazení uvedená v bloku na atributy vyhodnocených cílů. Několik přiřazovacích příkazů jako např.
24
myTargets@Inputs@Outputs = null ; myTargets@Inputs = $(newInputs) ;
lze snadno přepsat pomocí konstruktu on na on $(myTargets) { Inputs@Outputs = null ; Inputs = $(newInputs) ; }
3.3.8 Přerušení vykonávání příkazů Příkazy, které mohou přerušit vykonávání sekvence příkazů a převést vykonávání skriptu skokem na jiné místo, jsou break, continue a return. Příkazy break a continue mohou být použity ve smyčkách a v příkazu switch. Jelikož se jejich význam v těchto případech mírně liší, budou zmíněny ještě u příslušné konstrukce, se kterou se používají. Na tomto místě bude uvedena pouze jejich syntaxe, která je velmi jednoduchá: break ; continue ;
Příkaz return lze použít jednak ve funkci a jednak i na globální úrovni mimo tělo libovolné funkce. Obecná syntaxe příkazu return je následující: return [obecný-výraz] ;
Pokud je příkaz return použit ve funkci, způsobí návrat z ní. Pokud funkce má specifikován návratový typ, může příkaz return zadat i návratovou hodnotu (která bude na příslušný typ zkonvertována). Jestliže návratová hodnota není specifikována, vrácena je prázdná hodnota příslušného návratového typu. Jestliže není vyvolání funkce není ukončeno příkazem return, je blok příkazů funkce opuštěn, jako kdyby byl ukončen příkazem return bez návratové hodnoty. Mimo tělo funkce způsobí příkaz return ukončení interpretace aktuálního souboru. Pokud je interpretovaným souborem primární skript (a nikoliv skript zanořený), je ukončená interpretační fáze skriptů.
3.3.9 Podmíněné větvení Příkaz if – else if – else if booleovský-výraz { [příkazy] } [else if booleovský-výraz { [příkazy] } ... ] [else { [příkazy] }]
25
Tento příkaz pro podmíněné větvení funguje stejně jako v ostatních jazycích: blok příkazů uvedený za if je vykonán, právě když je splněna booleovská podmínka stojící za příslušným příkazem if. Při jejím nesplnění je vykonán blok else (resp. přejde se k bloku else if), pokud existuje. Blok else if může být opakován vícekrát, blok else smí být použit pouze jednou a na konci celého větvení.
Příkaz switch switch [modifikátor] obecný-výraz { [case obecný-výraz { [příkazy] } | else { [příkazy] }] ... }
Příkaz switch je zamýšlen především jako náhrada za mnoho vnořovaných příkazů if, které by jinak byly použity při některých úlohách, které skripty EBM typicky řeší. V těchto případech také bude switch efektivnější než použití více příkazů if. Výraz z hlavičky příkazu switch je vyhodnocen pouze jednou. Následně jsou po řadě vyhodnocovány výrazy ve větvích case a jejich výsledek je porovnáván s výsledkem výrazu z hlavičky switch. Budou provedeny příkazy všech vyhovujících větví, a pokud žádná větev v řadě case nevyhoví, provede se následující větev else. Po provedení else větve se pokračuje v porovnávání s dalšími větvemi. Větví else může být více a mají význam „jiný případ než předcházející řada case větví“. Sémantika porovnávání výrazů je řízena použitým modifikátorem, jejichž výčet je uveden v následující tabulce ve formě booleovských podmínek pro vykonání větve. Pokud není modifikátor uveden, je použit implicitní modifikátor <equal>. <equal>
switch-výraz common else-výraz case-výraz in switch-výraz switch-výraz == else-výraz switch-výraz like else-výraz switch-výraz in else-výraz
Příkazy ve větvích příkazu switch mohou obsahovat také příkazy break a continue. Příkaz break způsobí okamžité opuštění celého příkazu switch. Příkaz continue pouze přeruší vykonávání své větve a pokračuje vyhodnocováním dalších větví, jako kdyby větev nebyla vybrána (to má následně význam pro vybírání else větví).
Konstrukce config config [modifikátor] jméno-konfigurace [jméno-konfigurace ...] { [příkazy] }
Konstrukce config slouží po snazší podporu variant. Její použití by bylo sice možné nahradit příkazy if nebo switch, ale použití config je o něco jednodušší a také deklaruje viditelně, že se týká variant konfigurace, neboť k jinému účelu tento konstrukt není možné použít. Blok příkazů uvnitř config se vykoná pouze tehdy, pokud jméno aktuálně vybrané konfigurace je ve správném vztahu ke jménům konfigurací, která jsou uvedena 26
v záhlaví bloku. Druh vztahu udává opět modifikátor; pokud modifikátor není uveden, je použit implicitní modifikátor . <equal> <except>
Některé z uvedených jmen (resp. částí jmen) musí být obsaženo ve jménu vybrané konfigurace. Jméno vybrané konfigurace musí být přesně shodné s některým z uvedených jmen. Opak <equal> – blok příkazů bude vykonán pouze v případě, že jméno vybrané konfigurace není shodné s některým z uvedených jmen.
Pochopitelně lze příkazy config vnořovat, což se snadno využije pro hierarchizaci variant a podvariant. Ukázka vnořeného použití config je demonstrována příkladem: config win32.debug linux.debug { # zde by mohly být nějaké příkazy config shared { # příkazy pro konfigurace win32.debug.shared a # linux.debug.shared } }
3.3.10 Smyčky Ve všech typech smyček, které jazyk obsahuje, lze používat příkazy break a continue. Příkaz break způsobí okamžité opuštění smyčky, příkaz continue ukončí probíhající iteraci a začne následující iteraci. V případě zanoření více smyček do sebe, se efekt příkazů break a continue vztahuje pouze na nejvnitřnější smyčku, ve které je příkaz použit.
Cyklus for-each for-each typ-iterátoru jméno-iterátoru in obecný-výraz { [příkazy] }
Tento druh cyklu je nejpoužívanější a představuje silnější variantu iterativního výrazu. Podobně jako v případě iterativního výrazu je hlavičkou příkazu definován iterátor (včetně svého typu), který nabývá během iterací postupně hodnot seznamu získaného vyhodnocením výrazu z hlavičky příkazu. V bloku příkazů se iterátor chová jako konstantní lokální proměnná. Uvnitř cyklu for-each může být také použita funkce for-follow, která vrací zbytek hodnot, které ještě iterátor nezpracoval. Možné využití je v rozdělení seznamu na dvě části, jak ukazuje příklad:
27
# tato funkce vrátí zbytek seznamu, který je oddělený od jeho počátku # hodnotou předanou v parametru key function tail ( string-list list : string key) : string-list { for-each string it in $(list) { if $(it) == $(key) { # navrácení hodnoty vrácené funkcí for-follow return for-follow ; } } }
Cykly while a do – while Oba cykly fungují stejně jako v ostatních jazycích; cyklus while testuje podmínku provedení cyklu na začátku iterace, cyklus do – while na jejím konci. Tělo cyklu do – while se tedy vykoná alespoň jednou. while booleovský-výraz { [příkazy] } do { [příkazy] } while booleovský-výraz ;
28
4. Používání programu 4.1 Inicializační soubory EBM se nastavuje pomocí textových inicializačních souborů. Inicializační soubory jsou dva – jeden s globálními nastaveními a druhý s nastaveními, která tato globální nastavení mohou doplnit nebo předefinovat. Oba soubory mají zcela shodnou syntaxi, která bude v této kapitole také popsána.
Ukázka globálního inicializačního souboru
# vestavěné konfigurační parametry DefaultInitFile = "buildfile" DefaultConfiguration = default DefaultTargets = "/all" MaxProcessCount = 2 PrimaryScriptFile = "build.ebm" StateFileDir = ".statefiles" SystemImportPaths = "rules" # argumenty pro skripty arg "toolkits" arg "hw/details" arg "hw/platform" arg "os/name" arg "os/version"
= = = = =
"msvc" "gcc" "flex" "bison" "Pentium IV" "MMX" "SSE" "SSE2" "IA32" "Windows" "Windows 2000" "SP4"
Globální inicializační soubor Tento soubor se jmenuje defaults a je hledán ve stejném adresáři, ve kterém je umístěn spustitelný soubor. Pokud není nalezen, budou pro globální nastavení použity výchozí hodnoty. Vhodné je za globální nastavení definovat informace o platformě, na které je program provozován.
Uživatelský inicializační soubor Bývá hledán v aktuálním pracovním adresáři pod jménem pod jménem zadaným v globálním inicializačním souboru (doporučeno je jméno buildfile), pokud není specifikován explicitně přepínačem na příkazové řádce. Pokud není nalezen, není to chyba – jeho absence se bere jako použití globálních nastavení bez nutnosti je 29
předefinovávat nebo doplňovat. Vhodné je však tento soubor k projektu přiložit a brát jej jako součást popisu konfigurace projektu.
Obsah inicializačních souborů řetězce Hodnoty konfiguračních parametrů bývají uváděny většinou jako řetězce nebo jako seznamy řetězců, výjimkou je jeden parametr s numerickou hodnotou. Seznam řetězců nepoužívá mezi řetězci žádný oddělovač – řetězce seznamu jednoduše následují za sebou. Řetězec je tvořen posloupností znaků uzavřenou v rovných uvozovkách. Pomocí sekvence #xy; lze do řetězce vložit znak s hexadecimálním ASCII kódem xy. Zpětné lomítko plní v řetězci roli escape symbolu, kterým lze vložit do řetězce i řídící znaky jako znak # nebo uvozovky, které slouží k ohraničení řetězce.2 komentáře Obsah komentářů je ignorován. Komentáře mohou být buď ve formě komentářů jazyka C (začíná /* a končí */), které však jsou vnořované, nebo se za komentář považuje část řádku počínaje znakem # až do konce řádku. Počátek komentáře v řetězci ztrácí svůj význam, nelze tedy vložit komentář do řetězce. vestavěné hodnoty Mají tvar přiřazení do sady předdefinovaných symbolů. Výčet povolených symbolů a jejich význam je v následující tabulce. Některé parametry jsou nastavitelné jako argument programu z příkazové řádky a pak mají přednost před hodnotami v inicializačních souborech; tyto parametry mají u sebe příslušný přepínač poznamenaný. CustomImportPaths
Obsahuje seznam cest, ve kterých mají být vyhledávány importované skripty. Tyto cesty budou prohledány před SystemImportPaths a vztahují se k adresáři s inicializačním souborem, ve kterém je tento parametr uveden. Tento parametr by měl být nastavován pouze uživatelským inicializačním souborem.
DefaultInitFile
Implicitní jméno uživatelského inicializačního souboru. Má význam pouze v globálním inicializačním souboru (viz přepínač -i) a je relativní vůči aktuálnímu pracovnímu adresáři.
DefaultConfiguration
Jméno implicitně vybrané konfigurace (viz přepínač -c). Nastavením na jméno konfigurace, které není definováno ve skriptech, lze donutit uživatele explicitně uvést jméno konfigurace na příkazovém řádku.
DefaultTargets
Seznam jmen implicitních cílů (viz parametry programu).
2
Tedy řetězce používají stejný formát jako řetězce ve skriptovacím jazyce.
30
MaxProcessCount
Maximální počet podprocesů, které mohou být spuštěny naráz (viz parametr -j). Vhodné je tuto hodnotu nastavit na počet procesorů na daném počítači, či zkusit experimentovat, která hodnota poskytuje na daném systému nejlepší výsledky. Tento parametr je určen zejména pro globální inicializační soubor.
PrimaryScriptFile
Jméno skriptu, který se má vykonat (viz přepínač -s); jméno je relativní vůči adresáři, ve kterém je hledán uživatelský inicializační soubor. Tuto hodnotu by měl uživatelský inicializační soubor také vždy obsahovat.
StateFileDir
Jméno adresáře, kam mají být ukládány statefiles. Jméno je relativní vůči očekávanému umístění uživatelského inicializačního souboru.
SystemImportPaths
Analogie CustomImportPaths. Tento parametr by měl být nastavován globálním inicializačním souborem; uživatelský inicializační soubor by jej měl uvést pouze v případě, že chce úplně potlačit výchozí systémové nastavení.
argumenty skriptu Představují pojmenované parametry, na které se mohou skripty pomocí vestavěné funkce dotazovat podobně jako na proměnné prostředí. Výhodou oproti použití proměnných prostředí je sumarizace iniciálních parametrů skriptů v konfiguračním souboru, který je spjatý s projektem, a proto je použití argumentů skriptu preferováno před proměnnými prostředí.
4.2 Argumenty programu Program používá formát přepínačů, který je již ustálený na unixovských systémech. Obecná syntaxe pro spuštění programu se skládá z přepínačů (options) následovaných výčtem cílů, které mají být aktualizovány. Příkazová řádka: ebm [option...] [target...]
Přepínače Přepínače mají zpravidla krátký i dlouhý formát a jsou zpracovávány zleva doprava. Pozdější výskyt v případě kolidujících voleb má přednost před dřívějším. Argumenty na příkazové řádce mají také přednost před volbami uvedenými v inicializačních souborech. Některé přepínače mají také argumenty; pokud přepínač má argument, je tento argument povinný pro krátký i dlouhý tvar přepínače. -b, --break Pokud dojde při vykonávání aktualizačních operací k chybě, bude aktualizace ihned ukončena. V opačném případě (implicitní volba) bude aktualizováno co nejvíce cílů.
31
-c, --configuration konfigurace Vybere zadanou variantu konfigurace. Pokud jméno konfigurace není skriptem definováno, program bude ukončen před spuštěním aktualizace – povolena jsou jen definovaná jména konfigurací. -d, --debug Dovolí zobrazovat varování a ladicí hlášky. Tato volba s dalšími volbami pro výpis informací je užitečná při ladění skriptů. -e, --executed Budou zobrazovány všechny informace o všech podprocesech spouštěných během aktualizace. -g, --graph-dump Povolí výpis grafu závislostí. Zadaný graf bude vypsán v případě zacyklení. Odvozený graf bude vypsán vždy, kdy bude kromě tohoto přepínače zadán ještě přepínač -d. -h, --help Vypíše seznam všech přepínačů se stručnou nápovědou. -i, --init-file soubor Zadá uživatelský inicializační soubor. -j, --jobs počet Nastaví maximální počet paralelně spustitelných úloh. Nula znamená, že má být spuštěno tolik úloh, kolik je možné. V případě, že zadaná hodnota bude větší, než kolik je přípustné, bude použit limit systému. -n, --no-state Nařídí EBM ignorovat informaci ve statefile; statefile bude ale zachován. -o, --omit-update Nebude vykonávat aktualizační akce zařazené do fronty; nicméně nepotlačí provádění operací, které nejsou prováděné pomocí fronty příkazů. -r, --remove-state Odebere statefile pro zadanou konfiguraci. Tato volba implikuje přepínač -n. -s, --script-file Přikáže použít jiný skript, než který je zadaný v inicializačním souboru. Tato volba není vhodná pro běžné používání a měla by být používána pouze výjimečně. -v úroveň Nastaví úroveň podrobnosti hlášení; úroveň může nabývat hodnot 0–2. Tento přepínač nemá dlouhý tvar, ale pro jednotlivé úrovně existují přepínače v dlouhém formátu začínající prefixem --verbose. --verbose-brief Implicitní hodnota přepínače -v, která odpovídá úrovni 1. Bude vypsán seznam aktualizovaných cílů a výsledky příkazů, které skončily neúspěchem.
32
--verbose-full Zobrazen bude plný výpis všech cílů a jejich stavu; odpovídá úrovni 2 přepínače -v. --verbose-none Zobrazeny nebudou žádné výpisy, případná chyba aktualizace bude pouze konstatována s omezenými přídavnými informacemi; odpovídá úrovni 0 přepínače -v. Tato volba, pokud není použita volba -e, může drobně zlepšit výkonnost EBM, neboť není potřeba uchovávat výstup spouštěných programů. --version Zobrazí verzi EBM.
Seznam cílů Přepínače následuje seznam cílů. Jména cílů nemusí být absolutní; počáteční lomítko je doplněno, pokud chybí. Nejsou-li zadána žádné cíle, jsou použita jména specifikována v inicializačních souborech.
33
5. Standardní funkce Při popisu funkcí a proměnných v této kapitole budou vždy zobrazeny jejich hlavičky tak, aby bylo zřejmé typování objektů – tyto „deklarace“ nejsou však součástí jazyka. Zároveň bude používána u všech standardních funkcí následující konvence: pokud návratová hodnota funkce má indikovat úspěch či neúspěch, je za neúspěch považováno navrácení prázdné proměnné. Tato konvence umožňuje s pomocí implicitní konverze na booleovskou hodnotu snadno neúspěch funkce testovat.
5.1 Základní funkce a proměnné Funkce a proměnné popsané v této podkapitole jsou k dispozici každému skriptu a bez nutnosti je nějak explicitně zpřístupnit k používání. Jejich jména jsou zanesená přímo do globálního namespace.
Proměnné Všechny proměnné jsou deklarovány jako global , tedy jako konstantní. Neznamená to ale, že e během interpretace vůbec nemění – deklarovány jako konstantní jsou jednoduše proto, že jsou příliš důležité, než aby je skript mohl měnit přímo. Jejich změna je možná pomocí vestavěných funkcí, které zkontrolují korektnost změny. Pro přehlednost jsou ve výčtu ponechány z deklarace pouze typy a jména. string Version string Config/Name string Config/Parts string VirtualDir
string CurrentDir string IncludeDir string RootDir
Verze EBM. Celé jméno aktuálně vybrané konfigurace. Jméno aktuálně vybrané konfigurace rozebrané na jednotlivé komponenty. Aktuální virtuální adresář; vůči této cestě jsou doplňována relativně zadaná virtuální jména, např. při konverzích řetězců na typy target a targets. Aktuální logický adresář, vůči kterému jsou doplňována relativní logická jména. Aktuální logický adresář, který používá funkce include. Kořenový adresář projektu; v tomto adresáři se má nacházet uživatelský inicializační soubor.
34
Funkce function builtin ( string package : string error ) : string
Vestavěné funkce, s výjimkou základních, jsou k dispozici v tzv. balíčcích, které musí být touto funkcí aktivovány, mají-li být dostupné. Tato funkce představuje možnost budoucího dynamického rozšiřování programu. Běžný uživatel tuto funkci nikdy nepoužije, potřebná je jen pro soubory, které definují sady standardních funkcí. Pak je třeba vestavěné symboly z nějaké skupiny funkcí zadefinovat a případně doplnit. Parametry package
Jméno balíčku, který má být aktivován. error
Způsob ošetření chyby. Může nabývat následujících hodnot: "required"
"silent" "warning"
Balíček musí být dostupný. Pokud dostupný není, interpretace bude ihned ukončena s chybou (funkce se tedy již nevrátí). Tato hodnota je implicitní. I když balíček chybí, nebude vypsáno žádné varování a interpretace bude pokračovat dále. V případě chybějícího balíčku bude pouze vypsáno varování a interpretace bude pokračovat dále.
Návratová hodnota V případě chyby je vrácena prázdná proměnná, jinak je navráceno plné jméno balíčku, který byl aktivován (nemusí být nutně shodný s argumentem package). function changeVirtualDir ( string dir ) : string function changeCurrentDir ( string dir ) : string function changeIncludeDir ( string dir ) : string
Tyto tři funkce jsou podobné: mění nastavení aktuálního „adresáře“ pro virtuální, logické a „includovací“ cesty; aktuální hodnoty jsou ve výše zmíněných proměnných. Parametry dir
Nové nastavení cesty; cesta musí být v logickém formátu, a pokud není absolutní, je vztažena k aktuální hodnotě. Návratová hodnota Navrácena je původní hodnota nastavení, aby bylo možné snadno nastavení cesty opět vrátit při nějaké lokální změně.
35
function exit ( )
Funkce ukončí okamžitě interpretaci skriptů, tedy funkce se už nevrací. function import ( string file : string error ) : string
Funkce importuje skript z jiného souboru. Pokud je skript nalezen a zpracován bez syntaktických chyb, je spuštěna jeho interpretace. Po skončení interpretace importovaného skriptu pokračuje původní skript ve vykonávání dalších příkazů. Importování skriptu tedy funguje jako volání funkce, která může doplnit např. další globální deklarace, změnit stav cílů nebo vytvořit nové cíle. Každý skript lze importovat pouze jednou, opakovaný pokus o import je bez efektu. Při importování se prohledávají pouze adresáře zadané inicializačními soubory a nejsou měněna nastavení žádných cest (viz proměnné VirtualDir apod.). Funkce by měla sloužit především k importování předdefinovaných pravidel. Parametry file
Jméno souboru, který se má importovat. Jméno by mělo být pouze relativní. error
Tento argument má stejné chování jako u funkce builtin. Návratová hodnota V případě úspěchu je navráceno je absolutní logické jméno importovaného souboru; neúspěch je indikován navrácením prázdné hodnoty. function include ( string file string virtualDir string currentDir string error ) : string
: : :
Obdoba funkce import, která však má podstatně rozšířené chování a je určena k importování skriptů popisujících podprojekty či relativně samostatné části větších projektů. Místo vyhledávacích cest používaných funkcí import, hledá funkce include soubor pro import pouze v adresáři zadaného proměnnou IncludeDir. V případě úspěšného importování skriptu je po dobu jeho interpretace proměnná IncludeDir změněna na adresář importovaného souboru. Parametry file, error
Tyto parametry mají stejný význam a chování jako u funkce import.
36
virtualDir, currentDir
Nastavením těchto proměnných lze změnit po dobu běhu importovaného skriptu proměnné VirtualDir a CurrentDir podobným způsobem, jakým je změněna proměnná IncludeDir. Tímto lze snadno vnořovat části projektu a podprojekty, které jsou v různých adresářích, a zapojovat jména jejich cílů na vhodná místa do hierarchie virtuálních jmen, pokud respektují pravidlo, že by neměla být používána absolutní jména, pokud je to možné. Návratová hodnota V případě úspěchu je navráceno je absolutní logické jméno importovaného souboru; neúspěch je indikován navrácením prázdné hodnoty. function queryArgument ( string argument ) : string-list
Funkce vrátí hodnotu argumentu pro skript, který je zadán inicializačními soubory. Parametry argument
Jméno argumentu (což může být libovolný řetězec), jehož hodnota má být vrácena. Návratová hodnota Navrácen je seznam řetězců, které byly nastaveny jako argument. Není vyhrazena hodnota pro nedefinované argumenty – v případě, že argument nebyl definován, je vrácen prázdný seznam, což ale může být legální hodnota i pro definované argumenty.
5.2 Vestavěné atributy Před výčtem jednotlivých funkcí a proměnných je nutné uvést výčet všech vestavěných atributů, neboť s jejich existencí mnohé ze standardních funkcí počítají a opírají o ně svou funkčnost. Všechny deklarace v této podkapitole budiž považovány za uzavřené do bloku attributes. string Type ;
Typ cíle, podle něhož se řídí použití implicitního odvozovacího pravidla; tento atribut bere na vědomí i řada standardních funkcí a předdefinovaných pravidel. Dle konvence se typ cíle uvádí ve formátu, který připomíná MIME (např. "source/C"). target VirtualPath ;
Virtuální cesta (virtuální jméno) cíle. Tento atribut je pouze pro čtení, a i když to není explicitně deklarováno, je inherentně persistentní.
37
string Name ; string-list Dirs ;
Tyto dva atributy obsahují logickou cestu k souboru, který je spjat s cílem. Pokud jeden z těchto dvou atributů je prázdný, je cíl považován pouze za virtuální. Atribut Name obsahuje jméno souboru (resp. část, která je připojena k adresáři), Dirs obsahuje seznam adresářů, ve kterých se soubor má vyhledávat – při vyhledávání je respektováno pořadí adresářů v seznamu. Adresáře by měly být uváděny absolutní, ale není to podmínkou, jen velmi vhodným doporučením. Vzhledem k tomu, že o vytvoření absolutních cest vzhledem k jiným nastavením se mohou postarat snadno odvozovací pravidla, případně jsou cesty nastaveny automaticky, nepředstavuje toto doporučení zásadní problém. string Binding ;
Atribut ovlivňuje použití atributu Dirs při vyhledávání souboru spjatého s cílem. Může nabývat následujících hodnot: "default" "forbidden" "expendable" "prefer first" "prefer last"
Implicitní vyhledávací strategie: použije pouze první adresář v seznamu, aniž by se testovalo, zda soubor vůbec existuje. Zakáže svázání cíle a souboru i v případě, že jsou nastaveny oba atributy Name i Dirs – tedy vynutí, aby cíl byl vždy virtuální. Pokud se nepovede najít soubor prohledáním všech adresářů v seznamu, bude cíl prohlášen za virtuální. Pokud se nepovede soubor najít prohledáním všech adresářů, bude použit pro vazbu jména první adresář v seznamu. Totéž jako "prefer first", jen s rozdílem, že použit bude případně poslední adresář v seznamu.
string LogicalPath ;
Obsahuje logickou cestu k souboru, se kterým je cíl svázán. Tento atribut je pouze pro čtení, a jakmile je jednou nastaven, nemůže již být změněn. <scanning> string NativePath ;
Fyzická podoba atributu LogicalPath; také je pouze pro čtení a nelze po nastavení již změnit. string Timestamp ;
Čas poslední změny souboru ve tvaru RRRR/MM/DD HH:MM:SS,sss. Tento atribut je nastaven po aktualizační akci, u virtuálních cílů zůstává prázdný. Nelze jej měnit. Ačkoliv není tak deklarován, je prakticky tento atribut a je ukládán do statefile.
38
targets Inputs ;
Vstupní cíle pro aktualizační akci daného cíle. Společně s atributy Outputs a WaitFor slouží k sestavení grafu závislostí. targets Outputs ;
Výstupní cíle pro aktualizační akci daného cíle. targets WaitFor ;
Aktualizační akce daného cíle musí počkat na dokončení aktualizace cílů, které jsou uvedeny v tomto atributu. targets DependsOn ;
Daný cíl závisí na aktuálnosti času poslední změny souborů spjatých s cíli, které jsou uvedené v tomto atributu. Aktuálnost času poslední změny se posuzuje vůči statefile. Obsah atributu je interpretován striktně a to podle následujících dvou pravidel: • Pokud atribut obsahuje pouze prázdný řetězec (to není totožné s tím, že je atribut prázdný!) a cíl je svázán s nějakým souborem, bude cíl považován za neaktuální vždy. • Cíl jinak ovlivněn pouze cíli uvedenými v tomto atributu – má-li být ovlivněna jeho aktuálnost časem poslední změny jeho vlastního souboru, musí být také uveden mezi hodnotami v atributu. Naopak pokud v atributu uveden není, pak není čas poslední změny jeho souboru přímo podstatný pro jeho aktuálnost. string Derivation ;
Atribut má obsahovat „ukazatel“ na funkci, která se postará o zpracování cíle během odvozování sekundárního grafu závislostí. Pokud není atribut nastaven, je použita funkce implicitní pro typ cíle (tj. odvodí se z atributu Type). Funkce by měla mít prototyp (argument tgt představuje cíl, který se má funkcí zpracovat): function ( target tgt )
string Action ;
Atribut obsahuje „ukazatel“ na funkci, která je spuštěna na cíl během aktualizace cílů, je-li zjištěno, že daný cíl není aktuální. Funkce by měla mít prototyp: function ( target tgt ) : string
Argument tgt představuje cíl, který se má funkcí zpracovat. Návratová hodnota aktualizační funkce by měla být jednou z hodnot:
39
"update" "fail" "checked"
Aktualizační akce, které jsou funkcí připraveny ve frontě, mají být vykonány. Tato hodnota je implicitní. Všechny aktualizační akce se mají stornovat, funkce vyhodnotila dopředu pokus o aktualizaci za neúspěšný. Aktualizační akce se mají stornovat, protože cíl byl vyhodnocen za stále ještě aktuální.
<scanning> string Scanner ;
Soubor cíle má být funkcí zadanou tímto atributem prohledán na výskyt závislostí na dalších souborech. Zadaná funkce má mít následující prototyp: function ( target tgt ) : targets
Argument tgt představuje cíl, který se má funkcí zpracovat. Předpokládá se, že funkce vytvoří pro nalezené soubory pomocné dočasné cíle, kterým nastaví potřebné atributy, a tyto cíle vrátí jako svůj výsledek. Je vhodné, když funkce vrátí pouze cíle, na nichž bezprostředně zkoumaný cíl závisí. Mají-li tyto cíle nastavený atribut Scanner a budou-li k nim nalezeny soubory, budou EBM automaticky tyto cíle podrobeny rekurzivnímu zkoumání, čímž lze postavit snadno celý graf závislostí. Části grafu závislostí, které se jeví jako shodné, mohou být sloučeny a tím ušetřeno zbytečné zkoumání závislostí. Výsledky scanningu jsou ukládány do statefile a automaticky z něj natahovány a používány tak, aby se minimalizoval počet scanů a dotazů na souborový systém.
5.3 Standardní funkce a proměnné Standardní funkce a proměnné nemusí být již nutně vestavěné; některé jednoduché funkce, které tu budou zmíněny, jsou implementovány pomocí skriptu. Všechny symboly z následujícího výčtu jsou však definovány použitím příkazu import "base.ebm" ;
5.3.1 Namespace Action Funkce definované v tomto namespace fungují pouze během provádění aktualizační akce. Jejich vyvolání mimo aktualizační akce skončí jejich selháním. function queueCommand ( string-list command : string-list stdin )
Funkce zařadí do fronty aktualizačních příkazů momentálně zpracovávaného cíle (viz atribut Action) zadaný příkaz. Fungování je podobné jako funkce System.execute.
40
Parametry command
Příkaz, který se má vykonat. Prvním řetězcem seznamu je příkaz, dalšími pak po řadě jeho argumenty. stdin
Standardní vstup, který má být příkazu přesměrován. Každý řetězec v seznamu představuje jeden řádek vstupu. function queueScript ( string-list commands : string-list stdin )
Analogie funkce queueCommand, která však místo jednotlivého příkazu zařadí do fronty akcí celý shellový skript. Funkce má společné rysy též s funkcí System.shell – podrobnosti tedy tamtéž. function queryWorkingDir ( ) : string
Funkce vrací logické jméno pracovního adresáře, ve kterém proběhne aktualizační akce právě zpracovávaného cíle. Aktualizační akce každého cíle probíhají v dočasném adresáři, který je po aktualizaci smazán s celým svým obsahem včetně podadresářů. Každý cíl má pro svou aktualizaci vyhrazen svůj vlastní adresář, tedy nehrozí nebezpečí, že by se aktualizační procesy při současné aktualizaci více cílů navzájem ovlivňovaly prostřednictvím pracovních adresářů. Funkce, která aktualizační akce připravuje, může do tohoto adresáře připravit i další soubory, které mohou být potřeba, či jej využít pro vlastní pomocné soubory. Protože adresář bude po skončení aktualizace smazán, není třeba se explicitně starat o likvidaci případných pomocných souborů. Návratová hodnota Navráceno je logické jméno pracovního adresáře. Adresář po zavolání této funkce bude existovat a může být použit.
5.3.2 Namespace Core Namespace Core obsahuje klíčové funkce pro podporu aktualizačního algoritmu EBM. function addDependencies ( targets src : targets dest : string mode )
Funkce přidává závislosti mezi cíle, které již byly zpracovány odvozovacím pravidlem; na dosud nezpracované cíle nemá vliv. Závislosti přidané touto funkcí nejsou uloženy v žádném atributu, ale přímo ve vnitřních strukturách aktualizačního algoritmu. Použití této funkce může být vynuceno některými speciálními případy, lze jí ošetřit i případ komplikovaných implicitních závislostí mezi cíli, které nedovede vyřešit zabudovaný scanovací algoritmus.
41
Při používání je nutné dávat pozor na vznik cyklických závislostí, zvlášť v kombinaci se závislostmi pocházejícími z atributů Inputs, Outputs a WaitFor. Graf musí zůstat acyklický! Parametry src
Cíle, které mají sloužit jako zdroje závislostí. dest
Cíle, které jsou závislé na cílích z argumentu src. mode
Druh závislosti, který bude mezi cíle přidán. Parametr může nabývat těchto hodnot: "default"
"wait-for"
Odpovídá závislosti indukované atributy Inputs a Outputs – cíle dest budou závislé na aktuálnosti cílů src a budou ovlivněny jejich aktualizací. Tato hodnota je implicitní. Vytvoří synchronizační závislost, která odpovídá použití atributu WaitFor – aktualizace cílů dest musí proběhnout až po aktualizaci cílů src, ale cíle dest nejsou aktualizací src přímo ovlivněny.
function setupType ( string typeName : string detection )
Funkce zadefinuje typ cíle (viz atribut Type) a funkci, která jej umí detekovat; pokud cíl nemá nastaven typ před tím, než je vybráno a aplikováno na něj odvozovací pravidlo, je typ prvně detekován. Detekce probíhá tak, že se projde seznam zadaných detekčních funkcí, a typ cíle bude nastaven na typ, který je svázán s první detekční funkcí, která uspěje. Parametry typeName
Jméno typu cíle, které detekční funkce rozpoznává. detection
„Ukazatel“ na detekční funkci; může být zadaná i prázdná hodnota – ta zruší předchozí nastavení detekční funkce a ponechá typ bez detekce. Detekční funkce by měla mít prototyp: function ( target tgt ) : string
Argument tgt je cíl, jehož typ se má určit, návratová hodnota pak indikuje úspěch či neúspěch detekce. Neúspěch je indikován vrácením prázdné hodnoty, úspěch pak vrácením hodnoty neprázdné (doporučeno je vrátit řetězec "true"). Za povšimnutí stojí, že funkce samotná nemá informaci o tom, který typ detekuje – o tom rozhoduje svázání funkce s typem pomocí setupType, nikoliv funkce samotná. function queryType ( string typeName ) : string
Vrátí funkci, která detekuje zadaný typ.
42
Parametry typeName
Jméno typu, pro nějž má být vrácena detekční funkce. Návratová hodnota Vrácen je „ukazatel“ na funkci, která byla pro zadaný typ nastavena pomocí funkce setupType. Pokud taková funkce není, je vrácena prázdná hodnota.
function setupDerivation ( string-list typeNames : string derivation )
Funkce nastaví implicitní odvozovací funkci (viz také atribut Derivation) pro cíle se zadaným typem. Pokud cíl, který má být zpracován během odvozování sekundárního grafu, nemá explicitně zadané odvozovací pravidlo, je podle jeho typu (který může být prvně detekován) vybráno implicitní odvozovací pravidlo. Parametry typeNames
Jména typů, pro které má být odvozovací pravidlo nastaveno. derivation
„Ukazatel“ na funkci, která má sloužit jako odvozovací pravidlo; pokud je zadána prázdná hodnota, je implicitní pravidlo pro zadané typy zrušeno. Odvozovací funkce by měla mít prototyp: function ( target tgt )
function queryDerivation ( string typeName ) : string
Analogie funkce queryType; tato vrací odvozovací pravidlo pro zadaný typ (viz funkce setupDerivation). function setupDefaultDerivation ( string derivation )
Nastaví globální implicitní odvozovací pravidlo. Toto pravidlo je použito na cíl tehdy, pokud cíl nemá asociováno žádné odvozovací pravidlo, ani se je nepovede vydedukovat z jeho typu. Typickou akcí globálního pravidla by mělo být (aspoň při ladění skriptů) upozornění uživatele na fakt, že cíl není nijak zpracován, což může být opomenutí nebo chyba pravidel. Parametry derivation
„Ukazatel“ na globální implicitní odvozovací pravidlo; pokud je zadána neplatná hodnota, nebude žádné pravidlo nastaveno. function queryDefaultDerivation ( ) : string
Analogie funkce queryDerivation; vrací globální implicitní odvozovací funkci. 43
function queryDerived ( targets inputs : string-set types ) : targets
Projde sekundární graf závislostí (tj. pouze cíle, které již byly zpracovány odvozovacími pravidly) a vrátí všechny cíle, které jsou přímo závislé (atributy Inputs a Outputs) na zadaných vstupech. Vstupy lze omezit typovým filtrem. Tato funkce je klíčová pro odvozovací pravidla, pokud nastává rozšiřování grafu závislostí a je nutno zaměnit jednoduché vstupy z primárního grafu za výsledky jejich odvození. Parametry inputs
Cíle, které mají představovat pro prohledání výchozí uzly grafu. Těmito cíli počínaje, je graf procházen ve směru Inputs a Outputs závislostí. Do výsledku mohou být přidány kromě navštívených cílů i cíle z tohoto parametru. types
Jména typů, které mohou být přidány do výsledku. Pokud je argument prázdný, jsou přidány všechny navštívené uzly (včetně obsahu parametru inputs). Návratová hodnota Vrácen je seznam cílů vyhovujících typové podmínce. Seznam může být prázdný. Při zadání vstupů, které nebyly dosud zpracovány odvozovacími pravidly, mohou být do výstupu přidány i tyto vstupy, ale ne již žádné cíle na nich závislé. function wait ( targets tgts : targets awaited )
Funkce přidá cílům tgts do atributu WaitFor cíle awaited. Funkce není vestavěná; je definována pouze skriptem a její definice je následující: function wait ( targets tgts : targets awaited ) { tgts@WaitFor += $(awaited) ; }
function function function function
type = Core.setupType ; queryDerived = Core.queryDerived ; derivation = Core.setupDerivation ; wait = Core.wait ;
Tyto aliasy jsou definovány skriptem v globálním namespace.
5.3.3 Namespace File Následující funkce slouží k jednoduchým manipulacím s textovými soubory. Konce řádků jsou rozpoznávány automaticky stejným způsobem, jakým jsou rozpoznávány ve skriptech. Jména všech souborů musí být zadána v logickém tvaru a musí být absolutní (aby nebylo spoléháno na aktuální adresáře).
44
function load ( string path : string error ) : string-list
Načte obsah souboru. Parametry path
Absolutní jméno souboru v logickém tvaru. error
V případě chyby je vrácena chybová hláška v tomto argumentu. Pokud chyba nenastala, je vrácena prázdná hodnota. Návratová hodnota Vrácen je obsah souboru. Každý řetězec je jeden řádek souboru. Pokud soubor nebyl nalezen či jej nebylo možné přečíst, je vrácen prázdný seznam. Případná chyba je indikována argumentem error, nikoliv návratovou hodnotou. function save ( string path : string-list content : string semantics ) : string ;
Funkce uloží proměnnou do souboru. Parametry path
Absolutní jméno souboru v logickém tvaru. content
Proměnná, jejíž obsah má být do souboru uložen. Každý řetězec představuje jednu řádku souboru a bude ukončen koncem řádku s výjimkou posledního řetězce v seznamu (pokud má být posledním znakem v souboru konec řádku, je nutné připojit ještě prázdný řetězec). Konce řádků respektují platformu, na které EBM běží. semantics
Způsob uložení proměnné do souboru. Může nabývat následujících hodnot: "append"
"create" "overwrite"
Zadaná proměnná bude připojena na konec souboru; existující soubor tedy bude o zadaná data prodloužen. Neexistující soubor bude vytvořen. Toto je implicitní hodnota. Vytvoří nový soubor a zapíše do něj; existující soubor však nepřepíše a vrátí chybu. Pokud soubor neexistuje, je vytvořen. Pokud existuje, je před zápisem dat zkrácen na nulovou délku.
Návratová hodnota Vrácen je popis chyby, která nastala. Prázdná návratová hodnota indikuje úspěch operace.
45
function scan ( string path string regexp string subst ) : string-list ;
: :
Funkce přečte zadaný soubor a vybere z něj podřetězce zadané regulárním výrazem a šablonou pro substituci. Funkce představuje zabudovanou zjednodušenou alternativu k programu sed, jen nepodporuje globální substituce. Parametry path
Absolutní jméno souboru v logickém tvaru. regexp
Regulární výraz, podle kterého se mají vybrat řetězce. Regulární výraz je aplikován na každý řádek zpracovávaného souboru. Podrobnosti k regulárním výrazům viz funkce Strings.match. subst
Šablona předepisující tvar výsledných řetězců složených z nalezených podřetězců. Viz funkce Strings.subst. Návratová hodnota Vrácen je seznam řetězců sestavených podle šablony subst z řetězců vybraných regulárním výrazem. Chyba není nijak indikována.
5.3.4 Namespace FileSystem Repertoár funkcí pro manipulaci se souborovým systémem je zatím značně omezený, ale pro většinu požadovaných úkonů stačí. Jako v ostatních případech pracuje pouze s logickými cestami; zadané cesty musí být absolutní, aby nebylo spoléháno na nastavení aktuálních adresářů. function createDirs ( string-set dirs ) : string-set
Vytvoří zadané adresáře a všechny adresáře, které je nutné na zadané cestě vytvořit. Parametry dirs
Seznam cest k adresářům, které mají být vytvořeny. Návratová hodnota Je vrácen seznam cest, které stále ještě neexistují.
46
function deleteFiles ( string-set paths ) : string-set function deleteDirs ( string-set paths ) : string-set
Funkce deleteFiles smaže zadané soubory (maže pouze soubory, nikoliv adresáře!), funkce deleteDirs maže adresáře včetně veškerého jejich obsahu (ale nemaže jednotlivé soubory!). Parametry paths
Cesta k objektům, které mají být smazány. Návratová hodnota Vrácen je seznam cest, které nebylo možné smazat. function fileExists ( string path ) : string
Otestuje existenci souboru; funkce může uspět pouze na regulérních souborech. Použití speciální soubory (jako např. zařízení, pipes apod.) nebo adresáře vrátí neúspěch. Parametry path
Cesta k souboru, jehož existence má být otestována. Návratová hodnota Pokud funkce selže nebo soubor neexistuje, vrací prázdnou hodnotu. Jinak vrací řetězec "true".
5.3.5 Namespace Paths Tento namespace poskytuje rutiny pro manipulaci s logickými cestami, a protože virtuální cesty se řídí stejnými syntaktickými pravidly, lze je použít i na cesty virtuální. function concat ( string-list pathParts ) : string
Spojí komponenty cesty dohromady a postará se o oddělovače mezi nimi. Parametry pathParts
Seznam komponent cesty. Návratová hodnota Vrácena je cesta vzniklá spojením komponent pomocí oddělujících lomítek; vrácená cesta není normalizovaná (tj. může stále obsahovat metaznaky jako . a ..).
47
function split ( string path ) : string-list
Funkce je inverzní k funkci concat – rozdělí zadanou cestu na komponenty. Parametry path
Cesta, která má být rozdělena na komponenty. Cesta nemusí být v normalizovaném tvaru; vrácené komponenty také pak nebudou nijak normalizovány. Návratová hodnota Vráceny jsou v seznamu komponenty cesty. function join ( string basePath : string-list relPaths ) : string
Funkce sloučí dvě cesty dohromady; jedna cesta slouží jako základ pro cestu druhou, která k ní bude vztažena. Vrácený výsledek bude v každém případě normalizovaný. Parametry basePath
Tato cesta bude brána jako základ, vůči kterému bude vztažena cesta relPaths. Argument může být prázdný. relPaths
Seznam cest, které mají být vztaženy k basePath; vztaženy jsou pouze relativní cesty, absolutní nelze vztahovat a jsou ponechány. Návratová hodnota Výsledkem je vždy seznam normalizovaných cest obsahující zpracování všech relPaths se zachováním jejich pořadí. Při použití prázdného argumentu basePath lze funkci použít k normalizaci cest. function normalize ( string-list paths ) : string-list
Funkce vrátí seznam cest v normalizované podobě. Definována je skriptem pomocí funkce join. function replaceSuffix ( string-list paths string-list suffixes ) : string-list
:
Funkce zamění sufix zadaných cest; sufixem se myslí část cesty od poslední tečky v poslední komponentě cesty až do konce cesty, tečka je součástí sufixu. Funkce nahrazuje i prázdné sufixy, tj. cesty bez sufixu mohou být prodlouženy o nový sufix. Dalším využitím funkce je získání seznamu „base names“ – viz též funkce replaceBaseName.
48
Parametry paths
Cesty, které mají být zpracovány. suffixes
Seznam sufixů, které se mají použít. Sufixy jsou používány po řadě na řetězce paths. Pokud je sufixů méně než cest, je poslední zadaná sufix použit na zbývající cesty. Pokud je seznam sufixů prázdný, jsou ze všech zadaných cest sufixy odstraněny. Návratová hodnota Vrácen je seznam opracovaných cest; seznam zachovává pořadí původních cest. function replaceBaseName ( string-list paths string-list baseNames ) : string-list
:
Funkce je analogická k funkci replaceSuffix, pouze místo sufixů nahrazuje zbytek jména (tzv. base name); představuje tedy komplement k funkci replaceSuffix. Funkci lze také použít k získání seznamu sufixů. function replaceName ( string-list paths string-list names ) : string-list
:
Opět analogie k předchozím funkcím, zejména k funkci replaceBaseName. Tato funkce nahrazuje pouze jméno (část cesty od posledního lomítka do konce cesty; pokud lomítko není obsaženo, je jménem celá cesta). Návratová hodnota Návratová hodnota je podobná jako u analogických funkcí; za zmínku však stojí, že při použití funkce pro získání cest (names se zadá jako prázdný seznam) budou výsledné cesty ukončeny lomítkem, pokud nebyly celé jménem. function replaceDir ( string-list paths string-list dirs ) : string-list
:
Poslední z řady analogických funkcí – zaměňuje adresářovou část cesty (tj. doplněk jména; do adresářové části se však nepočítá případné lomítko oddělující jméno a adresáře).
49
function function function function
getSuffix getBaseName getName getDir
( ( ( (
string-list string-list string-list string-list
paths paths paths paths
) ) ) )
: : : :
string-list string-list string-list string-list
Uvedené funkce vracejí jednotlivé části cesty: sufix, base name, jméno a adresář. Vrácen je vždy seznam respektující pořadí vstupního seznamu. function makeName ( string-list baseNames string-list suffixes ) : string-list
:
Spojí dohromady base name a sufix. Parametry baseNames
Seznam jmen, ke kterým mají být připojeny sufixy. suffixes
Seznam sufixů, které mají být připojeny ke jménům. Použito je opět párování jako v případě replace- funkcí. Pokud je některý sufix neprázdný, ale nezačíná tečkou, je tato oddělující tečka do výsledného jména doplněna; funkce tedy neprovádí pouhé spojení dvou řetězců. function uniqueName ( ) : string
Navrátí unikátní jméno souboru. Toto jméno lze použít např. pro vytváření dočasných souborů apod. Unikátnost jména je zabezpečena čítačem; jména jsou vytvářena tedy sekvenčně a za běhu programu nebudou vytvořena dvě stejná jména. V rámci systému je unikátnost zajištěna použitím PID ve jméně souboru. function toNative ( string-list logical ) : string-list function toLogical ( string-list native ) : string-list
Konverzní rutiny pro převod cesty mezi logickým a fyzickým (nativním) formátem. global string NativeDirSep ;
Tato konstanta obsahuje oddělovač v nativním formátu cest.
5.3.6 Namespace Strings Pro manipulaci s textem slouží rutiny z tohoto namespace. Speciální rutiny pro spojování a rozdělování řetězců nejsou, neboť jsou zabudovány v jazyce v podobě operátorů.
50
function compare ( string left : string right : string sign ) : string
Funkce provede lexikografické porovnání dvou řetězců nebo test na požadovanou nerovnost. Parametry left, right
Řetězce, které mají být porovnány. sign
Pokud je tento parametr prázdný, vrací funkce znaménko, které splňuje nerovnost mezi zadanými řetězci. V opačném případě je provedeno testování na nerovnost zadanou tímto parametrem; parametr může nabývat hodnot: "==", "!=", "<", "<=", ">", ">=". Návratová hodnota V případě otestování (je zadáno znaménko sign) je vrácena prázdná hodnota pro nesplněnou nerovnost a "true" pro splněnou nerovnost. Při porovnání je vráceno znaménko splňující nerovnost, tedy je vráceno "==", "<" nebo ">". function match ( string-list strings : string regexp ) : string-list
Funkce vybere ze zadaného seznamu řetězce, které vyhovují regulárnímu výrazu. Parametry strings
Seznam řetězců, které mají být protříděny. regexp
Regulární výraz, který se na řetězce aplikuje. Regulární výraz používá notace obvyklé na unixovských systémech, např. v programu sed; povolené operátory jsou ., *, +, [ ] (množina, lze užít ^ obvyklým způsobem pro její negaci), \( \), \< \>, $, ^ a \n (kde n je číslo 1–9 pro předchozí podvýraz uzavřený v \( \)). Při zápisu regulárních výrazů je nutné dbát opatrnosti při escapování symbolů, zvlášť zpětných lomítek! Dále je nutné dát pozor na nečekané výsledky, pokud se nepoužijí operátory ^ nebo $, které přidají výrazu vztah k začátku, resp. konci řetězce. Návratová hodnota Navráceny jsou ty řetězce ze seznamu, které vyhovují zadanému výrazu. function mismatch ( string-list strings : string regexp ) : string-list
Funkce je duální k funkci match – vrátí řetězce, které byly funkcí match odmítnuty.
51
function divide ( string-list strings string regexp string-list match string-list mismatch )
: : :
Kombinuje funkce match a mismatch do jednoho volání a rozdělí řetězce na vyhovující a nevyhovující. Parametry match
Vrácen je seznam vyhovujících řetězců. mismatch
Vrácen je seznam nevyhovujících řetězců. function subst ( string-list strings string regexp string generator ) : string-list ;
: :
Funkce vytvoří řetězce podle vzoru zadaného regulárním výrazem. Parametry strings
Řetězce, které se mají zpracovat; řetězce jsou zpracovávány postupně a na každý je aplikován regulární výraz a příslušná substituce. regexp
Regulární výraz (viz též funkce match). Pokud řetězec nevyhovuje výrazu, nebude s ním nic podniknuto, nedostane se tedy žádnou formou ani do výsledku. generator
Pokud řetězec výrazu vyhovuje, bude přidáno do výsledku jeho zpracování podle šablony zadané tímto parametrem. Šablona je libovolný řetězec, který může obsahovat operátory \1 až \9 pro hodnoty odpovídající \( \) podvýrazům regulárního výrazu a znak & pro celý řetězec, který se shoduje s regulárním výrazem. Pozor při zadávání šablony (stejně jako regulárního výrazu) na fakt, že zpětné lomítko je escape znakem v řetězcích! Návratová hodnota Vrácen je seznam opracovaných vstupů, které vyhovují regulárnímu výrazu.
52
function function function function function
compare = Strings.compare ; match = Strings.match ; subst = Strings.subst ; divide = Strings.divide ; mismatch = Strings.mismatch ;
Tyto aliasy jsou deklarovány skriptem v globálním namespace.
5.3.7 Namespace System Pro interakci s operačním systémem a jeho prostředím slouží následující funkce a proměnné. global string OS/Name ;
Jméno hostitelského operačního systému. Tato proměnná je nastavována skriptem podle hodnoty argumentu "os/name". Příkladem možných hodnot jsou hodnoty "Windows" nebo "Linux". global string-set OS/Version ;
Detaily o hostitelském operačním systému. Tato proměnná je nastavována skriptem podle hodnoty argumentu "os/version". Příkladem možných hodnot jsou hodnoty "Windows NT" nebo "RedHat". global string HW/Platform ;
Identifikuje hostitelskou hardwarovou platformu. Tato proměnná je nastavována skriptem podle hodnoty argumentu "hw/platform". Příkladem možných hodnot jsou hodnoty "IA32" nebo "IA64". global string-set HW/Details ;
Detaily o hostitelské hardwarové platformě. Tato proměnná je nastavována skriptem podle hodnoty argumentu "hw/details ". Příkladem možné hodnoty je seznam "SSE" "SSE2" "MMX". function peek ( string varName ) : string
Funkce přečte hodnot proměnné prostředí. Vzhledem ke specifičnosti proměnných prostředí, je doporučováno se této funkci vyhýbat a používat ji nejvýše k účelům jako např. detekce platformy či přesné verze překladače. Parametry varName
Jméno proměnné, která má být načtena. 53
Návratová hodnota Pokud proměnná není definována, je vrácen prázdný řetězec; pokud naopak je nalezena, je vrácena její hodnota bez jakéhokoliv dalšího zpracování. Funkce tedy nikdy nevrací prázdnou proměnnou. function shell ( string-list commands string-list stdin string-list stdout string-list stderr string-list returned ) : string
: : : :
Funkce spustí shellový skript a počká na jeho dokončení. Parametry commands
Příkazy pro shell; každý řetězec představuje jeden řádek skriptu. Protože jsou všechny příkazy spouštěny jako skript, mohou využít plně možností skriptování včetně složitějších konstrukcí a faktu, že jsou spouštěny v jedné instanci shellu. stdin
Obsah této proměnné bude přesměrován na standardní vstup spuštěného skriptu. Pokud je proměnná prázdná, bude standardní vstup skriptu odpojen. stdout, stderr
Proměnné pro uložení standardního, resp. chybového výstupu skriptu. Výstup vzhledem k režii a faktu, že většinou není skriptem využit (postačuje návratová hodnota), není uložen do proměnných automaticky, nýbrž je nutné jeho získání povolit argumentem returned. returned
Obsahuje seznam výstupů, které mají být získány a vráceny; může obsahovat hodnoty "stdout" pro navrácení standardního výstupu a "stderr" pro vrácení chybového výstupu. Návratová hodnota Pokud nebylo možné skript spustit, vrácena je prázdná hodnota. V opačném případě je vrácen návratový kód shellu v podobě řetězce (tj. např. "0"). function execute ( string-list command string-list stdin string-list stdout string-list stderr string-list returned ) : string
: : : :
Funkce je velmi podobná svým chováním funkci shell. Také slouží ke spouštění externího procesu, rozdíl však je v tom, že spouští přímo spustitelný program voláním operačního systému a bez asistence shellu. Touto funkcí tedy nelze vykonat vše, co lze 54
vykonat na příkazovém řádku, ale její výhodou je zase její větší efektivita a není omezena možnostmi shellu (např. na platformě Windows má shell velmi omezenou délku příkazové řádky, a tedy i počet argumentů, které lze programu předat). Při spouštění programu je respektováno nastavení vyhledávání spustitelných programů – zpravidla bývá seznam adresářů se spustitelnými programy uveden v proměnné prostředí PATH. Parametry command
Prvním řetězcem v seznamu je jméno spustitelného programu. Ostatní řetězce jsou pak parametry, které mu jsou předány. function stdout ( string-list strings ) function stderr ( string-list strings )
Funkce vypíší svůj argument na standardní, resp. chybový výstup konzole EBM. Parametry strings
Řetězce, které mají být na výstup vypsány. Každý řetězec je ukončen odřádkováním. function setReturnValue ( string value )
Funkce nastaví návratovou hodnotu skriptu. Tato hodnota bude zkombinována s chybovým kódem interpreteru a vrácena jako návratový kód procesu operačnímu systému. Parametry value
Hodnota návratového kódu. Zadává se jako číselná hodnota v řetězci. Nečíselná hodnota bude ignorována. function getReturnValue ( ) : string
Funkce vrátí návratový kód skriptu nastavený funkcí setReturnValue. function echo = stdout ;
Alias na funkci stdout. Definováno skriptem. function function function function
echo = System.stdout ; peek = System.peek ; shell = System.shell ; execute = System.execute ;
Tyto aliasy jsou definovány skriptem v globálním namespace. 55
5.3.8 Namespace Targets Pro vytváření cílů a manipulaci s nimi slouží funkce z namespace Targets. function bind ( target tgt : string flag ) : string
Funkce vynutí svázání cíle se souborem na základě momentálních hodnot jeho atributů. Jakmile je cíl se souborem svázán, nelze tuto vazbu již změnit. Za normálních okolností proběhne vazba ihned po zpracování cíle odvozovacím pravidlem. Podrobnosti ohledně vazby souborů a cílů viz atributy, zejména atribut Binding. Parametry tgt
Cíl, jehož vazba má být uskutečněna. flag
Může nabýt hodnoty "timestamp"; její použití způsobí i okamžité naplnění atributu Timestamp. Návratová hodnota Pokud cíl je svázán se souborem, vrací "true", v opačném případě je vrácena prázdná hodnota. function assignAttributes ( target src : targets dest : string-set names : string sign )
Provede hromadné přiřazení vybraných atributů zadaného cíle stejnojmenným atributům celé skupiny cílů. Parametry src
Cíl, jehož atributy budou přiřazovány. Pokud cíl není zadán nebo neexistuje, zachová se funkce stejně, jako by byl dodán cíl, jehož všechny atributy jsou nepřiřazené. dest
Seznam cílů, do jejichž atributů má být přiřazováno. names
Seznam jmen atributů, které mají být přiřazeny. Pokud je v seznamu pouze prázdný řetězec, budou přiřazeny všechny momentálně definované atributy – tato vlastnost se musí používat opatrně, může mít nečekané vedlejší efekty. sign
Přiřazovací operátor, který se má použít. Operátor se zadává v podobě řetězce, který obsahuje symbol operátoru (např. ":="); pokud je zadána neznámá hodnota, bude použit operátor ?=. Speciálně jsou povoleny ještě hodnoty "?= empty", "= empty" a "= null" (pozor na mezeru mezi klíčovým slovem a operátorem – je povinná). 56
function createBlank ( targets tgts : string returns ) : targets
Vytvoří cíle se zadaným jménem. Vytvořené cíle budou mít nastavené pouze povinné atributy, ostatní zůstanou nenastavené. Parametry tgts
Jména cílů, které mají být vytvořeny. returns
Určuje sémantiku návratové hodnoty. Může nabývat hodnot: "created" "existed" "existing" "nothing"
Vráceny budou jména cílů, které byly tímto voláním skutečně vytvořeny. Vrácena budou jména cílů, které existovaly ještě před tímto voláním. Vrácena budou jména cílů, které budou existovat po tomto volání, nehledě na to, zda vznikly před ním nebo až během něj. Nebude vráceno nic. Tato hodnota je implicitní.
Návratová hodnota Řídí se hodnotou parametru returns. Implicitně není vraceno nic. function createByFile ( string-list paths : string-list flags : string returns ) : targets
Podobně jako funkce createBlank vytváří nové cíle podle zadaných jmen, ale na rozdíl od ní jim nastavuje řadu dalších atributů. Tato funkce je zvlášť užitečná pro vytváření nevirtuálních cílů. Při používání této funkce se nedoporučuje používat absolutních cest (v argumentu paths), neboť tím je narušen smysl virtuální jmen; v kombinaci s funkcí include představuje tato funkce silný nástroj, který dovoluje se absolutním cestám snadno vyhnout. Parametry paths
Seznam cest k souborům, ke kterým má být vytvořen cíl. Doporučuje se zadávat pouze relativní cesty. Relativní cesty jsou vztaženy ke globální proměnné CurrentDir. flags
Tento argument obsahuje příznaky, které ovlivňují fungování rutiny. Může obsahovat následující hodnoty: "free"
Při použití této hodnoty nebude cíl přidán do svého vlastního atributu DependsOn. V opačném (implicitním) případě přidán bude, a bude tak závislý na čase poslední modifikace svého souboru.
57
"local"
Má význam pro virtuální jméno vznikajících cílů. Pokud tato hodnota není použita (implicitní případ), zkombinuje se zadaná cesta s proměnnou VirtualDir: např. pro cestu "src/main.c" a VirtualDir = "/apps" bude výsledné virtuální jméno "/apps/src/main.c". Naopak pokud použita bude, užije se pro kombinaci s VirtualDir pouze jméno souboru; předchozí příklad tedy vytvoří virtuální jméno "/apps/main.c".
returns
Parametr má stejný význam jako ve funkci createBlank; jediný rozdíl je v tom, že jeho implicitní hodnotou je "existing". function createScannerTarget ( ) : target
Funkce vytvoří cíl s náhodně generovaným jménem a s omezenou dobou života. Funkci lze použít pouze po dobu běhu scanneru; je také určena pro potřeby scanneru – scanner pro nalezené soubory touto funkcí může snadno vytvořit cíle, které pak vrátí (viz též atribut Scanner). Cíle vytvořené touto funkcí mohou být zrušeny, jakmile nejsou potřeba, není proto vhodné uchovávat jejich jména a počítat s nimi. function createTemplate ( ) : target
Vytvoří a vrátí tzv. šablonový cíl. Šablonového cíl má náhodně generované jméno, není zpracováván odvozovacími pravidly, není aktualizován, prohledáván scannerem a jeho atributy nejsou ukládány do statefile. Využití šablonových cílů spočívá v tom, že do jejich atributů lze přiřadit předdefinované hodnoty, které pak mohou být hromadně přiřazovány dalším cílům (např. funkcí assignAttributes). function createVirtual ( targets tgts : string returns ) : targets
Rozšíření funkce createBlank implementované pomocí skriptu. Toto rozšíření nastaví atributy zadaných cílů způsobem vhodným pro virtuální cíle (např. zabrání jejich asociaci se soubory, typ cíle uvede "virtual" apod.). function dependOnSelf ( targets tgts )
Funkce implementovaná skriptem; nechá každý ze zadaných cílů záviset v atributu DepensOn na sobě samotném. function isTemplate ( target tgt ) : string
Funkce vrací prázdnou hodnotu, právě když zadaný cíl není šablonovým cílem; jinak je vracena hodnota "true".
58
function isValid ( target tgt ) : string
Funkce vrací prázdnou hodnotu, právě když zadaný cíl skutečně existuje (byl vytvořen některou z výše zmíněných konstrukčních funkcí); jinak je vracena hodnota "true". function loadAttribute ( target tgt : string name ) : unknown
Funkce načte ze statefile hodnotu pro zadaný atribut. Parametry tgt
Cíl, jehož atribut má být ze statefile načten. name
Jméno atributu, který má být načten. Návratová hodnota Typ návratové hodnoty bude přizpůsoben typu atributu. Pokud atribut zadaného jména není definován, bude navrácená nepřiřazená hodnota mít typ unknown. Atribut, který nebyl uložen vrátí prázdnou hodnotu. function function function function function
assign = Targets.assignAttributes ; blanks = Targets.createBlank ; files = Targets.createAsFile ; virtual = Targets.createVirtual ; template = Targets.createTemplate ;
Tyto aliasy jsou uvedeny v globální namespace.
59
6. Knihovna pravidel Fungování integrovaných grafických prostředích je většinou založeno na myšlence projektů. Projekty představují relativně samostatné dílčí nebo i finální výsledky a vznikají zpracováním (zpravidla) více vstupních souborů. Soubory v projektu typicky sdílejí řadu společných nastavení. Obvyklý postup vytvoření projektu v integrovaném prostředí se sestává z vybrání typu projektu (tedy typu úlohy, kterou projekt má řešit), z nastavení vlastností, které jsou společné souborům spravovaným projektem, a z přidání souborů do projektu (a případně nastavení jejich specifických vlastností). Projekty je také obvyklé sdružovat do skupin a stanovovat mezi nimi závislosti – každý projekt řeší nějakou relativně samostatnou úlohu a konečný výsledek vznikne jejich spojením. Typickým příkladem je použití různých knihovních modulů při vývoji aplikace. Předdefinovaná pravidla EBM se snaží tomuto „projektovému“ modelu co nejvíce přiblížit a zachytit ho do jednoduché textové podoby. Jejich vytváření a používání je založeno zejména na konvencích. Obsahem této kapitoly jsou tedy především právě konvence a způsob, jakým jsou předdefinovaná pravidla organizována. Informace nutné pro jejich rozšiřování je možné snadno získat nahlédnutím do příslušných souborů.
6.1 Organizace pravidel Předdefinovaná pravidla jsou rozdělena na tři části: obecné utility, prototypy nástrojů a implementace prototypů. Obecné utility představují sadu relativně obecných funkcí, které slouží jako základ pro podporu různých nástrojů, které má EBM používat, a také zajišťují základní podporu a konvence projektů. Podpora nástrojů je pak rozdělena mezi prototypy, které představují jednak rozhraní pro běžného uživatele a jednak zajišťují společné funkce svým implementacím. Implementace prototypů pak dodávají obsluhu konkrétních nástrojů a respektují jejich konkrétní zvláštnosti a možnosti. Toto rozdělení respektuje i adresářová struktura s předdefinovanými pravidly a měli by jej respektovat i uživatelé, kteří dodají další pravidla. Respektování navržené adresářové struktury také dovoluje snadné překrytí globálních skriptů skripty vlastními pro jednotlivé projekty: stačí modifikované verze skriptů umístit do stejné hierarchie a nastavit k nim uživatelskou vyhledávací cestu.
60
rules/ Tento adresář by měl nastaven v systémových vyhledávacích cestách; naopak by v nich neměly být uvedeny jeho podadresáře z důvodů patrných níže. Přímo v tomto adresáři jsou skripty s obecnými utilitami a také skript base.ebm, který lze považovat za systémový, neboť importuje a definuje standardní symboly. rules/proto/ Zde jsou uloženy skripty s prototypy jednotlivých nástrojů. Skript xyz.ebm se importuje takto: import "proto/xyz.ebm" ;
rules/toolkit/ Obsahuje skripty s implementacemi nástrojů. Nástroje se sdružují podle své distribuce v tzv. toolkitu – např. toolkit gcc obsahuje implementace nástrojů pro linkování, kompilaci C/C++ a vytváření knihoven, které pracují s překladačem gcc. Hlavní importní skript toolkitu by měl být uložen v souboru se jménem shodným se jménem toolkitu a s příponou .ebm; jména toolkitů (a tedy i skriptů) se dle konvence píší malými písmeny. Pokud je toolkit rozdělen do více skriptů, tyto jeho skripty by měly být uloženy v dalším podadresáři se jménem shodným se jménem toolkitu. Toolkit např. gcc může být importován příkazem: import "toolkit/gcc.ebm" ;
Toolkity by však neměly být importovány přímo; při dodržení této organizace mohou být importovány pomocí obecných utilit a snadněji, včetně kontroly chyby importování.
6.2 Organizace projektů Popis konfigurace skripty by měl sledovat strukturu popsanou v této pasáži; dodržení následujících konvencí zajistí snadné rozšiřování projektu o další podprojekty i jejich údržbu a redukci opakovaných informací v popisu konfigurace. • Vhodné je použít stromovou logickou strukturu: celý projekt lze většinou rozdělit na podprojekty a různé více či méně samostatné bloky, které bývají např. pod správou jednoho vývojáře. Konfigurační skripty by pak tuto hierarchii měly sledovat; každý podprojekt by měl být popsán vlastním skriptem, případně více skripty, z nichž jeden je pro podprojekt hlavní. Hlavní skript podprojektu pak importuje případné ostatní skripty podprojektu a sám je importován hlavním skriptem celého projektu. • Hlavní skript celého projektu by v první řadě měl zabezpečit import všech předdefinovaných pravidel, která jsou potřeba, a všech vyžadovaných nástrojů. Podprojekty se pak tímto již nemusí (při dodržení předchozího pravidla) zabývat a navíc je z hlavního skriptu ihned patrné, jaké všechny nástroje jsou potřebné pro úspěšné zpracování celého projektu. • V hlavním skriptu by také měla být definována výchozí nastavení všech projektů a deklarovány povolené varianty. Každý projekt sice potom může
61
libovolná nastavení měnit, ale vhodnou volbou výchozích nastavení se dá vyhnout opakování týž konfiguračních hodnot u většiny podprojektů. • Je vhodné se vyhnout používání absolutních jmen v odkazech na cíle, ale i na soubory – díky příkazu include a nastavování aktuálních „adresářů“ to lze snadno zařídit. • Virtuálními jména by měla kopírovat stromovou logickou strukturu projektů; speciálně výsledky podprojektů by měly mít pevné postavení v hierarchii jmen, což umožňuje jejich snadné spojování. Jednoduchý příklad představuje sada knihoven a aplikací, které tyto knihovny používají: aplikace mohou být představovány cíli např. /app/editor, /app/viewer atd., knihovny např. /lib/highlighting, /lib/jpeg…
6.3 Obecné utility
defaults.ebm
Importuje base.ebm, definuje podporu pro cíl /all, který bývá uváděn jako výchozí cíl aktualizace, a připraví podporu pro soubor toolkits.ebm. Navíc definuje pár pomocných funkcí pro vytváření prototypů. Soubor defaults.ebm by měl být první soubor, který je hlavním skriptem zanořený, druhým importovaným skriptem (po nezbytných akcích) by měl být toolkits.ebm.
output.ebm
Zde se skrývá podpora pro separátní ukládání souborů různých variant. Pomocí inicializačních souborů (argumenty dir/result a dir/build) lze nastavit jména adresářů pro výsledné či pro pomocné soubory (případně jsou použity standardní hodnoty). Odvozovací pravidla pak použitím funkce Output.apply, definované v tomto skriptu, změní cestu pro jimi generované cíle tak, aby příslušný soubor byl umístěn do správného adresáře, přičemž je v úvahu vzato i jméno aktuálně vybrané konfigurace. Soubory různých konfigurací se tedy nemíchají. Vzhledem ke svému uplatnění je tento skript importován zejména skripty s odvozovacími pravidly.
projects.ebm
Základní podpora projektů je implementována skriptem projects.ebm. Každý cíl by měl být přiřazen k některému projektu. V tomto skriptu je definován atribut Project, který představuje asociaci cíle s cílem projektu; prostřednictvím této asociace může odvozovací pravidlo do zpracování cíle zahrnout atributy projektového cíle, k němuž je zpracovávaný cíl přiřazen. Dále je tu definován šablonový cíl Projects.Defaults, jehož atributy získá každý nově vytvořený projektový cíl, pokud nezadá vlastní šablonu; tento cíl tedy představuje místo využitelné hlavním skriptem pro zadání globálních výchozích nastavení. Kromě tohoto mechanismu pro snadné dědění atributů je tu definováno rozhraní pro přidávání a ubírání cílů z projektu a také základní funkce pro vytvoření nového projektového cíle, které jsou využívány prototypovými skripty. Tento 62
skript tedy podobně jako output.ebm bývá importován skripty s odvozovacími pravidly a uživatel jej běžně importovat nemusí.
toolkits.ebm
Skript defaults.ebm definuje základní podporu pro aktivaci nástrojů; vlastní aktivace nástrojů je již úlohou toolkits.ebm a je provedena jeho importem (tedy importovat příslušné nástroje lze jen jednou – import se chová jako pouze jednorázově zavolatelná funkce). Skript importuje toolkity, jejichž jména jsou předána argumentem toolkits z inicializačního souboru, a porovná dostupné toolkity i jednotlivé nástroje s obsahem proměnných Toolkits.Required a Tools.Required. Tyto proměnné by měly být nastaveny před importem toolkits.ebm tak, aby obsahovaly jména toolkitů a nástrojů, které jsou bezpodmínečně nutné pro úspěšnou kompilaci projektu. Počátek hlavního skriptu tedy vypadá typicky jako v tomto příkladu: # importují se základní pravidla import "defaults.ebm" ; # sdělí se jména nástrojů, které jsou nutné pro tento projekt Tools.Required = "bison" "flex" "CC" "linking" ; # importují se toolkity import "toolkits.ebm" ;
6.4 Prototypy a implementace Prototyp definuje rozhraní pro běžného uživatele i pro implementaci, která prototyp doplní pro konkrétní nástroj. Prototyp by měl dbát následujících konvencí a obsahovat definice požadovaných symbolů. • Definice všech funkcí specifických pro prototyp by měly být uzavřeny ve vyhrazeném namespace; soubor skriptu s prototypy je vhodné pojmenovat stejně, ale jen s užitím malých písmen. Příkladem je prototypový namespace CC uložený v souboru proto/cc.ebm. V některých případech může být zahrnuta celá skupina příbuzných prototypů v jednom skriptu, např. proto/linking.ebm obsahuje prototypy pro linker aplikací, dynamických knihoven a knihovníka statických knihoven. • Atributy používané prototypem a jeho implementacemi se pojmenovávají dle konvence /, např. CC/Define. • Jsou-li definovány atributy se složitějšími hodnotami, je vhodné importovat projects.ebm a šabloně Projects.Defaults nastavit výchozí hodnoty atributů. Konvence říká, že výchozí hodnoty mají být vhodné pro finální podobu výsledků (tj. release varianty). • Jestliže je to pro prototypovaný nástroj vhodné, měl by prototyp definovat scanner. Vystačí-li si scanner s vestavěnými funkcemi, může jej prototyp rovnou i implementovat.
63
• Dále prototyp definuje obecnou část odvozovacích pravidel – typicky obecná část těchto pravidel zahrnuje vytvoření nových cílů a jejich provázání s projektem a s cíli, které jim daly vzniknout, a také nastavení výstupního adresáře (viz Output.apply). • Nedílnou součástí prototypu jsou rutiny pro detekci typu zdrojových souborů a jejich registrace. Pro manuální nastavení typu se definuje v namespace Files funkce nastavující typ cíle. Typ souboru bývá pojmenováván v konvenci připomínající jména MIME a stejně je pojmenována i funkce pro manuální nastavení typu cíle; příkladem je typ source/C. • Pokud prototyp definuje nové typy projektů, pak do namespace Projects přidá funkce, které slouží jako konstruktory nového projektu. Málokterý prototyp musí definovat všechny symboly zmíněné v tomto výčtu; jako nejúplnější ukázky v předdefinovaných pravidlech mohou sloužit prototypy v souborech proto/cc.ebm a proto/linking.ebm. Zároveň představují i pěknou ukázku coding style a konvencí, které by skripty EBM měly používat. Implementace prototypů doplňuje zpravidla jen několik funkcí, ale ty zase bývají delší a komplikovanější než funkce prototypů, neboť jejich úkolem je transformovat atributy cílů do příkazové řádky. Implementace by se také měla držet několika konvencí. • První konvence se týká pojmenování skriptů s implementací a byla zmíněna již výše v pasáži věnované organizaci pravidel. • V první řadě musí implementace ověřit podle proměnných Tools.Available, a Toolkits.Available, zda již není importována jiná implementace, která potenciálně narušuje její činnost. Většinou není možné, aby zcela automaticky fungovaly vedle sebe dvě implementace jednoho prototypu. V případě, že ke konfliktu došlo, musí implementace ignorovat své importování a ukončit skript; v opačném případě může všechny symboly nadefinovat a musí svou přítomnost ve zmíněných proměnných zaregistrovat. • Odvozovací pravidla (a případně další rutiny), které implementace definuje nebo rozšiřuje, musí zaregistrovat, aby byly automaticky použity.
6.5 Ukázka: Hello, World! Ukázka použití pravidel představuje zkrácenou verzi malého demoprojektu přiloženého distribuci EBM. Projekt obsahuje knihovnu a aplikaci, která tuto knihovnu používá. Adresářová struktura projektu je následující. / Kořenový adresář projektu. Obsahuje buildfile; ten kromě platformy apod. určuje také umístění konfiguračního skriptu, který bude zpracován jako první. /.statefiles/ Do tohoto adresáře budou umístěny statefiles pojmenované podle jednotlivých konfigurací. 64
/build/ V podadresářích se jmény ve tvaru konfigurace/podprojekt budou vytvořeny pomocné soubory, např. .obj či .o soubory. O toto se postarají už předdefinovaná pravidla. Tento adresář lze také smazat pro odstranění souborů, které nejsou potřeba pro spuštění výsledných programů. /result/ Do podadresářů se jmény jednotlivých konfigurací bude umístěn výsledek překladu, tedy binární programy (a případně další soubory nezbytné k jejich běhu). /src/ Zdrojové soubory. Zde je umístěn také soubor build.ebm. /src/apps/hello/ Adresář se zdrojovými soubory programu hello; obsahuje konfigurační skript hello.ebm. /src/include/ Sdílené hlavičkové soubory projektů. /src/libs/console/ Zdrojové soubory pro knihovnu console; obsahuje konfigurační skript console.ebm.
Konfigurační skripty ###################################################################### # soubor: build.ebm # # Hlavní skript; importuje potřebné nástroje, postará se o společná # nastavení a zanoří konfigurační skripty podprojektů. # # Jména cílů jednotlivých podprojektů: # "/apps/hello" Hello, World! (spustitelný program) # "/libs/console" utility (statická nebo sdílená knihovna) # import běžných pravidel import "defaults.ebm" ; # nastavení požadovaných nástrojů Tools.Required = "CC" "linking" ; # import nástrojů - v případě, že požadované nástroje nejsou # k dispozici, skript nebude za tímto příkazem pokračovat import "toolkits.ebm" ; # deklarace povolených konfigurací (povoleny jsou tímto zápisem např. # win32.debug.static, ale nikoliv např. win32.debug.release) configurations win32 linux solaris : debug release : static shared ; # nastavení defaults všem projektům - zde potřebujeme nastavit cesty # pro hlavičkové soubory pro překladač jazyka C Projects.Defaults@CC/IncludeDirs = '$(RootDir)/src/include' ; # "release" konfigurace si vystačí s implicitními hodnotami, "debug" # konfigurace potřebuje nastavit některé atributy jinak config debug {
65
on $(Projects.Defaults) { CC/Debugging = "on" ; CC/Optimize = "off" ; CC/Define += "_DEBUG" ; } # on } # config # pro "static" konfigurace musíme nastavit, že si přejeme vytvoření # statických knihoven (implicitní nastavení tvoří dynamické knihovny) config static { Projects.Library/Type = "static" ; } # config # zanoříme soubory s jednotlivými podprojekty; příkaz include nám # dovolí též "namontovat" jednotlivé cíle do hierarchie virtuálních # jmen - to je význam druhého parametru (jinak detaily viz uživatelská # příručka) include "apps/hello/hello.ebm" : "/apps/hello" ; include "libs/console/console.ebm" : "/libs/console" ; # a ještě připojíme příkazem all hlavní cíl - aplikaci hello – # k populárnímu cíli all (resp. tedy /all), který bývá defaultním # cílem all "/apps/hello" ; ##################################################################### # soubor: hello.ebm # vytvoří projekt spustitelného programu, přidá do něj soubor hello.c # a učiní projekt závislým na cíli /libs/console, který může být # cokoliv – pravidla už se zařídí Projects.executable "../hello" : "hello.c" : "/libs/console" ; ##################################################################### # soubor: console.ebm # do pomocné proměnné se uloží jméno cíle knihovny, aby se s ním lépe # manipulovalo local target console = "../console" ; # vytvoříme projekt knihovny podobně jako jsme vytvořili projekt # aplikace hello Projects.library $(console) : "colors.c" "input.c" "print.c" ; # deklarujeme cíle, které představují externí knihovny vyhledávané # linkerem (to by nebylo ani nutné, ale takto jsou aspoň viditelně # jejich deklarace a na společném místě) local target curses = Files.library/search "/libs/curses" ; local target ncurses = Files.library/search "/libs/ncurses" ; # nastavení jednotlivých konfigurací config linux {
66
# přidáme závislost projektu console na knihovně ncurses Projects.addInputs $(console) : $(ncurses) ; } # config config solaris { # Solaris ncurses nemá, použít můžeme proto pouze curses Projects.addInputs $(console) : $(curses) ; } # config config solaris linux { # ať již se užije curses nebo ncurses, necháme pro konfigurace solaris # i linux nadefinovat další makro on $(console) { CC/Define += "USE_CURSES" ; } } # config config win32 { config shared { # pro dynamickou Win32 knihovnu přidáme ještě další vstupní soubory, # resource script a definiční soubor – nemuseli jsme ani deklarovat # záměr použít resource compiler: pokud není k dispozici, budou # pravidla tyto soubory ignorovat, protože k nim nebude existovat # vhodné odvození Projects.addFileInputs $(console) : "console.def" "resources.rc" ; } # config } # config
Komentáře Příklad je podrobně komentován, přesto je vhodné upozornit na několik věcí, které demonstruje. • Je použita logická hierarchie projektů a jim odpovídajících skriptů; hlavní skript se stará o globální výchozí nastavení, importuje nástroje a vnořuje skripty podprojektů. • Absolutní jména se téměř nepoužívají; užijí se jen tam, kde je to nevyhnutelné. • Soubory nebo závislosti na jiných cílech lze přidávat do projektu postupně a s ohledem na varianty konfigurace.
67
Příloha: Gramatika jazyka Obsahem této přílohy je gramatika jazyka, jejíž zjednodušené fragmenty byly uváděny u výkladu jednotlivých syntaktických konstrukcí. Výpis gramatiky uvedený v této příloze se řídí následujícími konvencemi. • Neterminály jsou psány malými písmeny. • Terminály jsou buď uvedeny ve formě literálů uzavřených do rovných apostrofů, nebo symbolickým jménem zapsaným velkými písmeny. Terminály zapsané jménem mohou nabývat nekonečného množství hodnot (např. jména identifikátorů), nebo představují virtuální tokeny. • Pravidla mají na levé straně uveden neterminál, na který se redukuje pravá strana složená z neterminálů i terminálů; pravá a levá strana je oddělena dvojtečkou, alternativy k téže levé straně pak jsou oddělovány svislou čarou. Pravá strana je vždy odsazena, v případě příliš dlouhých pravidel je pak tímto odsazením naznačeno pokračování pravidla na dalším řádku. • Případné komentáře jsou uváděny ve formě komentářů jazyka C; nejčastěji bude pomocí komentáře /*empty*/ označena prázdná pravá strana pravidla. • Nepovinný symbol symbol na pravé straně pravidla bude označen symbol(opt). • Speciality gramatiky, lexikální konvence a pojmenované neterminály budou vysvětleny níže, pod gramatikou.
Gramatika /* počáteční neterminál */ translation_unit : statements /*end-of-file*/ /* příkazy */ statements
: statements statement
68
statement
: | | | | | | | | | | | | | | | | | |
assignment attributes break config configurations continue do_while for_each function function_call ';' if namespace on return statement_compound switch variable while /*empty*/
statement_compound : '{' statements '}' /* typy */ type : 'string' | 'string-list' | 'string-set' | 'target' | 'targets' /* identifikátory apod. */ id : id_composed | id_qualified id_composed : id_atomic | id_composed '.' id_atomic id_qualified : '$' '.' id_composed id_atomic : ID object aggregate attribute_chain
: aggregate | id : id '@' attribute_chain : id_atomic | attribute_chain '@' id_atomic
id_reference object_usage
: '@(' id ')' : '$(' object ')'
modifier modifiers modifier_list
: MODIFIER(opt) : modifier_list(opt) : MODIFIER modifier_list(opt)
/* použití funkcí */ function_call
: 'for-follow' function_call_args | id function_call_args | indirect_function_call
indirect_function_call : '$@' expression_atom | '$@' expression_atom ':' function_call_args function_call_args
: function_call_arg | function_call_arg ':' function_call_args
function_call_arg
: expression_atoms | id_reference | /*empty*/
/* výrazy */ expression expression_atoms
: expression_atoms | function_call : expression_atom expression_atoms(opt)
69
expression_atom
: | | | | | |
expression_expand expression_include expression_join expression_split object_usage string_template STRING_VALUE
expression_include
: '${' expression(opt) '}'
expression_expand : '${' iterative_declarator expand_selector(opt) expand_generator(opt) '}' iterative_declarator iterative_variable expand_selector expand_generator
: : : :
iterative_variable 'in' expression type id_atomic '|' bool_expression ';' expression
expression_join
: '&{' expression ';' expression '}' | '&{' expression '}'
expression_split
: '*{' expression ';' expression '}' | '*{' expression '}'
string_template
: ''' string_tpt_content '''
string_tpt_content
: string_tpt_content(opt) STRING_VALUE | string_tpt_content(opt) string_expression
string_expression
: STRING_TPT_INTERRUPT string_expansion
string_expansion
: | | |
expression_expand expression_include expression_join object_usage
/* booleovské výrazy */ bool_expression : bool_term | bool_term bool_ebop bool_expression bool_ebop
: '^' | '|'
bool_term
: bool_atom | bool_atom '&' bool_term
bool_atom
: '!' bool_atom | bool_compound | expression_bool
bool_compound
: '(' bool_expression ')'
expression_bool
: expression | expression_bool_uop expression | expression expression_bool_bop expression
expression_bool_bop
: 'common' | 'in' | 'like' | 'not-in' | '==' | '!='
expression_bool_uop
: '%' | '?'
70
/* přiřazovací příkazy */ assignment : aggregate assignment_box ';' | id assignment_box ';' assignment_box
assign_op
: | | |
assign_op expression '?=' 'empty' '=' 'empty' '=' 'null'
: '?=' | '=' | ':=' | '+=' | '*=' | '=+' | '-=' | '=*'
/* blokové přiřazení */ on : 'on' expression '{' on_assignments(opt) '}' on_assignments : on_assignment on_assignments(opt) on_assignment : attribute_chain assignment_box ';' /* řízení control flow */ do_while : 'do' statement_compound 'while' bool_expression ';' while : 'while' bool_expression statement_compound for_each : 'for-each' iterative_declarator statement_compound if else
: 'if' bool_expression statement_compound else(opt) : 'else' statement_compound | 'else' if
switch
: 'switch' modifier expression '{' switch_cases(opt) '}'
switch_cases switch_case break continue return
: switch_case switch_cases(opt) : 'case' expression statement_compound | 'else' statement_compound
: 'break' ';' : 'continue' ';' : 'return' expression(opt) ';'
/* deklarace a podobné konstrukce */ namespace : 'namespace' id_atomic statement_compound function
: function_declarator '=' id ';' | function_declarator function_args function_ret(opt) statement_compound
function_declarator function_args
: 'function' id_atomic : '(' function_arg_list(opt) ')'
function_arg_list : function_arg | function_arg_list ':' function_arg function_arg function_ret
: modifier type id_atomic : ':' type
variable : variable_object type id_atomic variable_init(opt) ';' variable_object variable_init
: 'global' modifier | 'local' : '=' expression
attributes attribute_decls attribute_decl
: 'attributes' '{' attribute_decls(opt) '}' : attribute_decl attribute_decls(opt) : modifiers type id_atomic ';'
configurations
: 'configurations' configurations_desc ';'
71
configurations_desc config_names
: config_names | config_names ':' configurations_desc
: id_atomic | config_names id_atomic | /*empty*/
config : 'config' modifier config_desc statement_compound config_desc
: id_composed | config_desc id_composed
Lexikální pravidla Pro lexikální analýzu jsou s výjimkou řetězcových konstant nevýznamné bílé znaky, ty jsou ignorovány. Povoleny jsou komentáře ve stylu jazyka C (tj. komentář je uzavřen mezi znaky řetězce /* a */), které mohou být zanořovány, a také komentáře obvyklé pro unixovské skripty, které začínají znakem # a končí až koncem řádku. Komentáře jsou považovány za bílé znaky. Jiné znaky, než znaky kvalifikované jako součást některého tokenu nebo komentáře, jsou s varováním ignorovány. Vysvětlení terminálů označených v gramatice pouze symbolickým jménem obsahuje následující tabulka. ID
Tento token nese řetězec se jménem identifikátoru. Jméno identifikátoru musí být neprázdné a smí začínat pouze znaky A–Z, a–z a _, dalšími znaky, které se mohou ve jménech vyskytovat, ale pouze na jiné než první pozici, jsou 0–9, +, - a / (lomítko se ostatně v konvencích jazyka používá jako hierarchický oddělovač identifikátorů příbuzného významu).
MODIFIER
Modifikátorem je identifikátor uzavřený mezi znaky < a >. Rozpoznáváno je momentálně 12 modifikátorů. Ne všechny modifikátory jsou aplikovatelné na místa, kde v gramatice je token MODIFIER použit; použití hlídá sémantika pravidla. Tomuto způsobu byla dána přednost před pevným zasazením modifikátorů do gramatiky vzhledem k jejich značnému počtu a možnému rozšiřování.
72
STRING_VALUE
Token nese literál obsažený ve vstupním souboru. Literál může být uzavřen do rovných uvozovek nebo rovných apostrofů. Význam obou způsobů zápisu je odlišný. Literál uzavřený do uvozovek představuje pevný řetězec, literál uzavřený v apostrofech smí obsahovat některé výrazy gramatiky (např. expanze proměnných). Escape znakem v obou případech je zpětné lomítko, které do řetězce vloží znak za ním následující. Literál by neměl přecházet přes konec řádku. Kromě znaků omezujících literál a zpětného lomítka jsou řídícími znaky uvnitř literálu ještě # a ~. Znak # počíná zápis ASCII kódu znaku, který je tvořen hexadecimálními číslicemi a ukončen středníkem (např. #9; pro tabulátor). Znak ~ přeruší literál, který je navázán opětovným výskytem tohoto znaku; mezi oběma znaky by měly být pouze bílé znaky. Znak ~ usnadňuje zápis literálů, které by se nevešly na jeden rozumně krátký řádek, aniž by vznikly nejasnosti, které bílé znaky do literálu mají patřit, nebo bylo nutné se vzdát odsazování rozděleného literálu.
STRING_TPT_INTERRUPT
Jedná se o virtuální token, který zabezpečuje implementaci literálů s vloženými výrazy; je tu použito tzv. lexical tie-ins, kdy lexikální analýza je ovlivňována syntaktickou. Pokud lexikální analýza nalezne při rozpoznávání literálu s výrazy sekvenci znaků, jimiž může začít vložený výraz, emituje tento token a přepne se do normálního módu. Syntaktická analýza po rozpoznání příslušného výrazu přepne lexikální analýzu zpět do režimu rozpoznávání literálu.
73