PROGRAMOVACÍ JAZYKY A PŘEKLADAČE LEXIKÁLNÍ ANALÝZA 2011 Jan Janoušek BI-PJP Evropský sociální fond Praha & EU: Investujeme do vaší budoucnosti
LEXIKÁLNÍ ANALÝZA Kód ve vstupním jazyku Lexikální analyzátor Posloupnost tokenů, tyto tokeny jsou terminální symboly bezkontextové gramatiky, která popisuje syntaxi vstupního jazyka
Z pohledu teorie formálních jazyků Jazyk lexikálních tokenů lze popsat regulární gramatikou a lexikální analyzátor je proto realizovaný konečným automatem! Příklad regulární gramatiky generující jazyk identifikátorů a celých čísel: G = ({S, A, B}, {p, c}, P, S), kde P obsahuje pravidla: S -> p p A c c B A -> p c p A c A B -> c c B
LEXIKÁLNÍ ANALYZÁTOR (LA) Rozpoznává lexikální elementy (tzv. tokeny) identifikátory klíčová slova literály (čísla, řetězce) jedno a víceznakové speciální symboly Vynechává oddělovače lexikálních elementů mezery, oddělovače řádků komentáře Rozpoznává a reaguje na direktivy překladače #include #if...
LEXIKÁLNÍ ANALYZÁTOR dále vrací syntetizované atributy tokenů: Příklad lexikálního elementu Identifikátor syntetizovaný atribut lexikálního elementu Textová podoba identifikátoru Celé číslo Hodnota čísla Reálné číslo Hodnota čísla
Komunikace lexikální <-> syntaktický analyzátor Kód ve vstupním jazyku Lexikální analyzátor get_next_token() Syntaktický analyzátor Lexikální element + případně jeho syntetizované atributy
Příklad Vstupní kód: if a<0 then {toto je poznamka} znam := -1; Posloupnost lexikálních elementů (a atributů), kterou vrací lexikální analyzátor: <kwif> <ident> ( a ) <less> <numb> (0) <kwthen> <ident> ( znam ) <assign> <minus> <numb> (1) <semicolon>
Návrh a konstrukce LA Syntaxe lexikálních elementů Lexikální elementy a jejich reprezentace Vstupní symboly a jejich reprezentace Návrh konečného automatu Programová realizace POZN. Existují také generátory lexikálních analyzátorů, např. programy lex, flex, (viz. předmět BI-AAG)
Příklad syntaxe lexikálních elementů lexikální-element = identifikátor číslo spec-symbol spec-symbol = '+' '-' '<' '<=' ':=' klíčové-slovo klíčové-slovo = 'if' 'then' 'else' identifikátor = písmeno (písmeno číslice)* (pozn. zapsáno regulárním výrazem) číslo = číslice {číslice}* (pozn. zapsáno regulárním výrazem) písmeno = 'A' 'B'... 'Z' 'a' 'b'... 'z' číslice = '0' '1' '2' '3' '4' '5' '6' '7' '8' '9
Příklad oddělovačů lexikálních elementů mezera, oddělovač řádků, komentář komentář je posloupnost znaků začínající znakem '{' a končící znakem '}', který jinak není v posloupnosti obsažen komentář musí být dokončen každý identifikátor nebo klíčové slovo musí být od následujícího identifikátoru, klíčového slova nebo čísla odděleno alespoň jedním oddělovačem lexikálních elementů
Příklad: Lexikální elementy a jejich reprezentace
Zpracování klíčových slov Klíčová slova se často zpracovávají v rámci identifikátorů: nedělají se pro ně speciální přechody automatu, ale po zjistění identifikátoru se podle tabulky klíčových slov zkontroluje jestli identifikátor není náhodou klíčovým slovem (pokud ano, pak lexikální analyzátor vrátí token příslušného klíčového slova)
Vstupní symboly a jejich reprezentace
Vstupní symboly a jejich reprezentace Realizace vstupní funkce: static int Znak; // vstupni znak static char Vstup; // vstupni symbol void CtiVstup(void) { Znak = CtiZnak(); if (Znak>='A' && Znak<='Z' Znak>='a' && Znak<='z') Vstup = 'p'; else if (Znak>='0' && Znak<='9') Vstup = 'c'; else if (Znak == EOF) Vstup = 'e'; else if (Znak <= ' ') Vstup = 'm'; else Vstup = Znak; }
Společně s tokenem identifikátor lexikální analyzátor vrací: příklad atributové překladové gramatiky na tabuli
Zavedení nového koncového stavu Rozdíl mezi standartním konečným automatem (KA) a lexikálním analyzátorem: KA čte vstupní posloupnost znaků až do konce. Lexikální analyzátor se čtením vstupu končí, dostane-li vstupní symbol, který již do rozpoznávaného lexikálního elementu nepatří lexikální analyzátor při dosažení konce vstupní posloupnosti znaků vrátí speciální lexikální symbol (EOI) Úprava přechodového diagramu: doplníme nový koncový stav reprezentující akci návrat z lexikálního analyzátoru z každého původního koncového stavu vedeme hranu do nového koncového stavu ohodnocenou vstupními symboly, které se nevyskytují v ohodnocení jiných hran vedoucích z daného koncového stavu z počátečního stavu vedeme hranu do nového koncového stavu ohodnocenou vstupním symbolem označujícím konec souboru
Zavedení nového koncového stavu
Doplnění o sémantické akce Vypočítávají hodnoty syntetizovaných atributů lexikálních elementů
Doplnění o sémantické akce
Sémantické akce A 1 : delkaid = 1; Ident[0] = Znak; A 2 : if (delkaid<maxlenident) {Ident[delkaId] = Znak; delkaid++;} A 3 : Ident[delkaId] = 0; Symb = KlicoveSlovo(Ident); A 4 : Cislo = Znak -'0'; Symb = NUMB; A 5 : Cislo = 10 * Cislo + (Znak -'0'); A 6 : Symb = PLUS; A 7 : Symb = MINUS; A 8 : Symb = LESS; A 9 : Symb = LESS_OR_EQ; A 10 : Symb = ASSIGN; A 11 : Symb = EOI; A 12 : Chyba("Neocekavany konec souboru v komentari");
Programová realizace /* lexan.h */ typedef enum {IDENT, NUMB, PLUS, MINUS, LESS, LESS_OR_EQ, ASSIGN, kwif, kwthen, kwelse, EOI} LexSymbol; enum {MaxLenIdent = 32}; extern LexSymbol Symb; extern char Ident[MaxLenIdent+1]; /* atribut symbolu IDENT */ extern int Cislo; /* atribut symbolu NUMB */ void CtiSymb(void); void InitLexan(char*);
Programová realizace /* lexan.c */ #include "lexan.h" #include "vstup.h" #include <stdlib.h> #include <string.h> #include <stdio.h> LexSymbol Symb; char Ident[MaxLenIdent]; /* atribut symbolu IDENT */ int Cislo; /* atribut symbolu NUMB */ static int Znak; // vstupni znak static char
Programová realizace void CtiVstup(void) { Znak = CtiZnak(); if (Znak>='A' && Znak<='Z' Znak>='a' && Znak<='z') Vstup = 'p'; else if (Znak>='0' && Znak<='9') Vstup = 'c'; else if (Znak == EOF) Vstup = 'e'; else if (Znak <= ' ') Vstup = 'm'; else Vstup = Znak; }
Programová realizace const struct {char* slovo; LexSymbol symb;} TabKS[] = { {"if", kwif}, {"then", kwthen}, {"else", kwelse}, {NULL, IDENT} }; LexSymbol KlicoveSlovo(char* id) //rozpoznani klicoveho slova tabulkou { int i = 0; while (TabKS[i].slovo) if (strcmp(id, TabKS[i].slovo)==0) return TabKS[i].symb; else i++; return IDENT; } void Chyba(char* text) { printf("\n%s\n", text); exit(1); }
Programová realizace void CtiSymb(void) { int delkaid; q0: /*0*/ while (Vstup=='m') CtiVstup(); switch (Vstup) { case 'e': Symb = EOI; break; case '{': CtiVstup(); /*1*/ while (Vstup!='}' && Vstup!='e') CtiVstup(); if (Vstup=='e') Chyba("neocekav any konec souboru v komentari"); else { CtiVstup(); goto q0; }
Programová realizace case 'p': /*A1*/ delkaid = 1; Ident[0] = Znak; CtiVstup(); /*2*/ while (Vstup=='p' Vstup=='c') { /*A2*/ if (delkaid<maxlenident) {Ident[delkaId] = Znak; delkaid++;} CtiVstup(); } /*A3*/ Ident[delkaId] = 0; Symb = KlicoveSlovo(Ident); break; case 'c': /*A4*/ Cislo = Znak -'0'; Symb = NUMB; CtiVstup(); /*3*/ while (Vstup=='c') { /*A5*/ Cislo = 10 * Cislo + (Znak -'0'); CtiVstup(); } break;
Programová realizace case '+': /*A6*/ Symb = PLUS; CtiVstup(); /*4*/ break; case '-': /*A7*/ Symb = MINUS; CtiVstup(); /*5*/ break; case '<': CtiVstup(); /*6*/ if (Vstup!='=') { /*A8*/ Symb = LESS; break; } CtiVstup(); /*7*/ /*A9*/ Symb = LESS_OR_EQ; break;
Programová realizace } case ':': CtiVstup(); /*8*/ if (Vstup!='=') Chyba("ocekava se ="); CtiVstup(); /*9*/ /*A10*/ Symb = ASSIGN; break; default: Chyba("nedovoleny znak"); } /*K*/ void InitLexan(char *jmeno) { InitVstup(jmeno); CtiVstup(); }