Lexikální analýza Překladače, přednáška č. 2 Šárka Vavrečková Ústav informatiky, FPF SU Opava sarka.vavreckova@fpf.slu.cz http://fpf.slu.cz/ vav10ui Poslední aktualizace: 14. října 2011
Symboly Co je to symbol? Symbol je nejmenší část kódu s vlastním významem číslo, identifikátor (proměnná, název funkce, klíčové slovo), aritmetický či relační operátor, závorka, středník, atd. Struktura symbolu identifikace (název) o jaký typ symbolu jde, atribut (-y) skutečná hodnota čísla, název proměnné, pozice ve zdrojovém souboru, apod.
Symboly Co je to symbol? Symbol je nejmenší část kódu s vlastním významem číslo, identifikátor (proměnná, název funkce, klíčové slovo), aritmetický či relační operátor, závorka, středník, atd. Struktura symbolu identifikace (název) o jaký typ symbolu jde, atribut (-y) skutečná hodnota čísla, název proměnné, pozice ve zdrojovém souboru, apod.
Symboly TSymbol = record typ: TTypSymbolu; // identifikace symbolu atrib: string; // atribut symbolu end;
Příklad CONST hodn = 32; VAR prom; BEGIN prom := 25 * (hodn + 4); IF prom < 100 THEN PRINT prom ELSE PRINT prom - 100; END S CONST S ID HODN S EQ S NUM 32 S SEM S VAR S ID PROM S SEM S BEGIN S ID PROM S IS S NUM 25 S MUL... S NUM 100 S SEM S END
Úkoly lexikální analýzy Lexikální analyzátor převádí zdrojový (textový) soubor na posloupnost symbolů. Vstup: zdrojový program překladače Výstup: posloupnost symbolů Lexikální chyby: v rámci jednoho symbolu, například posloupnost znaků, která není symbolem (72R4), znak nepatřící do abecedy jazyka,...
Úkoly lexikální analýzy Lexikální analyzátor převádí zdrojový (textový) soubor na posloupnost symbolů. Vstup: zdrojový program překladače Výstup: posloupnost symbolů Lexikální chyby: v rámci jednoho symbolu, například posloupnost znaků, která není symbolem (72R4), znak nepatřící do abecedy jazyka,...
Vstupní formáty text (většinou jeden nebo několik textových souborů), binární formát (např. struktura graficky nadefinovaného formuláře a jiné prvky, které uživatel umístil myší ), vázaný text (předem daná struktura např. každý příkaz na novém řádku), dynamická struktura v paměti.
Abstraktní fáze plánování Určíme abecedu jaké znaky se ve vstupu mohou vyskytovat typy symbolů, které budeme rozpoznávat bude jazyk case-sensitive? klíčová slova tvar identifikátorů (názvů proměnných) a čísel
Postup vytvoření lexikálního analyzátoru Příklad celá nezáporná čísla (pro konstanty), rezervované identifikátory klíčová slova: BEGIN, END, VAR, CONST, IF, THEN, ELSE, PRINT, ostatní identifikátory pro názvy proměnných, aritmetické operátory (+,,, /), relační operátory (<, <=, >, >=, <>, =), operátor přiřazení (:=), pomocné symboly (závorky, středník).
Vytvoření syntaktického grafu symbolu S ID, S NUM: letter digit letter digit S PLUS, S SEM, S LQ: + ; < =
Regulární gramatika lexikální struktury jazyka (k příkladu) G = (N, T, P, S), T = Σ, N = {S, A, B, C, D, E} S l la identifikátory A l d la da S d db čísla B d db S + / aritmetické operátory S > < = < C > D relační operátory C = > D = S : E operátor přiřazení E = S ( ) ; pomocné symboly
Konečný automat rozpoznávající slova jazyka Postup zpracování slov jazyka 1 Na vstupu máme řetězec znaků, který chceme analyzovat. 2 Automat postupně čte znaky ze vstupu, mění svůj stav, a pokud je to nutné, načtené znaky ukládá na výstupní pásku. 3 Pro každý typ symbolu má automat jiný koncový stav. Podle toho, ve kterém stavu ukončí výpočet, určíme, o jaký symbol se jedná.
Konečný automat rozpoznávající slova jazyka Postup zpracování slov jazyka 1 Na vstupu máme řetězec znaků, který chceme analyzovat. 2 Automat postupně čte znaky ze vstupu, mění svůj stav, a pokud je to nutné, načtené znaky ukládá na výstupní pásku. 3 Pro každý typ symbolu má automat jiný koncový stav. Podle toho, ve kterém stavu ukončí výpočet, určíme, o jaký symbol se jedná.
Konečný automat rozpoznávající slova jazyka Postup zpracování slov jazyka 1 Na vstupu máme řetězec znaků, který chceme analyzovat. 2 Automat postupně čte znaky ze vstupu, mění svůj stav, a pokud je to nutné, načtené znaky ukládá na výstupní pásku. 3 Pro každý typ symbolu má automat jiný koncový stav. Podle toho, ve kterém stavu ukončí výpočet, určíme, o jaký symbol se jedná.
Konečný automat rozpoznávající slova jazyka S ID, S NUM S letter S ID digit letter digit S S NUM digit :=, <=, <, <> : S A = S IS < S S LESS > = S NEQ S LQ
Postup 1 V každém stavu automatu program načte ze zdrojového programu jeden znak a podle něho se rozhodne, kterou větví pokračovat. 2 V koncových stavech je třeba provést test, zda je načtený symbol korektně ukončen, tedy načteme následující znak. 3 Pokud automat nenalezne větev, po které by pokračoval, a 1 je v koncovém stavu právě načetl jeden celý symbol a po analýze dalšího znaku (viz předchozí bod) se přesouvá do počátečního stavu S, aby (po případné přestávce) mohl načítat další symbol. 2 není v koncovém stavu načtený znak je chybný, došlo k lexikální chybě.
Postup 1 V každém stavu automatu program načte ze zdrojového programu jeden znak a podle něho se rozhodne, kterou větví pokračovat. 2 V koncových stavech je třeba provést test, zda je načtený symbol korektně ukončen, tedy načteme následující znak. 3 Pokud automat nenalezne větev, po které by pokračoval, a 1 je v koncovém stavu právě načetl jeden celý symbol a po analýze dalšího znaku (viz předchozí bod) se přesouvá do počátečního stavu S, aby (po případné přestávce) mohl načítat další symbol. 2 není v koncovém stavu načtený znak je chybný, došlo k lexikální chybě.
Postup 1 V každém stavu automatu program načte ze zdrojového programu jeden znak a podle něho se rozhodne, kterou větví pokračovat. 2 V koncových stavech je třeba provést test, zda je načtený symbol korektně ukončen, tedy načteme následující znak. 3 Pokud automat nenalezne větev, po které by pokračoval, a 1 je v koncovém stavu právě načetl jeden celý symbol a po analýze dalšího znaku (viz předchozí bod) se přesouvá do počátečního stavu S, aby (po případné přestávce) mohl načítat další symbol. 2 není v koncovém stavu načtený znak je chybný, došlo k lexikální chybě.
Postup 1 V každém stavu automatu program načte ze zdrojového programu jeden znak a podle něho se rozhodne, kterou větví pokračovat. 2 V koncových stavech je třeba provést test, zda je načtený symbol korektně ukončen, tedy načteme následující znak. 3 Pokud automat nenalezne větev, po které by pokračoval, a 1 je v koncovém stavu právě načetl jeden celý symbol a po analýze dalšího znaku (viz předchozí bod) se přesouvá do počátečního stavu S, aby (po případné přestávce) mohl načítat další symbol. 2 není v koncovém stavu načtený znak je chybný, došlo k lexikální chybě.
Postup 1 V každém stavu automatu program načte ze zdrojového programu jeden znak a podle něho se rozhodne, kterou větví pokračovat. 2 V koncových stavech je třeba provést test, zda je načtený symbol korektně ukončen, tedy načteme následující znak. 3 Pokud automat nenalezne větev, po které by pokračoval, a 1 je v koncovém stavu právě načetl jeden celý symbol a po analýze dalšího znaku (viz předchozí bod) se přesouvá do počátečního stavu S, aby (po případné přestávce) mohl načítat další symbol. 2 není v koncovém stavu načtený znak je chybný, došlo k lexikální chybě.
Základní datové typy type TTypSymbolu = (S_BEGIN, S_END, S_CONST, S_VAR, S_ID, S_NUM, S_SEM, S_LPAR, S_RPAR, S_IF, S_THEN, S_ELSE, S_PRINT, S_IS, S_PLUS, S_MINUS, S_MUL, S_DIV, S_EQ, S_NEQ, S_LESS, S_GRT, S_LQ, S_GQ); TSymbol = record typ: TTypSymbolu; atrib: string; end; // identifikace (název) symbolu // atributy
Práce se vstupem type TZnak = record rad: string; // zpracovávaný řádek pozice: byte; // pozice posledního načteného znaku na řádku delka: byte; // délka tohoto řádku cislo: word; // číslo řádku end; var zdroj: Text; symbol: TSymbol; znak: TZnak; // zdrojový program (textový soubor) // proměnná pro zachycení načítaného symbolu // proměnná pro uložení části vstupního souboru
Práce se vstupem procedure DejZnak; var i: byte; begin if eof(zdroj) then znak.rad[pozice] := #0 else with Znak do begin if delka = pozice then begin // je nutné načíst další řádek readln(zdroj, rad); rad := rad + ; delka := length(rad); pozice := 1; for i := 1 to delka do rad [i] := UpCase(rad [i]); // převod na velká písmena end; end; end else inc(pozice); // ještě nejsme na konci řádku
Metoda 1. Přímé stavové programování Princip a vlastnosti přepisujeme schéma stavového diagramu (nebo tabulku přechodů) metoda je použitelná pro přepis diagramu, kde nejsou smyčky přes více než jeden stav metoda je určena pro nekonečné jazyky
while zn = a0 do begin DejZnak; zn := znak.rad[znak.pozice]; end; case zn of a1: begin... end; a2: begin... end;... else...; end;... a 1 a2 a. 0 A a n
< S S LESS > = case znak.rad[znak.pozice] of // stav S... < : begin // stav S_LESS S NEQ S LQ DejZnak(znak); // posun ve vstupu na další znak case znak.rad[pozice] of > : symbol.typ := S_NEQ; // stav S_NEQ = : symbol.typ := S_LQ; // stav S_LQ else symbol.typ := S_LESS; // zůstáváme ve stavu S_LESS end;... // jiný typ symbolu end; else...; // ošetření chyby end;
Metoda 2. Tabulka přechodů jako celočíselná matice Princip a vlastnosti přepisujeme tabulku přechodů, a to na matici (2D pole) metoda je použitelná pro jakoukoliv tabulku přechodů, tabulka musí být deterministická používá se především na konečné jazyky
Metoda 2. Tabulka přechodů jako celočíselná matice Příklad G = (N, T, P, S), kde N = {S, A 1, A 2,..., A 8 }, T = {i, f, t, h, e, n, l, s} S ia 1 A 1 f [ IF ] S ta 2 A 2 ha 3 A 3 ea 4 ia 8 A 4 n A 8 s [ T HEN] [ T HIS] S ea 5 A 5 la 6 A 6 sa 7 A 7 e [ ELSE]
Metoda 2. Tabulka přechodů jako celočíselná matice 1 2 3 4 5 6 7 8 i f t h e n l s 0 1 2 5 1 10 2 3 3 8 4 4 11 5 6 6 7 7 12 8 13 CH 9 10 11 12 13
const k_chyba = 9; // chybový stav a koncové stavy k_if = 10; k_then = 11; k_else = 12; k_this = 13; MaxStav = 8; // maximální číslo stavu MaxZnak = 8; // maximální číslo znaku var tab: array [0..MaxStav, 1..MaxZnak] of byte; procedure DalsiZnak (var x: byte); external; // Přečte ze vstupu jeden znak, vrátí číslo z intervalu 1..MaxZnak // pro jeden ze znaků i, f,..., nebo číslo 0 pro chybný vstup.
procedure NactiTabulkuPrechodu; var i, j: byte; begin for i := 0 to MaxStav do for j := 1 to MaxZnak do tab[i,j] := k_chyba; tab[0,1] := 1; tab[0,3] := 2; tab[0,5] := 5; tab[2,4] := 3; tab[3,1] := 8; tab[3,5] := 4; tab[5,7] := 6; tab[6,8] := 7; tab[1,2] := k_if; tab[4,6] := k_then; tab[7,5] := k_else; tab[8,8] := k_this; end;
procedure Lex; var stav: byte; znak: byte; begin symbol := ; stav := 0; // vynulujeme while (not konec_vstupu) and (stav < k_chyba) do begin DalsiZnak(znak); stav := tab[stav, znak]; end; // while case stav of k_chyba:writeln( Chyba při zpracování ); k_if: Symbol.Typ := S_IF; k_then: Symbol.Typ := S_THEN; k_else: Symbol.Typ := S_ELSE; k_this: Symbol.Typ := S_THIS; end; end;
Metoda 3. Stav reprezentován proměnnou Princip a vlastnosti přepisujeme tabulku přechodů nebo regulární gramatiku, a to přímo do programu v jedné proměnné je uchován stav (řádek tabulky), v druhé načtený znak (sloupec tabulky) použitelnost stejná jako u předchozí metody
procedure Lex; begin stav := 0; while (not konec_vstupu) and (stav < k_chyba) do begin DalsiZnak(znak); case stav of 0: case znak of I : stav := 1; T : stav := 2; E : stav := 5; else stav := k_chyba; end; 1: if znak = F then stav := k_if else stav := k_chyba; 2: if znak = H then stav := 3 else stav := k_chyba; 3: case znak of I : stav := 8; E : stav := 4; else stav := k_chyba; end; 4: if znak = N then stav := k_then else stav := k_chyba; 5: if znak = L then stav := 6 else stav := k_chyba;
6: if znak = S then stav := 7 else stav := k_chyba; 7: if znak = E then stav := k_else else stav := k_chyba; 8: if znak = S then stav := k_this else stav := k_chyba; else stav := k_chyba; end; end; // while case stav of k_chyba:writeln( Chyba při zpracování ); k_if: Symbol.Typ := S_IF; k_then: Symbol.Typ := S_THEN; k_else: Symbol.Typ := S_ELSE; k_this: Symbol.Typ := S_THIS; end; end;