820 likes | 964 Vues
Implementazione di Linguaggi 2 PARTE 2. Massimo Ancona DISI Università di Genova Testo: A.V. Aho, R. Sethi, J.D.Ullman Compilers Principles,Techniques and Tools, Addison Wesley. Analisi semantica: Traduzione Guidata dalla Sintassi. Si basa sull’associazione di:
E N D
Implementazione di Linguaggi 2PARTE 2 Massimo Ancona DISI Università di Genova Testo: A.V. Aho, R. Sethi, J.D.Ullman Compilers Principles,Techniques and Tools, Addison Wesley
Analisi semantica: Traduzione Guidata dalla Sintassi Si basa sull’associazione di: • attributi semanticialle componenti sintattiche del linguaggio, e di • regole semantiche alle produzioni della grammatica per calcolare i valori degli attributi.
Esempio di traduzione guidata dalla sintassi Esempio: conversione da notazione infissa a postfissa. Formalmente definita da: • se E e’ una variabile allora post(E)=E • E=E1opE2post(E1opE2)=post(E1)post(E2)op • E=(E1) post(E)=post(E1).
Definizione Guidata dalla Sintassi Una DGS (DDS) e’ dafinita da: • definizione di una CFG G=(N,T,P,S) per specificare la struttura sintattica del linguaggio, • associazione di attributi ai simboli di G (elementi di V=NT), • associazione ad ogni elemento di P di un insieme di regole per valutare gli attributi di V=N T.
Algoritmo di traduzione Guidata dalla Sintassi Per ogni input I: • costruire il parse tree T di I, • per ogni nodo n etichettato da XV si denota X.a il valore dell’attributo a di x in n. Il valore di X.a viene calcolato in base alle regole semantiche per la valutazione di a associate alla produzione di X usata nel nodo n. • Un parse-tree che evidenzi i valori degli attributi e’ detto annotato.
Attributi sintetizzati E’ un attributo il cui valore nel parse tree viene calcolato tramite i vlori degli attributi dei figli in una visita bottom-up dell’albero. Esempio expr expr1+term expr.t:=expr1.t||term.t||’+’ expr expr1-term expr.t:=expr1.t||term.t||’-’ expr term expr.t:=term.t term a|b|c|d. term.t:=‘a’|’b’|’c’|’d’
Analisi e Traduzione expr expr1 + term expr1 - term c term b a expr.t=‘ab-c+’ expr1.t=‘ab-’ + term.t=‘c’ expr1.t=‘a’ - term.t=‘b’ c term.t=‘a’ b a
Schemi di Traduzione Uno schema di traduzione e’ DGS nelle cui produzioni sono inseriti frammenti di codice detti azione semantiche. Le azioni semantiche vengono inserite nelle parti destre delle produzioni e vengono eseguite da un parser esteso quando le incontra. Esempio: rest + term {print(‘+’)} rest1
Schemi di Traduzione Un DGS generalmente non impone un ordine specifico di valutazione degli attributi. Negli esempi piu’ semplici si usa una visita depth-first del parsing tree. proc visit(n: node) begin for each child m of n from-left-to-right do visit(m); eval semantic rules at n end Le traduzioni DGS viste finora sono semplici cioe’ la traduzione delle regole ass. alla parte sinistra di una regola e’ la concatenazione di quelle della relativa parte destra
Parsing Il processo che stabilisce se una stringa di token apprartiene al linguaggio generato da una grammatica G. Per ogni CFG esiste un parser (con back-tracking) di complessita’ O(n3) (in teoria O(n2.7)) che analizza una stringa di n token e costruisce un parse tree di essa. I linguaggi di programmazione hanno algoritmi di complessita’ O(n) che leggono l’input una sola volta da sinistra a destra leggendo “avanti” un solo token per volta, detto look-ahead token
Parsing I parser si dividono in due categorie TOP-DOWN BOTTOM-UP A seconda di come costruiscono il parse tree. I primi costruiscono una derivazione canonica sinistra, i secondi una destra. I primi sono molto popolari perche’ intuitivi e usabili in compiler costruiti a mano. I secondi si applicano a classi di linguaggi piu’ generali.
Parser top-down Metodo (applicato alla G seguente): • typesimple| • ^id| • array[simple] of type • simple integer | char | num dotdot num A partire dalla radice etichettata dal simbolo iniziale (type) si applicano i seguenti passi: • Nel nodo n etichettato A selezionare una produzione A e costruire da i figli di n • Determinare il nodo successivo cui associare un sotto albero
Parser top-down Input: ‘ array[num dotodot num] of integer ‘ type array[num dotodot num] of integer array [ simple ] of type type array[num dotodot num] of integer array [ simple ] of type num dotodot num type array[num dotodot num] of integer array [ simple ] of type num dotodot num simple type array[num dotodot num] of integer array [ simple ] of type num dotodot num simple integer
Parser a Discesa Ricorsiva Metodo top-down formato da un insieme di procedure ricorsive. Si associa una procedura ad ogni non terminale della grammatica. Per la G seguente: • typesimple| • ^id| • array[simple] of type • simple integer | char | num dotdot num Si scrive una proc. per type ed una per simple
Parser a Discesa Ricorsiva proc match(t: token); /* scanner*/ begin if lookahead = t then next_token else error; end;
Proc type proc type; /* */ begin if lookahead in {‘integer’,’char,’num’} then simple else if lookahead =‘^’ then begin match(‘^’); match(id) end else if lookahead = ‘array’ then begin match(‘array’); match(‘[‘); simple; match(‘]’); match(‘of’); type end else error end;
Proc simple proc simple; /* */ begin if lookahead =‘integer’ then match(‘integer’) else if lookahead = ‘char’ then match(’char’) else if lookahead =‘num’ then begin match(‘num’);match(‘dotdot’); match(‘num’) end else error end;
Codice completo in Io (1) MODULE PostFix(TABLES); VAR ch: CHAR; PROCEDURE Find; BEGIN DO READ(ch) UNTIL (ch<>' ')AND NOT EOLN END; (* Find*)
Codice completo in Io (2) PROCEDURE Expression; VAR Op: CHAR; PROCEDURE Term; PROCEDURE Factor; BEGIN IF ch='(' THEN Find; Expression; (* ch=) *) ELSE WRITE(ch) FI; Find END; (* Factor *)
Codice completo in Io (3) BEGIN (*Term*) Factor; WHILE ch='*' DO Find; Factor; WRITE('*') OD; END; (*Term*)
Codice completo in Io (4) BEGIN (* Expression*) Term; WHILE (ch='+') OR (CH='-') DO Op:=ch;Find; Term; WRITE(Op) OD; END; (* Expression*)
Codice completo in Io (5) BEGIN (* Main*) Find; DO WRITE(' '); Expression; WRITELN UNTIL Ch='.' END.
Parser Predittivo: teoria Lo sviluppo si basa sulla conoscenza dei primi simboli generabili nella parte destra di una produzione A insieme indicato: FIRSTk()={wT*| |w|<k & *w or |w|=k & xT* *wx} FIRST()=FIRST1() FIRST1()={a T{}|a= & * OR xT* *ax} Se A e A sono due produzioni di G allora esiste un parser a discesa ricorsiva senza backtraking se: FIRST() FIRST()= Le produzioni A richiedono un trattamento speciale
Parser Predittivo: ricorsione sinistra Un parser a discesa ricorsiva puo’ entrare in loop quando incontra una produzione (o un set di produzioni) che originano una ricorsione sinistra. Ad esempio le produzioni: EE+T | E-T | T Sono entrambe ricorsive a sinistra (in E). Questo fa parte di un caso generale AA| In cui =+T (-T)e =T. Questo problema e’ eludibile sostituendo le produzioni precednti con: AR RR|
PARTE APPROFONDITA FINE PARTE DI SURVEY INIZIO LUCIDI DETTAGLIATI
ANALISI LESSICALE • Motivazioni: • Semplificazione progettuale • Efficienza • Portabilita’ • Token pettern lexemi • b2a • Xyw letter{(letter|digit)} ident • Zz23 • Lexemi pattern token
ANALISI LESSICALE • In certi linguaggi l’analisi lessicale non e’ semplice (es. Fortran): • Ammette keyword come identificatori • E’ position dependent • Fa uso troppo libero degli spazi • DO 1 I=1,10 istruzione do (tipo for loop) • DO 1 I=1.10 assegnamento • 100 FORMAT(X7H)=(3-2) ambigua
ATTRIBUTI ASSOCIATI AI TOKEN • Quando piu’ di un lexema soddisfano un pattern lo scanner deve associare attributi ai singoli lexemi per distinguerli: • zx12 ident ‘zx12’ la stringa che identifica uno specifico lexema • 12.45 float ‘12.45 in codice float’ • Se la corrispondenza lexema token e’ 1-1 non occorrono attributi. Esempio • WHILE <---> whiletoken
ERRORI LESSICALI • Pochi errori sono identificabili a questo livello. Il recupero di errori si basa sulla panic rule • Si scartano i caratteri successivi a quello errato finche’ non e’ possibile riconoscere un nuovo token
INSIEMI REGOLARI • Dato un alfabeto T, la famiglia degli insiemi regolari su T e’ la famiglia piu’ piccola di insiemi di T* che: • contiene gli insiemi finiti • e’ chiusa rispetto alle operazioni • unione (), • concatenazione (.) • star (*)
ESPRESSIONI REGOLARI • Una sintassi comoda per esprimere insiemi regolari: • e’ un’espressione regolare che denota l’insieme • e’ un’espressione regolare che denota • per ogni aT a e’ un’espressione regolare che denota a
ESPRESSIONI REGOLARI • Se p e q sono espressioni regolari che denotano gli insiemi regolari P e Q rispettivamente, allora: • p+q e un’ E.R. che denota l’I.R. PQ (a volte usiamo anche p|q). • pq (o p.q) un’E.R. che denota P.Q • p* e’ un’E.R. che denota P*
ESPRESSIONI REGOLARI ESEMPI • Un identificatore e’ una sequenza di lettere o cifre, la prima delle quali e’ una cifra: • digit=0+1+…+9 • letter = a+b+c+…+x+y+z allora: • ident = letter(digit+letter)* • int = digit(digit)* • espon=(+’E’(‘+’|’-’| )digitdigit)
ESPRESSIONI REGOLARI ESEMPI • int = digit(digit)* • espon=(+’E’(‘+’+’-’+ )int) • float=int(‘.’int)espon
ESPRESSIONI REGOLARI E CFG Definizione Una CFG G=(N,T,P,S) e’ lineare a destra (risp. sinistra) se ogni A ha forma AxB|y A,BN x,yT (rispettivamente ABx|y). Teorema Un sottoinsieme L T* e’ regolare sse esiste CFG G lineare a destra (sinistra) tale che L=L(G).
ESPR. REGOLARI E AUTOMI Definizione Un automa finito non-deterministico e’ una quintupla A=(Q,T,, q0,F) dove: Q e’ un insieme finito detto degli stati T e’ un alfabeto dei simboli in input e’ la funzione di transizione : QT(Q) dove (Q)=2Q e’ l’insieme delle parti di Q. q0Q e’ detto stato iniziale F e’ l’insieme degli stati finali di accettazione
ESPR. REGOLARI E AUTOMI Spiegazione L’automa opera leggendo sequenzialmente da un nastro in input simboli di T partendo dallo stato iniziale. Ad ogni input A esegue una transizione pilotata da , l’input viene accettato se l’automa termina la lettura in uno stato di F, non accettato se qualunque sequenza di transizioni non porta mai ad uno stato di F. Piu’ esattamente:
ESPR. REGOLARI E AUTOMI Definizione Ogni coppia (q,w) Q T* e’ detta una configurazione di A. Definizione una mossa di A, denotata ├ o├A e’ definita wT* tra 2 configurazioni da: (q,aw)├A(q’,w) aT: q’(q,a) Se C e C’ sono configurazioni di A allora: C ├0 C’ sse C=C’; C ├n C’ sse C=C1├ C2 ├ … ├ Cn=C’ C ├*C’ sse n0: C ├n C’
LINGUAGGIO ACCETTATO DA UN AUTOMA DefinizioneL(A)=wT*|(q0,w)├*(q, ) & qF e’ detto linguaggio accettato da A. Teorema Un linguaggio L T* e’ regolare se e solo se esiste un automa A tale che L=L(A). Ne segue che gli insiemi regolari sono caratterizzati equivalentemente da espressioni regolari, automi finiti o grammatiche lineari a destra (sinistra)
DETERMINISMO non DETERMINISMO Definizione Un automa A=(Q,T,, q0,F) e’ detto deterministico se per ogni qQ e aT si ha | (q,a)|1. Inoltre A e’ detto completamente specificato se | (q,a)|=1 Teorema Sia L T* un linguaggio accettato da un automa non deterministico A (L=L(A)) allora esiste un automa deterministico A’ tale che L=L(A’).
DETERMINISMO non DETERMINISMO Definizione Se A=(Q,T,, q0,F) e’ un automa deterministico, si indica (q,a)=q’ in luogo di (q,a)=q’, l’unico valore associato a (q,a). Essendo gli insiemi regolari definibili sia da espressioni regolari che da automi, usiamo le prime per definire lo scanner e i secondi per implementarlo.
DETERMINISMO non DETERMINISMO Definizione Dato A=(Q,T,, q0,F) automa deterministico, da (q,a)=q’ si deduce che per ogni configurazione C=(q,aw) e’ unico lo stato q’ e la mossa o transizione (q,aw)├(q’,w)=C’ per cui (q,a)=q’. Per questo, gli automi deterministici sono piu’ efficienti di quelli non deterministici e piu’ adatti per implementare scanner.
IMPLEMENTAZIONE DI SCANNER Scanner: costruzione. Si definisce un insieme di espressioni regolari che date in input ad uno ‘scanner constructor’ costruisce un automa finito deterministico ottimizzato che implementa la scanner richiesto. Noi mostrimo i seguenti passaggi: Esp.Reg NFA DFA DFA ottimale la costruzione del NFA e’ ridodndante
DA NFA a DFA • Scanner: costruzione. Se L=L(M) per NFA A=(Q,T,, q0,F) allora il DFA equivalente e’ A’=(Q’,T,’, q’0,F’) con: • Q’=(Q)=2Q • q0’=q0, • SQ ’(S,a)=p| qS: p(q,a)= • (S,a) • F’=S Q | SF
DA NFA a DFA NFADFA: e’ facile mostrare che: A(S,w)├iA’(S’,) S’={p|qS: (q,w)├iA (p, )}
DIAGRAMMI DI TRANSIZIONE E’ un flowchart stilizzato di un analizzatore lessicale. Permette di tener traccia dei caratteri letti dallo scanner. Si possono avere diagrammi deterministici e non. Grafo di transizione e’ un grafo orientato con vertici etichettati da sinboli di Q e gli archi etichettati da simboli in T. I diagrammi di transizione modellano DFA i
DIAGRAMMI DI TRANSIZIONE Diagramma di transizione: flowchart stilizzato di un analizzatore lessicale. Permette di tener traccia dei caratteri letti dallo scanner. Si possono avere diagrammi deterministici e non. Grafo di transizione e’ un grafo orientato con vertici etichettati da sinboli di Q e gli archi etichettati da simboli in T. I diagrammi di transizione modellano DFA
DIAGRAMMI DI TRANSIZIONE I diagrammi di transizione sono deterministici, gli archi uscenti dallo stesso vertice hanno etichette distinte. I grafi di transizione sono in generale non deterministici: dallo stesso vertice possono uscire archi con la stessa etichetta.