Překladače 4. Lexikální analýza © Milan Keršlágerhttp:// Obsah: ● vstupní formáty,symboly ● syntaktické grafy ● rozpoznání jazyka konečným automatem ● implementace, příklady ● datové typy, převod čísla, řetězce
Lexikální analýza ● první fáze zpracování zdrojového kódu ● vstup: zdrojový kód programu ● zápis v nějakém programovacím jazyce – různé vstupní formáty – řeší se raději konverzními programy ● výstup: posloupnost symbolů ● atom, lexém, lexikální jednotka ● lexikální chyby
Lexikální chyby ● pouze v rámci jednoho symbolu ● neplatný znak – mimo alfanumerické znaky, avšak některé speciální ano ● neplatný identifikátor – název proměnné nesmí začínat číslem ● neplatný řetězec – chybějící ukončovací uvozovky
Vstupní formáty ● text ● holý text (plain text), jeden či více souborů – snadno čitelný, bez skrytých významů, case sensitive? ● vázaný text ● struktura používána k vytváření konstrukcí jazyka – odsazování v Pythonu, jeden příkaz = jeden řádek atp. ● binární formát ● typicky generovaný kód (návrhář formulářů) ● dynamická struktura v paměti ● produkt jiného programu
Symboly ● též atom, lexém, lexikální jednotka,... ● jsou to nejmenší části s vlastním významem ● klíčové slovo, číslo, operátor, název proměnné,... ● skládá se ze dvou částí ● název (identifikace symbolu) – S_BEGIN, S_NUM, S_PLUS, S_ID,... ● atribut (skutečná hodnota) – číslo, název proměnné,... – pozor na nevhodné atributy (typ operátoru) – → obtížnější určování priority operátorů
Příklad zpracování CONST pi=3.14; VAR obvod; BEGIN obvod:=2*pi*polomer;... END S_CONST S_IDpi S_EQ S_NUM3.14 S_SEM S_VAR S_IDobvod S_SEM S_BEGIN S_IDobvod S_IS S_NUM2 S_MUL S_IDpi S_MUL S_IDpolomer S_SEM... S_END // pro výstup záznamů // použit výčtový typ Tsymbol = record typ: TtypSymbolu; attr:string; end;
Skladba vstupu ● abeceda ● A..Z, a..z, 0..9, +, -, ×, /, >, <, =, (, ), ;, : ● rezervované identifikátory (klíčová slova) ● BEGIN, END, VAR, CONST, IF, THEN, ELSE,... ● ostatní identifikátory (názvy proměnných) ● operátory ● aritmetické, relační, přiřazení ● pomocné symboly ● závorky, středník,...
Syntaktické grafy ● slouží pro definici jazyka (v učebnicích) ● definuje jednotlivé lexikální symboly písme no číslo Symbol S_ID číslo Symbol S_NUM + Symbol S_PLUS <= Symbol S_LE
Rozpoznávání symbolů ● potřebujeme konečný deterministický automat ● na vstupu je řetězec znaků k analýze ● čtení znaků + změna vnitřního stavu automatu – lze využít výstupní pásku ● pro každý symbol má automat jiný koncový stav – po načtení celého vstupu zjistíme, jaký je to symbol ● nejprve zákres automatu ● kolečka značí stavy automatu ● šipky nesou symbol, který způsobí změnu stavu
Konečné automaty S S_I D písmeno Konečný automat pro S_ID písmeno číslo S S_N UM číslo Konečný automat pro S_NUM číslo S S_I S : Konečný automat pro S_IS S S_L T < Konečný automat pro relační operace A = S_L E S_N E > =
Rozpoznání jazyka automatem ● stavové diagramy pro všechny symboly ● využijeme syntaktické grafy ● vzniknou konečné deterministické automaty ● koncové stavy představují symboly ● shrneme všechny diagramy do jednoho ● sloučíme počáteční stavy pro všechna slova jazyka ● může vzniknout problém determinističnosti – jeden znak vede do dvou různých stavů – nejlépe jazyk, který toto nemá; nicméně lze vyřešit i jinak ● vzniká automat rozpoznávající jazyk
Činnost konečného automatu 1) v každém stavu se načte jeden další znak ● podle něho se rozhodne, kterou větví dále 2) v koncovém stavu ještě následující znak ● pro kontrolu správného ukončení symbolu 3) nenalezne-li se další větev → symbol? ● není větev, po které by se pokračovalo ● je v koncovém stavu → je to symbol (zapíšeme) 4) hlášení chyby ● není-li v koncovém stavu a není kam pokračovat
Zápis vystupujících symbolů ● holý text ● snadné hledání chyb lexikálního analyzátoru ● např. vázaný text ● pomalejší na zpracování ● binární soubor ● jednodušší načítání (např. pole nebo typ záznam) ● dynamická struktura v paměti ● vhodné pro přímé předání další fázi překladu ● dynamický seznam
Implementace automatu ● automat pro nekonečné jazyky ● obsahují identifikátory proměnných, čísla, řetězce... ● těmi běžně programujeme ● automat pro konečné jazyky ● používá se na rozlišení klíčových slov ● kombinujeme oba přístupy 1) jako nekonečné jazyky → vše je identifikátor 2) konečné jazyky → není identifikátor klíčové slovo?
Vstup analyzátoru ● ve formě textového souboru ● čtení po znacích není efektivní ● čteme po řádcích – řádek do záznamu – včetně čísla řádku, délky a pozice čteného znaku ● type Tznak = record rad: string;// zpracovávaný řádek pozice: byte;// pozice posl. načt. znaku delka: byte;// délka řádku cislo: word;// číslo řádku (pro chyb. hláš.) end;
Výstup analyzátoru ● lexikální analyzátor jako funkce ● každé zavolání funkce vrátí jeden symbol – symbol předán na vstup syntaktického analyzátoru ● výstupní symboly do výčtového typu – vystupující název symbolu bude index do výčtu – atribut bude řetězec ● type TTypSymbolu = (S_BEGIN,...) ● Tsymbol = record typ: TTypSymbolu;// index atrib: string;// atribut ● end;
Metody programování ● přímé stavové programování ( nekonečné jazyky ) ● změna stavu vyjádřena místem v programu ● tj. algoritmus sleduje strukturu jazyka ● tabulka přechodů ( konečné jazyky ) ● změna stavu je změnou pozice v tabulce ● obecně naprogramovaný automat ● stav reprezentován proměnnou ( konečné jazyky ) ● variace na tabulku
Stavové programování ● po načtení znaku ● změna stavu → příkaz switch (case) ● načtení nemění stav (smyčka) → while ● při změně struktury jazyka: → nutné měnit kód programu ● smyčka nesmí být přes více, než 1 stav ● v kódu programu nelze couvat (tj. vrátit se do výše položeného místa) → GOTO?
Příklad: stavové programování
Tabulka přechodů ● pro regulární gramatiku ● pravidla ve tvaru: A → aB nebo A → a – velká písmena: neterminály, malá: terminální symboly ● různé neterminály označují různé stavy ● použijeme-li indexy → řádky tabulky ● terminální symboly → sloupce ● při výpočtu se mění pozice v matici ● čísla uvnitř matice → číslo nového stavu ● je to nejobecněji naprogramovaný automat ● změna v jazyce mění pouze tabulku, ne však kód
Příklad: tabulka přechodů
Příklad: tabulka přechodů (kód)
Stav reprezentován proměnnou ● pro konečné, ale i nekonečné jazyky ● přechod do jiného stavu je přímo v kódu ● změna jazyka mění kód automatu ● odpadají velké tabulky v paměti → dají se však realizovat řídkými maticemi ● to však dále komplikuje kód lexikálního analyzátoru ● čtení vstupu pomocí smyčky while ● příkaz switch zajistí různé stavy ● jednotlivé case řeší přechody do jiného stavu
Příklad: stav pomocí proměnné
Datové typy ● dosud uvažována jen celá kladná čísla ● reálná čísla, řetězce, výčtové typy, pole, pointery... ● lexikální analýza to nemusí řešit, ale: ● konstanty se řeší již v lexikální analýze ● jako by přibylo další klíčové slovo ● ovlivňují analýzu dynamicky (za běhu) ● reprezentace v atributu symbolu ● číslo jako řetězec ● převod na vhodnou binární reprezentaci
Převod čísla na binární tvar ● buď v lexikální nebo sémantické analýze ● číslo načteme jako řetězec ● volíme cílový datový typu ● prostorově nejnáročnější (reálné, komplexní) ● prostorově nejúspornější – nutná bližší analýza (znaménko?, mezní hranice?, E?) ● problém reprezentace na dané architektuře – integer 4 bajty na 32bitovém CPU, ale 8 bajtů na 64bit. – může způsobit nepřenositelnost programu
Řetězce ● důležitá je jen délka řetězce ● optimalizace prostorové náročnosti ● někdy (jazyk C) lze řetězec na více řádků ● můžeme složit dohromady ● nezabýváme se však spojováním řetězců ● tzv. zřetězení → syntaktická analýza ● nicméně lze v primitivních případech
Pole, záznamy ● jde jen o zadávání polí a záznamů ● každý jazyk definuje jinak ● příliš komplikované a sofistikované ● není to úloha pro lexikální analýzu ● předáváme dál v rozloženém tvaru ● jako jednotlivé symboly