SlideShare a Scribd company logo
1 of 87
Download to read offline
LFC




                                     Il front-end di un
                                     compilatore
                                     Architettura del front-end
                                     Symbol table
                                     Traduzione guidata dalla
                                     sintassi
                                     Generazione del
Linguaggi formali e compilazione     three-address code



    Corso di Laurea in Informatica




           A.A. 2008/2009
LFC
Linguaggi formali e compilazione
                                          Il front-end di un
                                          compilatore
                                          Architettura del front-end
                                          Symbol table
                                          Traduzione guidata dalla
                                          sintassi
                                          Generazione del
                                          three-address code




Il front-end di un compilatore
     Architettura del front-end
     Symbol table
     Traduzione guidata dalla sintassi
     Generazione del three-address code
LFC
Linguaggi formali e compilazione
                                          Il front-end di un
                                          compilatore
                                          Architettura del front-end
                                          Symbol table
                                          Traduzione guidata dalla
                                          sintassi
                                          Generazione del
                                          three-address code




Il front-end di un compilatore
     Architettura del front-end
     Symbol table
     Traduzione guidata dalla sintassi
     Generazione del three-address code
LFC
Componenti del front-end
                                                            Il front-end di un
                                                            compilatore
                                                            Architettura del front-end
                                                            Symbol table
                                                            Traduzione guidata dalla
      Il front-end di un compilatore (cioè la parte che
  ◮                                                         sintassi
                                                            Generazione del
      termina con la creazione di una rappresentazione      three-address code


      intermedia del programma) è costituita dai seguenti
      moduli:
            analizzatore lessicale;
        ◮

            parser;
        ◮

            symbol table;
        ◮

            generatore di codice intermedio.
        ◮


      Per ragioni di tempo non ci potremo occupare, in
  ◮
      questo corso, della gestione degli errori.
LFC
Il modello di front-end
                                                                                                           Il front-end di un
                                                                                                           compilatore
                                                                                                           Architettura del front-end
                                                                                                           Symbol table
                                                                                                           Traduzione guidata dalla
      La seguente figura illustra un tipico schema di
  ◮                                                                                                        sintassi
                                                                                                           Generazione del

      organizzazione del front-end di un compilatore (tratto                                               three-address code



      da Aho, Lam, Sethi, Ullman (2007)).
                                 Richiesta
                                  token
                                                                                          three-addreess
      Programma                                            Albero
                                                                                               code
       sorgente                                                         Generatore di
                  Analizzatore                           sintattico
                                                Parser
                                                                      codice intermedio
                   lessicale
                                  Token




                                             Gestore della
                                             symbol table
LFC
Analizzatore lessicale
                                                                         Il front-end di un
      L’analizzatore lessicale ha il fondamentale ruolo di
  ◮                                                                      compilatore
                                                                         Architettura del front-end
      leggere la sequenza di caratteri che forma l’input file             Symbol table
                                                                         Traduzione guidata dalla
      e inviare al parser una sequenza di token, oggetti                 sintassi
                                                                         Generazione del
      astratti che coincidono essenzialmente con i simboli               three-address code


      terminali del linguaggio (ma vedremo subito dopo
      una definizione precisa).
      Ci sono però altri compiti che deve svolgere
  ◮
      l’analizzatore lessicale:
            riconoscere e “filtrare” commenti, spazi e altri
        ◮

            caratteri di separazione;
            associare gli eventuali errori trovati da altri moduli del
        ◮

            compilatore (in particolare dal parser) alle posizioni
            (righe di codice) dove tali errori si sono verificati allo
            scopo di emettere appropriati messaggi diagnostici;
            procedere all’eventuale espansione delle macro (se il
        ◮

            compilatore le prevede).
LFC
Interazione parser-lexical analyzer
                                                                  Il front-end di un
      L’analizzatore lessicale è normalmente programmato          compilatore
  ◮
                                                                  Architettura del front-end
      come “routine” del parser.                                  Symbol table
                                                                  Traduzione guidata dalla
                                                                  sintassi
      Quando il parser deve leggere il prossimo simbolo di
  ◮                                                               Generazione del
                                                                  three-address code
      input (si ricordino gli esempi di parser, top down o
      bottom up indifferentemente, visti nel gruppo di lucidi
      relativi al parsing di linguaggi liberi) esegue in realtà
      una chiamata all’analizzatore lessicale;
      quest’ultimo si attiva leggendo la “prossima”
  ◮
      porzione di input fino a quando non riconosce un
      simbolo terminale della grammatica, che viene
      restituito al parser;
      ciò che viene propriamente restituito è un token
  ◮
      name, una delle due componenti che costituiscono il
      token vero e proprio.
LFC
Token
                                                                      Il front-end di un
                                                                      compilatore
      Un token è un oggetto astratto caratterizzato da due            Architettura del front-end
  ◮
                                                                      Symbol table

      attributi, un token name (obbligatorio) e un valore             Traduzione guidata dalla
                                                                      sintassi

      opzionale.                                                      Generazione del
                                                                      three-address code


      Ecco alcuni esempi di sequenze di caratteri
  ◮
      riconosciuti come token da un compilatore C++:
            “0.314E+1”, cui corrisponde la coppia (token) in cui
        ◮

            il nome è number e il valore è il numero razionale
            3.14, opportunamente rappresentato;
            “x1”, cui corrisponde un token in cui il nome è id e il
        ◮

            cui valore è, a sua volta, un insieme di informazioni
            elementari (la stringa di caratteri che forma
            l’identificatore, il suo tipo, il punto del programma
            dove è stato definito);
            “else”, cui corrisponde il solo token name else.
        ◮
LFC
Token name
                                                                 Il front-end di un
     Il nome del token serve principalmente al parser, in        compilatore
 ◮
                                                                 Architettura del front-end
     quanto sono propriamente i token name che                   Symbol table
                                                                 Traduzione guidata dalla
     coincidono con i simboli terminali della grammatica.        sintassi
                                                                 Generazione del
                                                                 three-address code
     Il valore dei token è invece (in prima istanza) non
 ◮
     rilevante dal punto di vista della correttezza sintattica
     del programma.
     Ad esempio, le frasi “x<10” e “pippo!=35” sono del
 ◮
     tutto equivalenti e riconducibili alla frase generica in
     cui il valore di un identificatore è confrontato con un
     numero.
     Una tale frase può quindi essere astrattamente
 ◮
     descritta come “id comparison number”, dove
     abbiamo utilizzato il nome comparison per il token
     che descrive gli operatori relazionali.
LFC
Token value
                                                                         Il front-end di un
                                                                         compilatore
                                                                         Architettura del front-end
                                                                         Symbol table
      Il valore di un token gioca il ruolo principale nella
  ◮                                                                      Traduzione guidata dalla
                                                                         sintassi

      traduzione del codice.                                             Generazione del
                                                                         three-address code

      Con riferimento agli esempi del lucido precedente:
  ◮
            di un identificatore è importante sapere (tra gli altri) il
        ◮

            tipo, perché da questo dipende (in particolare) la
            quantità di memoria allocare;
            di un operatore di confronto è necessario sapere
        ◮

            esattamente di quale confronto si tratta, per poter
            predisporre il test e l’eventuale istruzione di salto
            opportuna;
            di una costante numerica è necessario sapere il
        ◮

            valore per effettuare l’inizializzazione corretta.
LFC
Token (continua)
                                                                Il front-end di un
                                                                compilatore
                                                                Architettura del front-end
      Il progettista del linguaggio stabilisce quali debbano
  ◮                                                             Symbol table
                                                                Traduzione guidata dalla
      essere considerati i simboli terminali del linguaggi, e   sintassi
                                                                Generazione del
      quindi i token.                                           three-address code



      Naturalmente, a tali simboli potranno corrispondere
  ◮
      (in generale) molte stringhe alfanumeriche nel codice
      sorgente (si pensi ai token che descrivono numeri o
      identificatori).
      Il progettista deve quindi stabilire esattamente anche
  ◮
      la mappatura fra stringhe e token name/terminali del
      linguaggio.
      Tale mappatura viene tipicamente descritta, come
  ◮
      già sappiamo, mediante espressioni regolari.
LFC
Pattern, lessemi e terminali
                                                             Il front-end di un
                                                             compilatore
                                                             Architettura del front-end
                                                             Symbol table
                                                             Traduzione guidata dalla

      Una descrizione ben formalizzata di quanto appena      sintassi
  ◮
                                                             Generazione del
                                                             three-address code
      ricordato può essere fatta utilizzando i concetti di
      lessema e pattern.
      Un pattern è una descrizione (formale o informale,
  ◮
      ma tipicamente esprimibile mediante espressioni
      regolari) delle stringhe che devono essere
      riconosciute (dall’analizzatore lessicale) come
      istanze di un determinato simbolo terminale.
      Le stringhe stesse sono dette lessemi.
  ◮
LFC
Pattern, lessemi e terminali (continua)
                                                                      Il front-end di un
                                                                      compilatore
                                                                      Architettura del front-end
                                                                      Symbol table
                                                                      Traduzione guidata dalla
      La seguente tabella illustra, mediante alcuni esempi,
  ◮                                                                   sintassi
                                                                      Generazione del
      i concetti che stiamo analizzando.                              three-address code




      Token name   Pattern                       Esempio di lessema
      id           letter(letter | digit)∗       pippo1
      number       [+ | -]nzd digit∗ [.]digit∗   -3.14
      comparison   <, >, <=, >=, =, !=           <
      literal      “[:alpha:]”                   “Pi greco”
      if           if                            if
      while        while                         while


Si noti che, per ragioni di spazio, il pattern che descrive il
token number non prevede la notazione scientifica.
LFC
Linguaggi formali e compilazione
                                          Il front-end di un
                                          compilatore
                                          Architettura del front-end
                                          Symbol table
                                          Traduzione guidata dalla
                                          sintassi
                                          Generazione del
                                          three-address code




Il front-end di un compilatore
     Architettura del front-end
     Symbol table
     Traduzione guidata dalla sintassi
     Generazione del three-address code
LFC
Symbol table
      Come evidenziato dalla struttura del front-end,
  ◮
                                                              Il front-end di un
      l’analizzatore lessicale presenta al parser una         compilatore
                                                              Architettura del front-end
      funzione, get-next-token(), che il parser invoca        Symbol table
                                                              Traduzione guidata dalla
      quando deve leggere il prossimo simbolo terminale       sintassi
                                                              Generazione del
      della grammatica.                                       three-address code



      Il risultato esplicito della get-next-token() è il
  ◮
      nome del prossimo token.
      Nei casi in cui è significativo anche il valore (fanno
  ◮
      eccezione le keyword, simboli di separazione come il
      punto e virgola e gli operatori), l’analizzatore
      lessicale restituisce anche (tipicamente) un
      puntatore alla symbol table dove è stato
      memorizzato il valore stesso.
      In pratica, la funzione get-next-token() può
  ◮
      restituire una coppia nome, valore , oppure (come
      nel caso di Lex/Yacc) il valore viene restituito
      attraverso una variabile globale.
LFC
Esempio
                                                         Il front-end di un
     Nel caso del seguente frammento di programma C++
 ◮                                                       compilatore
                                                         Architettura del front-end

     while (i<10) {                                      Symbol table
                                                         Traduzione guidata dalla
                                                         sintassi
       i=i+1;                                            Generazione del
                                                         three-address code
     }
     l’analizzatore lessicale restituisce al parser la
     seguente sequenza di token:

       while                         id, pointer
       leftpar                       assign_op
       id, pointer                   id, pointer
       comparison, pointer           plus_op
       number, pointer               number, pointer
       rightpar                      semicolon
       leftbrace                     rightbrace
LFC
Symbol table (continua)
                                                                 Il front-end di un
      Come già detto, le informazioni raccolte nella symbol      compilatore
  ◮
                                                                 Architettura del front-end

      table, relative ai token ai quali è associato un valore,   Symbol table
                                                                 Traduzione guidata dalla

      sono fondamentali nella generazione del codice, ma         sintassi
                                                                 Generazione del
                                                                 three-address code
      non solo.
      Mediante tali informazioni il front-end può anche
  ◮
      completare l’analisi sintattica del programma e,
      dicendo questo, intendiamo procedere a chiarire un
      aspetto che potrebbe essere rimasto oscuro,
      In precedenza abbiamo affermato che, dal punto di
  ◮
      vista sintattico, frasi come i < 10 o j < 10 sono del
      tutto equivalenti,
      Questo non è del tutto vero e non solo perché esse
  ◮
      (in termini di codice generato) significano cose
      diverse.
LFC
Symbol table (continua)
      Infatti, in molti linguaggio di programmazione
  ◮
                                                              Il front-end di un
      moderni, una variabile deve essere dichiarata prima     compilatore
                                                              Architettura del front-end

      di poter essere utilizzata.                             Symbol table
                                                              Traduzione guidata dalla
                                                              sintassi
      Se, con riferimento all’esempio appena fatto,
  ◮                                                           Generazione del
                                                              three-address code

      l’identificatore i fosse stato dichiarato mentre j no,
      allora la presenza della seconda frase genererebbe
      un errore.
      Usare una variabile non definita è un errore
  ◮
      sintattico? Si e no.
      Teoricamente sarebbe possibile inserire nella
  ◮
      grammatica il vincolo che ogni variabile deve essere
      dichiarata prima dell’uso.
      Questo però richiederebbe l’uso di produzioni (e
  ◮
      quindi di grammatiche) non context-free, per le quali
      non è neppure garantita l’esistenza di algoritmi di
      parsing.
LFC
Symbol table (continua)
      La soluzione adottata è quindi di non inserire tali
  ◮
                                                             Il front-end di un
      regole nella grammatica ma di controllare il           compilatore
                                                             Architettura del front-end
      soddisfacimento dei vincoli in altro modo, cioè        Symbol table
                                                             Traduzione guidata dalla
      proprio mediante l’uso della symbol-table.             sintassi
                                                             Generazione del
                                                             three-address code
      Più precisamente, quando il parser incontra un
  ◮
      identificatore nel contesto, poniamo, di
      un’espressione, va a verificare nella symbol table se
      tale identificatore è già presente (ed eventualmente
      con quale tipo è stato dichiarato).
      Se non è presente (ovvero se il tipo non è quello
  ◮
      “giusto”) segnala una condizione di errore, che
      considereremo di natura semantica.
      Un altro esempio di vincolo non facilmente
  ◮
      descrivibile con produzioni libere è quello della
      concordanza fra numero di parametri formali di una
      procedura e argomenti ad essa passati nelle varie
      chiamate.
LFC
Symbol table (continua)
                                                               Il front-end di un
                                                               compilatore
      Precisiamo bene come tali controlli vengono svolti
  ◮                                                            Architettura del front-end
                                                               Symbol table

      dal front-end usando la symbol table, che può essere     Traduzione guidata dalla
                                                               sintassi

      strutturata in modo tale da soddisfare la più ampia      Generazione del
                                                               three-address code


      esigenza rappresentata dalla implementazione delle
      regole di ambiente o di visibilità dei simboli (in
      inglese scoping rule).
      Ricordiamo che lo scope di una variabile (e della
  ◮
      dichiarazione che tale variabile introduce) è la
      porzione di programma in cui tale variabile è visibile
      (e dunque utilizzabile).
      Lo stesso identificare (un caso comune è i) può
  ◮
      essere definito più volte in un programma, ma ad
      esso saranno associati ambienti diversi.
LFC
Symbol table (continua)
      Nei moderni linguaggi di programmazione l’ambiente
  ◮                                                                 Il front-end di un
                                                                    compilatore
      di una variabile è “quasi sempre” determinabile in            Architettura del front-end

      modo statico, cioè guardando il testo del programma           Symbol table
                                                                    Traduzione guidata dalla
                                                                    sintassi
      (se escludiamo alcuni casi ricercati).                        Generazione del
                                                                    three-address code

      In particolare, le regole di visibilità degli identificatori
  ◮
      riflettono la struttura di annidamento dei blocchi di
      programma (incluse le procedure e le classi, se
      previste).
      Come detto, in quaesto caso si parla di static (o
  ◮
      lexical) scoping.
      Dal punto di vista del compilatore, la nozione di
  ◮
      ambiente può essere realizzata definendo una nuova
      symbol table all’ingresso di ogni blocco di
      programma e “concatenando” le simbol table in una
      lista, in cui la testa è sempre l’ultima symbol table
      definita.
LFC
Esempio
                                                        Il front-end di un
                                                        compilatore
                                                        Architettura del front-end
                                                        Symbol table
     Si consideri, come esempio, il seguente semplice
 ◮                                                      Traduzione guidata dalla
                                                        sintassi
     frammento di programma C++:                        Generazione del
                                                        three-address code

     1. { int i,j;
     2.   cin » i » j;
          { int i; float x;
     3.
     4.     i=1;
     5.     x=2.0*j;
     6.     cout « i « “: ” « x;
          }
     7.
     8.   cout « i « “: ” « j;
     9. }
LFC
Esempio (continua)
      Chiameremo ambiente la lista di symbol table.
  ◮
                                                               Il front-end di un
                                                               compilatore
      All’ingresso del blocco più esterno viene creata una
  ◮                                                            Architettura del front-end

      symbol table S1 (essenzialmente un dizionario) che       Symbol table
                                                               Traduzione guidata dalla
                                                               sintassi
      diviene la testa di ambiente.                            Generazione del
                                                               three-address code

      Ogni volta che viene incontrata una definizione,
  ◮
      l’identificatore viene inserito nella symbol table alla
      testa di ambiente.
      Ne consegue che gli identificatori i e j di riga 1
  ◮
      vengono inseriti in S1 .
      All’ingresso del blocco interno (riga 3) viene creata
  ◮
      (e inserita in testa ad ambiente) una seconda symbol
      table, S2 , nella quale vengono poi inseriti gli
      identificatori i e x di riga 3 (col loro tipo).
      All’uscita di ogni blocco, il puntatore alla testa di
  ◮
      ambiente viene fatto avanzare, con l’effetto di
      rendere inaccessibile la symbol table di testa.
LFC
Esempio (continua)
                                                                  Il front-end di un
                                                                  compilatore
                                                                  Architettura del front-end
      Quando nel programma si incontra un riferimento ad
  ◮                                                               Symbol table
                                                                  Traduzione guidata dalla
      una variabile, l’identificatore relativo viene cercato       sintassi
                                                                  Generazione del
      nella lista ambiente, seguendo il naturale ordine.          three-address code



      Ad esempio, il riferimento alla variabile i di riga 4
  ◮
      viene risolto con un lookup alla symbol table di testa
      (S2 ), che contiene un’entry con chiave i.
      Si noti quindi che, nel blocco interno, la variabile i
  ◮
      definita alla riga 1 diviene inaccessibile.
      Il riferimento alla variabile j di riga 5 richiede invece
  ◮
      il lookup di due symbol table, nell’ordine in cui sono
      presenti nella lista ambiente: S2 (search miss) ed S1
      (search hit).
LFC
Symbol table (continua)
                                                                     Il front-end di un
                                                                     compilatore
                                                                     Architettura del front-end

      Come già sottolineato, oltre alla implementazione              Symbol table
  ◮                                                                  Traduzione guidata dalla
                                                                     sintassi
      dello scope (e alla verifica del soddisfacimento di             Generazione del
                                                                     three-address code

      vincoli non facilmente esprimibili a livello
      grammaticale), le symbol table mantengono
      informazioni fondamentali per la generazione del
      codice
      Ad esempio, la entry per un identificatore contiene:
  ◮
            la sequenza di caratteri che ne costituisce il lessema
        ◮

            (che può essere usata come chiave per l’accesso
            alla tabella);
            il tipo della variabile;
        ◮

            l’indirizzo di memoria della variabile.
        ◮
LFC
Linguaggi formali e compilazione
                                          Il front-end di un
                                          compilatore
                                          Architettura del front-end
                                          Symbol table
                                          Traduzione guidata dalla
                                          sintassi
                                          Generazione del
                                          three-address code




Il front-end di un compilatore
     Architettura del front-end
     Symbol table
     Traduzione guidata dalla sintassi
     Generazione del three-address code
LFC
Le due parti del compilatore
      Il passaggio dal codice sorgente ad un codice            Il front-end di un
  ◮
                                                               compilatore
      macchina efficiente viene tipicamente spezzato in         Architettura del front-end
                                                               Symbol table

      due parti.                                               Traduzione guidata dalla
                                                               sintassi
                                                               Generazione del
      La prima parte, gestita dal front-end del compilatore,
  ◮                                                            three-address code


      termina con la generazione di un opportuno codice
      intermedio ed è chiaramente dominata dalle
      caratteristiche del linguaggio sorgente.
      La seconda parte, gestita dal back-end, è invece
  ◮
      specializzata ad ottenere un codice macchina
      efficiente in funzione della particolare architettura.
      La suddivisione netta del progetto di un compilatore
  ◮
      in front-end e back-end (la prima indipendente
      dall’architettura e la seconda indipendente dal
      linguaggio) ha, fra le altre proprietà, un notevole
      impatto in termini di economicità di progetto.
LFC
Rappresentazioni intermedie
                                                                    Il front-end di un
      Le rappresentazioni intermedie più importanti sono i          compilatore
  ◮
                                                                    Architettura del front-end

      syntax tree e il cosiddetto three-address code.               Symbol table
                                                                    Traduzione guidata dalla
                                                                    sintassi
      Per ora ci concentriamo sui syntax tree, posponendo
  ◮                                                                 Generazione del
                                                                    three-address code

      alla sezione sulla generazione del codice intermedio
      la definizione di three-address code.
      Un abstract syntax tree per un linguaggio L è un
  ◮
      albero in cui:
            i nodi interni rappresentano costrutti di L;
        ◮

            i figli di un nodo che rappresenta un costrutto C
        ◮

            rappresentano a loro volta le componenti significative
            di C;
            le foglie sono “costrutti elementari” (non
        ◮

            ulteriormente decomponibili) caratterizzati da un
            valore lessicale (tipicamente un numero o un
            puntatore alla symbol table).
LFC
Esempio
                                                                 Il front-end di un
     Un abstract syntax tree per la frase id + id × id è
 ◮                                                               compilatore
                                                                 Architettura del front-end
                                    +                            Symbol table
                                                                 Traduzione guidata dalla
                                                                 sintassi
                                                                 Generazione del
                                                  ×
                      id, ptr                                    three-address code




                                        id, ptr       id, ptr

     Abstract syntax tree e parse tree sono oggetti diversi.
 ◮
                                    E


                      E         +                 T


                                                  ×
                      T                 T                   F


                      F                 F                   id


                     id                 id
LFC
Rappresentazioni intermedie
      Per alcuni semplici linguaggi interpretati, una           Il front-end di un
  ◮
                                                                compilatore
      rappresentazione finale basata su una struttura ad         Architettura del front-end
                                                                Symbol table

      albero (da dare poi in input all’interprete) può essere   Traduzione guidata dalla
                                                                sintassi

      sufficiente.                                               Generazione del
                                                                three-address code


      Questo è il caso, ad esempio, della realizzazione di
  ◮
      un semplice “desk calculator” (interattivo) che
      accetta in input formule che seguono la sintassi
      classica (con operatori infissi).
      In questo caso, infatti, è semplice scrivere un
  ◮
      interprete per la formula rappresentata come
      abstract syntax tree.
      In generale, tuttavia, i syntax tree costituiscono solo
  ◮
      un passaggio intermedio verso al generazione di
      codice intermedio (tipicamente il three-address
      code).
LFC
I vari passaggi in dettaglio
      Per chiarezza didattica, possiamo “in prima battuta”     Il front-end di un
  ◮
                                                               compilatore
      immaginare che tutte le strutture intermedie che         Architettura del front-end
                                                               Symbol table

      portano alla generazione del three-address code          Traduzione guidata dalla
                                                               sintassi

      vengano effettivamente costruite.                        Generazione del
                                                               three-address code


      A partire dalla sequenza di token restituita
  ◮
      dall’analizzatore sintattico, possiamo quindi
      immaginare (per il momento) che il parser costruisca
      esplicitamente tanto il parse tree quanto l’abstract
      syntax tree delle stringhe “corrette”.
      In un compilatore reale, tuttavia, il parser emette il
  ◮
      three-address code senza costruire “esplicitamente”
      il syntax tree ne’ il parse tree.
      In realtà, anche nell’ambito della nostra trattazione
  ◮
      semplificata vedremo come almeno la generazione
      del parse tree sia (quasi sempre) evitabile.
LFC
Traduzione guidata dalla sintassi
      La generazione dell’abstract syntax tree, il nostro
  ◮
                                                                 Il front-end di un
      obiettivo attuale, può essere eseguita mediante            compilatore
      l’applicazione di una tecnica nota come Traduzione         Architettura del front-end
                                                                 Symbol table

      guidata dalla sintassi (in inglese Syntax-Directed         Traduzione guidata dalla
                                                                 sintassi
                                                                 Generazione del
      Translation).                                              three-address code


      In altre parole, l’output desiderato (un abstract syntax
  ◮
      tree) viene generato mediante un processo che è
      guidato dalla stessa grammatica e, in particolare,
      dalle sue produzioni.
      Tipicamente, quando viene applicata una produzione
  ◮
      (nel parsing top-down) oppure viene effettuata una
      riduzione (nel parsing bottom-up) viene anche
      effettuata una qualche azione di costruzione
      dell’abstract syntax tree.
      La modalità specifica che vedremo per la
  ◮
      realizzazione del processo di traduzione è quella
      degli schemi di traduzione (syntax-directed
      translation scheme).
LFC
Syntax-directed translation scheme
      Uno schema di traduzione prevede l’inserimento di
  ◮
                                                              Il front-end di un
      frammenti di codice nel corpo (parte destra) delle      compilatore
                                                              Architettura del front-end

      produzioni.                                             Symbol table
                                                              Traduzione guidata dalla
                                                              sintassi
      Per ogni produzione, il codice viene eseguito dal
  ◮                                                           Generazione del
                                                              three-address code

      parser in un preciso momento che può essere prima,
      durante o dopo l’applicazione della produzione
      stessa.
      Negli esempi di costruzione del syntax tree che
  ◮
      svilupperemo le azioni saranno essenzialmente
      operazioni di costruzione di nodi e manipolazione di
      puntatori.
      L’aspetto importante che dovremo chiarire è relativo
  ◮
      al preciso ordine di esecuzione dei vari frammenti di
      codice.
      Dobbiamo però preliminarmente descrivere quali dati
  ◮
      vengono manipolati dagli schemi di traduzione.
LFC
Attributi
                                                                  Il front-end di un
      Un qualunque processo di calcolo richiede la
  ◮                                                               compilatore
                                                                  Architettura del front-end
      manipolazione di informazione, che deve essere              Symbol table
                                                                  Traduzione guidata dalla
      memorizzata “da qalche parte”.                              sintassi
                                                                  Generazione del
                                                                  three-address code
      Nel caso degli schemi di traduzione, l’informazione
  ◮
      manipolata è costituita da attributi associati ai simboli
      della grammatica.
      Naturalmente abbiamo bisogno di una notazione per
  ◮
      poter fare riferimento a tale informazione.
      Se X è un simbolo della grammatica e a è il nome di
  ◮
      un suo attributo, useremo dunque la scrittura X .a per
      indicare il valore dell’attributo a di X .
      Il valore di un attributo può essere di qualunque
  ◮
      natura: stringa, numero, albero, tabella, tipo di dato,
      ecc.
LFC
Attributi (continua)
                                                                   Il front-end di un
                                                                   compilatore
      Si noti tuttavia che nel processo di derivazione di una
  ◮                                                                Architettura del front-end
                                                                   Symbol table
      stringa, (e quindi nel corrispondente parse tree) un         Traduzione guidata dalla
                                                                   sintassi

      simbolo X della grammatica può apparire più volte.           Generazione del
                                                                   three-address code


      Dobbiamo quindi precisare che la scrittura X .a si
  ◮
      applica non tanto al “generico” simbolo X bensì ad
      una sua occorrenza nel parse tree.
      In altri termini, se la scrittura X .a nel frammento di
  ◮
      codice associato (poniamo) alla produzione
      A → XYZ si dovrà intendere come riferita ad ogni
      specifica applicazione della regola (che introduce il
      simbolo X ), bisognerà comunque tenere conto del
      fatto che, in applicazioni diverse della regola, il valore
      di X .a potrà essere (e in generale sarà) diverso.
LFC
Attributi (continua)
                                                                Il front-end di un
                                                                compilatore
                                                                Architettura del front-end
                                                                Symbol table

      Nel nostro caso, i valori degli attributi saranno
  ◮                                                             Traduzione guidata dalla
                                                                sintassi

      puntatori al “costruendo” syntax tree.                    Generazione del
                                                                three-address code


      Per questa ragione, associato ad ogni non terminale
  ◮
      della grammatica si troverà sempre un attributo di
      nome node.
      In alcuni casi ci potranno essere anche ulteriori
  ◮
      attributi
      Siamo ora in grado di presentare il nostro primo
  ◮
      esempio di schema di traduzione, relativo alla “solita”
      grammatica delle espressioni aritmetiche.
LFC
Esempio
                                                                       Il front-end di un
                        {E .node ← new node(′ +′ , E1 .node,
     → E1 + T
 E                                                                     compilatore
                                                                       Architettura del front-end

                                                   T .node)}           Symbol table
                                                                       Traduzione guidata dalla

     →T                 {E .node ← T .node}
 E                                                                     sintassi
                                                                       Generazione del

                        {T .node ← new node(′ ×′ , T1 .node,
     → T1 × F
 T                                                                     three-address code



                                                   F .node)}
     →F                 {T .node ← F .node}
 T
     → (E )             {F .node ← E .node}
 F
     → id               {F .node ← new leaf(id, id.lexval)}
 F

     Si noti che:
 ◮
           ai non terminali è associato il solo attributo node;
       ◮

           ai terminali è associato l’attributo lexval (tipicamente,
       ◮

           un puntatore alla entry della symbol table).
           l’aggiunta dell’indice ai non terminali E e T , nella
       ◮

           parte destra delle produzioni E → E + T e
           T → T × F , serve unicamente a disambiguare i
           riferimenti nelle corrispondenti azioni.
LFC
Calcolabilità degli schemi
                                                               Il front-end di un
      Come si passa dalla descrizione “statica” delle azioni
  ◮
                                                               compilatore
      associate alle singole produzioni ad un processo di      Architettura del front-end
                                                               Symbol table

      calcolo precisamente definito?                            Traduzione guidata dalla
                                                               sintassi
                                                               Generazione del

      Se immaginiamo (come più volte affermato) che il         three-address code
  ◮
      parser costruisca effettivamente il parse tree,
      possiamo pensare che le azioni descritte dallo
      schema vengano eseguite nell’ambito di un
      “opportuno” attraversamento del parse tree stesso.
      In questo modo, specificando il tipo di
  ◮
      attraversamento, otteniamo la descrizione cercata.
      Infatti, una volta stabilito l’attraversamento (ad
  ◮
      esempio, in ordine posticipato) rimane univocamente
      determinato l’ordine di visita ai nodi del parse tree
      con conseguente esecuzione delle azioni specificate
      dallo schema.
LFC
Calcolabilità degli schemi (continua)
      Si noti che, in generale, il valore di un attributo        Il front-end di un
  ◮
                                                                 compilatore
      presente in un nodo del parse tree dipende dal             Architettura del front-end
                                                                 Symbol table

      valore degli attributi presenti in altri nodi.             Traduzione guidata dalla
                                                                 sintassi
                                                                 Generazione del
      L’ordine di attraversamento dell’albero deve quindi
  ◮                                                              three-address code


      garantire il soddisfacimento della fondamentale
      proprietà che il valore di ogni attributo presente nello
      schema sia calcolato prima di essere utilizzato.
      Ad esempio, con riferimento allo schema introdotto, i
  ◮
      valori degli attributi T1 .node e F .node dovranno
      essere disponibili nel momento in cui viene eseguito
      l’assegnamento
      T .node ← new node(‘×′ , T1 .node, F .node).
      Si tratta di un punto molto delicato, al quale
  ◮
      dobbiamo dedicare attenzione specifica utilizzando
      anche la terminologia appropriata.
LFC
Il ruolo del parse tree
      Prima di procedere, vogliamo però soffemarci sul         Il front-end di un
  ◮
                                                               compilatore
      ruolo del parse tree nei processi di calcolo dei quali   Architettura del front-end
                                                               Symbol table

      stiamo discutendo.                                       Traduzione guidata dalla
                                                               sintassi
                                                               Generazione del
      Tale ruolo è essenzialmente di “raccordo”, in quanto
  ◮                                                            three-address code


      il processo di parsing consente di generare in modo
      naturale prorpio il parse tree e non (direttamente) il
      syntax tree.
      Il parse tree offre, da un lato, un “posto” per
  ◮
      memorizzare gli attributi (cioè l’informazione
      manipolata dagli schemi), dall’altro (come abbiamo
      appena visto) un modo per descrivere l’ordine di
      esecuzione delle azioni dello schema.
      Va comunque da se che, qualora si trovino modi più
  ◮
      efficienti per risolvere questi due problemi, la
      costruzione esplicita del parse tree diviene superflua.
LFC
Tipi di attributi
                                                                       Il front-end di un
                                                                       compilatore
      Tornando al nostro problema (garantire cioè che il
  ◮                                                                    Architettura del front-end
                                                                       Symbol table
      calcolo del valore di un attributo venga eseguito solo           Traduzione guidata dalla
                                                                       sintassi
      quando tutti gli attributi ai quali esso fa riferimento          Generazione del
                                                                       three-address code

      siano stati a loro volta calcolati), definiamo le due
      diverse tipologie di attributi utilizzate nelle
      applicazioni reali: gli attributi sintetizzati e gli attributi
      ereditati.
      Con riferimento ad una generica produzione
  ◮


                       A → X1 . . . Xi−1 Xi . . . Xk

      un attributo per A si dice sintetizzato se il suo valore
      dipende solo dal valore degli attributi dei simboli Xi
      nella parte destra della produzione.
LFC
Tipi di attributi (continua)
                                                                  Il front-end di un
                                                                  compilatore
                                                                  Architettura del front-end
      Più precisamente, un attributo che etichetta un nodo
  ◮                                                               Symbol table
                                                                  Traduzione guidata dalla
      del parse tree è sintetizzato se il suo valore dipende      sintassi
                                                                  Generazione del
      solo dal valore degli attributi nei nodi figli.              three-address code



      Ci si può facilmente rendere conto che questa
  ◮
      condizione vale per tutti gli attributi dell’unico schema
      di traduzione finora visto come esempio.
      Uno schema in cui tutti gli attributi sono sintetizzati è
  ◮
      detto S-attributed.
      Se associamo ad uno schema S-attributed un ordine
  ◮
      di attraversamento posticipato del parse tree, la
      proprietà fondamentale viene automaticamente
      soddisfatta.
LFC
Esempio
     Attraversando in ordine anticipato il parse tree                     Il front-end di un
 ◮
                                                                          compilatore
     relativo alla stringa di input id + id × id si ottiene un            Architettura del front-end
                                                                          Symbol table

     processo che costruisce correttamente il syntax tree.                Traduzione guidata dalla
                                                                          sintassi
                                                                          Generazione del
     Il risultato completo del processo (incluso il valore
 ◮                                                                        three-address code


     associato a tutti gli attributi utilizzati nello schema)
     può essere descritto mediante il cosiddetto
     annotated parse tree, che mostriamo di seguito.

                         E E.node      <,>


      E E.node           +                 T T.node       <,>


      T T.node                  T T.node              F F.node

      F F.node                  F F.node              id <id,id.lexval>

     id <id,id.lexval>         id <id,id.lexval>
LFC
Esempio (continua)
                                                                 Il front-end di un
                                                                 compilatore
                                                                 Architettura del front-end
      Nell’annotated parse tree sono evidenziati gli attributi
  ◮                                                              Symbol table
                                                                 Traduzione guidata dalla
      presenti ai vari nodi nodi e il loro valore, che (in       sintassi
                                                                 Generazione del

      questo caso) è un puntatore ai nodi dell’abstract          three-address code



      syntax tree.
      Per semplificare l’interpretazione della figura,
  ◮
      abbiamo usato frecce diverse per rappresentare
      derivazioni (e dunque puntatori ai nodi del parse
      tree) e valori degli attributi (puntatori ai nodi
      dell’abstract syntax tree).
      Comprendere il modo in cui si arriva alla situazione
  ◮
      illustrata eseguendo le azioni è, in questo caso,
      molto semplice.
LFC
Esempio (continua)
                                                                 Il front-end di un
                                                                 compilatore
                                                                 Architettura del front-end
      Seguendo l’ordine di attraversamento posticipato ad
  ◮                                                              Symbol table
                                                                 Traduzione guidata dalla
      ogni nodo A si esegue l’azione prevista dallo schema       sintassi
                                                                 Generazione del

      in corrispondenza della produzione A → XYZ , dove          three-address code



      X , Y e Z sono i figli del nodo A.
      Si noti che, in realtà, non è necessario neppure
  ◮
      seguire strettamente l’ordine di attraversamento
      posticipato; è infatti sufficiente procedere in modo
      bottom up, dalle foglie verso la radice, con notevoli
      gradi di libertà sull’ordine preciso di esecuzione delle
      azioni descritte dallo schema.
      Si noti infine che il syntax tree completo è accessibile
  ◮
      attraverso l’attributo E .node nella radice parse tree.
LFC
Tipi di attributi (riprende)
      Ancora con riferimento alla generica produzione
  ◮
                                                                    Il front-end di un
                                                                    compilatore
                      A → X1 . . . Xi−1 Xi . . . Xk                 Architettura del front-end
                                                                    Symbol table
                                                                    Traduzione guidata dalla

      un attributo per Xi si dice ereditato se il suo valore        sintassi
                                                                    Generazione del
                                                                    three-address code
      dipende (eventualmente) dal valore di attributi
      ereditati di A e di attributi (ereditati o sintetizzati) di
      X1 , . . . , Xi−1 .
      Con riferimento al parse tree, un attributo a un nodo
  ◮
      è ereditato se il suo valore dipende dal valore di
      attributi ereditati al nodi genitore o attributi (di
      qualunque tipo) ai nodi che sono “fratelli maggiori”.
      Questa definizione non è la più generale ma, di fatto,
  ◮
      è quella operativa.
      In particolare, schemi di traduzione in cui vi sia (oltre
  ◮
      ad eventuali attributi sintetizzati) almeno un attributo
      ereditato secondo questa definizione vengono detti
      L-attributed.
LFC
Esempio
     Il seguente schema di traduzione, relativo alla
 ◮                                                               Il front-end di un
                                                                 compilatore
     grammatica per le espressioni modificata (per                    Architettura del front-end

     renderla adatta al parsing a discesa ricorsiva), è              Symbol table
                                                                     Traduzione guidata dalla
                                                                     sintassi
     L-attributed.                                                   Generazione del
                                                                     three-address code

     Si noti che alcuni nodi hanno due attributi,
 ◮
     sintetizzato (node) ed ereditato (inh).
 E → TE ′           {E ′ .inh ← T .node; E .node ← E ′ .node}
 E ′ → +TE1
          ′         {E1 .inh ← new node(′ +′ , E ′ .inh, T .node);
                        ′
                      ′ .node ← E ′ .node}
                    E              1
 E′ → ǫ             {E ′ .node ← E ′ .inh}
 T → FT ′           {T ′ .inh ← F .node; T .node ← T ′ .node}
 T ′ → ×FT1
          ′         {T1 .inh ← new node(′ ×′ , T ′ .inh, F .node);
                        ′

                    T ′ .node ← T1 .node}
                                    ′

 T′ → ǫ                 ′ .node ← T ′ .inh}
                    {T
 F → (E )           {F .node ← E .node}
 F → id             {F .node ← new leaf(id, id.lexval)}
LFC
Calcolabilità degli schemi (riprende)
                                                              Il front-end di un
                                                              compilatore
                                                              Architettura del front-end
      Vedremo un poco più avanti la logica che sta dietro
  ◮                                                           Symbol table
                                                              Traduzione guidata dalla
      lo schema L-attributed dell’esempio appena visto.       sintassi
                                                              Generazione del
                                                              three-address code
      Qui vogliamo riconsiderare per un istante il problema
  ◮
      generale della calcolabilità degli schemi.
      Esiste infatti un rischio insito nella definizione di
  ◮
      attributi con dipendenze “arbitrarie”, e cioè che non
      esista nessun ordine di esecuzione delle azioni in
      grado si soddisfare la proprietà fondamentale.
      Comunque, sempre nel caso generale, la
  ◮
      determinazione dell’esistenza di un tale ordine di
      esecuzione costituisce problema algoritmico molto
      difficile.
LFC
Calcolabilità degli schemi (continua)
                                                                Il front-end di un
                                                                compilatore
                                                                Architettura del front-end
                                                                Symbol table
                                                                Traduzione guidata dalla
                                                                sintassi

      È alla luce di ciò che dobbiamo “apprezzare” la bontà     Generazione del
  ◮                                                             three-address code

      degli schemi S-attributed e L-attributed.
      Infatti, l’esistenza (e la immediata determinazione) di
  ◮
      un ordine di esecuzione delle azioni previste dagli
      schemi è garantita proprio nei seguenti due casi:
            la grammatica ammette parsing di tipo LR e lo
        ◮

            schema è S-attributed, oppure
            la grammatica ammette parsing di tipo LL e lo
        ◮

            schema è L-attributed.
LFC
Parsing LR
      La prima combinazione, di grammatica analizzabile
  ◮                                                                Il front-end di un
                                                                   compilatore
      con parser LR unitamente a schema S-attributed, è            Architettura del front-end

      più semplice da comprendere e, di fatto, l’abbiamo           Symbol table
                                                                   Traduzione guidata dalla
                                                                   sintassi
      già vista.                                                   Generazione del
                                                                   three-address code

      Infatti, in un parser LR, il parse tree viene costruito
  ◮
      (anche solo implicitamente) dal basso verso l’alto.
      Questo implica che, nel momento in cui viene
  ◮
      eseguita una riduzione A → α, è possibile calcolare il
      valore degli attributi (sintetizzati) definiti nel nodo
      corrispondente ad A.
      Tali valori dipendono infatti solo dal valore di attributi
  ◮
      definiti ai nodi corrispondenti ai simboli di α, che
      sono stati a loro volta già calcolati.
      In altri termini, la situazione che abbiamo visto
  ◮
      nell’esempio è del tutto generale per grammatiche
      LR e schemi S-attributed.
LFC
Esempio
                                                                  Il front-end di un
                                                                  compilatore
     Riconsideriamo in dettaglio l’esempio relativo al
 ◮                                                                Architettura del front-end
                                                                  Symbol table
     parsing della stringa id + id × id, secondo lo schema        Traduzione guidata dalla
                                                                  sintassi

     (S-attributed) introdotto.                                   Generazione del
                                                                  three-address code


     Usando il parser SLR(1) che abbiamo discusso a
 ◮
     suo tempo, l’albero di parsing viene costruito dal
     basso verso l’alto.
     Se le azioni indicate dallo schema vengono eseguite
 ◮
     nel momento in cui si eseguono le corrispondenti
     riduzioni, i valori degli attributi sono tutti calcolabili
     (cioè le dipendenze sono tutte soddisfatte) come
     illustrato nelle due seguenti trasparenze (nelle quali,
     per semplicità, non sono state disegnate foglie
     isolate).
LFC
Esempio (continua)
                                                            E E.node
                                                                               Il front-end di un
                                T T.node                    T T.node           compilatore
                                                                               Architettura del front-end
                                                                               Symbol table
      F F.node                  F F.node                    F F.node           Traduzione guidata dalla
                                                                               sintassi
                                                                               Generazione del
      id <id,id.lexval>         id <id,id.lexval>          id <id,id.lexval>   three-address code
    Dopo la riduzione F−>id           Dopo T−>F               Dopo E−>T

                  E E.node

                  T T.node

                  F F.node                    F F.node

                  id <id,id.lexval>           id <id,id.lexval>
                                      Dopo F−>id


                  E E.node

                  T T.node                    T T.node

                  F F.node                    F F.node

                  id <id,id.lexval>           id <id,id.lexval>
                                Dopo T−>F
LFC
Esempio (continua)
        E E.node
                                                                             Il front-end di un
                                                                             compilatore
        T T.node                   T T.node              F F.node
                                                                             Architettura del front-end
                                                                             Symbol table
        F F.node                   F F.node              id <id,id.lexval>   Traduzione guidata dalla
                                                                             sintassi
                                                                             Generazione del
        id <id,id.lexval>         id <id,id.lexval>                          three-address code

                                  Dopo F−>id

        E E.node                              T T.node       <,>


        T T.node                   T T.node              F F.node

        F F.node                   F F.node              id <id,id.lexval>

        id <id,id.lexval>         id <id,id.lexval>
                                  Dopo T−>T*F

                            E E.node      <,>


        E E.node            +                 T T.node       <,>


        T T.node                   T T.node              F F.node

        F F.node                   F F.node              id <id,id.lexval>

        id <id,id.lexval>         id <id,id.lexval>
                                  Dopo E−>E+T
LFC
Aspetti implementativi
      Nel caso di parsing LR e schema S-attributed è
  ◮
                                                                Il front-end di un
      relativamente semplice mostrare come la                   compilatore
      costruzione esplicita del parse tree non sia richiesta.   Architettura del front-end
                                                                Symbol table

      Se infatti è vero che gli attributi sono logicamente      Traduzione guidata dalla
  ◮                                                             sintassi
                                                                Generazione del
      definiti ai nodi del parse tree, è però immediato          three-address code


      convincersi del fatto che, non appena un nodo N è
      stato collegato al padre P (a seguito di una
      riduzione), gli attributi definiti in N non servono più.
      Nella pratica è sufficiente una modifica allo stack
  ◮
      utilizzato dal parser LR, in modo che esso non
      memorizzi soltanto stati ma anche gli attributi
      associati al simbolo che corrisponde a quello stato.
      A quest’ultimo riguardo, possiamo notare come ogni
  ◮
      stato s dell’automa possa essere associato ad un
      simbolo della grammatica (si riconsideri, ad esempio,
      l’automa LR(0) relativo alla nostra grammatica per le
      espressioni, che riportiamo per comodità nella
      successiva trasparenza).
LFC
Aspetti implementativi (continua)
          accept                                                                        Il front-end di un
                                                                                        compilatore
                   $                                                                    Architettura del front-end

                       +                                                                Symbol table
                                                          T
                               I6
               I1                                                                       Traduzione guidata dalla
                                                                                        sintassi
                                                                                        Generazione del
                                                                                        three-address code

                           F
                                                  +
  E
               I3                                                                 I9
                                        id
      F
                               (
                                                                    )
              id
 I0                                                            I8
                                             I5                         I11
                       F            id
                                                                              ×
          (                                               id
  T                                          F
                               I4
                       T                              (
                                    (
                                                                        F         I10
               I2                                              I7
                                    ×
LFC
Schema di traduzione modificato
                                                                Il front-end di un
                                                                compilatore
                                                                Architettura del front-end
      Presentiamo di seguito lo schema di traduzione per
  ◮                                                             Symbol table
                                                                Traduzione guidata dalla
      la grammatica delle espressioni, modificato in modo        sintassi
                                                                Generazione del

      da utilizzare lo stack per memorizzare gli attributi.     three-address code



      Più precisamente, lo stack memorizza ora coppie
  ◮
       s, p , dove s è uno stato (un numero compreso fra 0
      e 11), mentre p è il valore della proprietà node (in
      pratica, un puntatore ad un nodo dell’abstract syntax
      tree in costruzione) del simbolo associato ad s.
      Si noti che non è più necessario distinguere differenti
  ◮
      occorrenze dello stesso simbolo (in quanto non
      vengono più indicati esplicitamente gli attributi nelle
      azioni dello schema).
LFC
Schema di traduzione modificato
                                                        Il front-end di un
                                                        compilatore
     → E +T   {p2 ← pop().p; pop(); p1 ← pop().p;
 E                                                      Architettura del front-end
                                                        Symbol table

                s ← GOTO(top().s, E );                  Traduzione guidata dalla
                                                        sintassi
                                                        Generazione del
                push( s, new node(′ +′ , p1 , p2 ) )}   three-address code


     →T       {p ← pop().p; s ← GOTO(top().s, E );
 E
                push( s, p )}
     → T ×F   {p2 ← pop().p; pop(); p1 ← pop().p;
 T
                s ← GOTO(top().s, T );
                push( s, new node(′ ×′ , p1 , p2 ) )}
     →F       {p ← pop().p; s ← GOTO(top().s, T );
 T
                push( s, p )}
     → (E )   {pop(); p ← pop().p; pop();
 F
                s ← GOTO(top().s, F ); push( s, p )}
     → id     {p ← pop().p; s ← GOTO(top().s, F );
 F
                push( s, new leaf(id, id.lexval) }
LFC
Parsing LL
      Passiamo ora ad unalizzare il secondo caso di           Il front-end di un
  ◮
                                                              compilatore
      calcolabiltà efficiente degli schemi di traduzione, e    Architettura del front-end
                                                              Symbol table

      precisamente quello relativo a schemi L-attributed in   Traduzione guidata dalla
                                                              sintassi

      unione con grammatiche di tipo LL.                      Generazione del
                                                              three-address code


      Anche in questo caso, l’esempio che ci guiderà sarà
  ◮
      quello relativo alla grammatica per le espressioni
      modificata con l’eliminazione della ricorsione a
      sinistra.
      Abbiamo già visto come lo schema sia L-attributed.
  ◮

      Ciò che ci resta da stabilire è dunque come debbano
  ◮
      essere sequenzializzati i frammenti di codice
      associati alle varie produzioni in modo da ottenere
      un processo di calcolo che produca effettivamente
      (ed efficientemente) l’output desiderato (l’abstract
      syntax tree).
LFC
Un semplice esempio
      Per capire il processo generale ci aiutiamo con il
  ◮
                                                                Il front-end di un
      semplice esempio di parsing della frase id1 + id2 :       compilatore
                                                                Architettura del front-end

                                   E                            Symbol table
                                                                Traduzione guidata dalla
                                                                sintassi
                                                                Generazione del
                                                                three-address code
                                             E′
                          T


                              T′
                     F             +         T         E’


                                                  T′
                              ǫ                        ǫ
                    id1                F


                                                  ǫ
                                       id2

      Come già per gli schemi S-attributed, le dipendenze
  ◮
      funzionali fra gli attributi (e dunque il flusso di dati
      necessario affinché il calcolo proceda in modo
      corretto) possono essere evidenziati utilizzando un
      annotated parse tree.
LFC
Esempio (continua)
      In questo caso, nel disegno dell’annotated parse tree
  ◮
                                                                        Il front-end di un
      abbiamo utilizzato una notazione semplificativa.                   compilatore
                                                                        Architettura del front-end
                                                                        Symbol table
      Ciò è dovuto al fatto che la maggior parte dei nodi ha
  ◮                                                                     Traduzione guidata dalla
                                                                        sintassi
      due attributi i quali, se scritti esplicitamente,                 Generazione del
                                                                        three-address code

      avrebbero reso la figura difficile da “leggere”.
      Abbiamo quindi usato la seguente convenzione:
  ◮
            una freccia           indica un arco del parse tree
        ◮

            (queste frecce sono sempre orientate verso il basso);
            una freccia           (orientata verso l’alto) indica che
        ◮

            l’attributo sintetizzato al nodo di partenza è usato per
            calcolare l’attributo sintetizzato al nodo di arrivo;
            una freccia           (orientata verso il basso) indica
        ◮

            che l’attributo ereditato al nodo di partenza è usato
            per calcolare l’attributo ereditato al nodo di arrivo;
            infine, una freccia           (orientata verso destra)
        ◮

            indica che l’attributo sintetizzato al nodo di partenza
            è usato per calcolare l’attributo ereditato al nodo di
            arrivo.
LFC
Esempio (continua)
      L’annotated parse tree:                                 Il front-end di un
  ◮
                                                              compilatore
                                                              Architettura del front-end
                                                              Symbol table
                                E                             Traduzione guidata dalla
                                                              sintassi
                                                              Generazione del
                                                              three-address code


               T                               E’



                                    +
       F             T’                        T         E’



                     ε                                   ε
       id 1                             F           T’



                                                    ε
                                        id 2
LFC
Organizzazione concreta del calcolo
      Nel caso di implementazione del parser LL mediante
  ◮                                                             Il front-end di un
                                                                compilatore
      procedure ricorsive, un’implementazione dello             Architettura del front-end

      schema di traduzione che rispetti le dipendenze           Symbol table
                                                                Traduzione guidata dalla
                                                                sintassi
      funzionali è alquanto semplice.                           Generazione del
                                                                three-address code

      Consideriamo infatti l’utilizzo di una generica
  ◮
      produzione A → X1 . . . Xi−1 Xi . . . Xk .
      Nel momento in cui la procedura per Xi viene
  ◮
      chiamata (dalla procedura per A), le procedure per
      X1 , . . . , Xi−1 sono già state chiamate e dunque
      eventuali valori da esse produtti possono essere
      passati in input a Xi .
      Questo consente proprio di implementare il flusso
  ◮
      dati richiesto dalla definizione di attributo ereditato,
      nel senso che, qualora Xi abbia attributi ereditati,
      questi dipendono da valori associati a (e producibili
      dalle procedure per) A e/o X1 , . . . , Xi−1 .
LFC
Organizzazione concreta del calcolo
(continua)
                                                              Il front-end di un
                                                              compilatore
      In realtà, è proprio la struttura delle procedure
  ◮                                                           Architettura del front-end
                                                              Symbol table

      ricorsive di un parser LL ad aver suggerito la nostra   Traduzione guidata dalla
                                                              sintassi

      definizione di attributo ereditato.                      Generazione del
                                                              three-address code


      Tornando alla nostra grammatica per le espressioni,
  ◮
      consideriamo, ad esempio, la procedura per E e le
      corrispondenti azioni dello schema:

           E ′ .inh ← T .node e     E .node ← E ′ .node.

      La procedura invocherà quindi dapprima la routine T
  ◮
      (che non necessita di “informazione ereditata”).
      Il valore restituito da quest’ultima (T .node) sarà
  ◮
      passato in input a E ′ come valore ereditato.
      Il valore restituito dalla chiamata ad E ′ sarà poi
  ◮
      anche il valore restituito da E .
LFC
Organizzazione concreta del calcolo
(continua)
                                                              Il front-end di un
                                                              compilatore
                                                              Architettura del front-end
                                                              Symbol table
                                                              Traduzione guidata dalla
                                                              sintassi
                                                              Generazione del
                                                              three-address code

      Si noti quindi che le procedure corrispondenti ai non
  ◮
      terminali sono più utilmente realizzate mediante
      funzioni.
      La funzione per E può quindi essere sinteticamente
  ◮
      espressa nel modo seguente:

            Tnode ← T ()
       1:
            return E ′ (Tnode )
       2:
LFC
Organizzazione concreta del calcolo
(continua)
                                                               Il front-end di un
                                                               compilatore
      Consideriamo, come altro esempio, la routine per E ′ .
  ◮                                                            Architettura del front-end
                                                               Symbol table

      Abbiamo appena visto che essa riceve in input un
  ◮                                                            Traduzione guidata dalla
                                                               sintassi

      argomento (attributo ereditato) che supporremo           Generazione del
                                                               three-address code


      venga assegnato al parametro formale inh.
      Il corpo della funzione può essere espresso nel
  ◮
      modo seguente (si ricordi la tabella di parsing per la
      produzione E ′ → +TE ′ ):
        1: if t ← get_next_token() =′ +′ then
               Tnode ← T ()
        2:
               inh1 ← new node(′ +′ , inh, Tnode )
        3:
               return E ′ (inh1 )
        4:
                           ′
        5: else if t =′ ) or t =′ $′ then
               return inh
        6:
        7: else
               error()
        8:
LFC
Organizzazione concreta del calcolo
(continua)
                                                                     Il front-end di un
      Nel caso di implementazione ricorsiva del parser,              compilatore
  ◮
                                                                     Architettura del front-end

      non è dunque necessario che venga                              Symbol table
                                                                     Traduzione guidata dalla

      preliminarmente costruito il parse tree.                       sintassi
                                                                     Generazione del
                                                                     three-address code
      Nel caso il parse tree sia disponibile non è
  ◮
      comunque difficile rendersi conto di come le
      dipendenze funzionali presenti negli schemi siano
      soddisfattibili mediante un attraversamento in ordine
      anticipato (preordine) del parse tree medesimo.
      Infatti, l’attaversamento in ordine anticipato consente
  ◮
      (sempre con riferimento alla generica produzione
      A → X1 . . . Xi−1 Xi . . . Xk ) di “raccogliere”
      l’informazione (attributi) relativa ad A e X1 , . . . , Xi−1
      prima di usarla (come informazione ereditata) nel
      calcolo degli attributi per Xi
      Si veda al riguardo l’annotated parse tree relativo
  ◮
      alla frase id1 + id2 .
LFC
Linguaggi formali e compilazione
                                          Il front-end di un
                                          compilatore
                                          Architettura del front-end
                                          Symbol table
                                          Traduzione guidata dalla
                                          sintassi
                                          Generazione del
                                          three-address code




Il front-end di un compilatore
     Architettura del front-end
     Symbol table
     Traduzione guidata dalla sintassi
     Generazione del three-address code
LFC
Il completamenento del front-end
      Obiettivo di quest’ultima parte del corso è la
  ◮
                                                                 Il front-end di un
      presentazione di alcuni semplici esempi di                 compilatore
                                                                 Architettura del front-end
      generazione di codice intermedio.                          Symbol table
                                                                 Traduzione guidata dalla

      Non intendiamo presentare una descrizione                  sintassi
  ◮
                                                                 Generazione del

      completa, sia per ragioni di tempo sia perché (a           three-address code



      nostro avviso) questa attività è molto meno
      interessante dal punto di vista delle idee sottostanti.
      Gli esempi hanno dunque il solo scopo di illustrare
  ◮
      l’approccio base per la generazione di codice
      intermedio, ignorando quegli aspetti che
      richiederebbero un ragionamento più approfondito.
      Faremo inoltre tre ipotesi molto forti, e precisamente:
  ◮
      (1) di disporre dell’abstract syntax tree delle stringhe
      da tradurre; (2) che il risultato debba essere
      presentato sotto forma di file alfanumerico, (3) che
      esista un unico tipo di dato nei programmi sorgenti
      (ad esempio, numeri interi).
LFC
Il modello di computer intermedio
      Il codice intermedio nel quale dovrà essere tradotto il
  ◮
                                                                Il front-end di un
      programma sorgente sarà il cosiddetto three-address       compilatore
      code.                                                     Architettura del front-end
                                                                Symbol table

      Naturalmente, prima di descrivere gli esempi di           Traduzione guidata dalla
  ◮                                                             sintassi
                                                                Generazione del
      traduzione, dobbiamo precisare che cosa si intenda        three-address code


      con codice a tre indirizzi, cioè a quale modello
      astratto di calcolatore tale codice faccia riferimento.
      Ipotizziamo dunque che la macchina astratta sia in
  ◮
      grado di eseguire semplici istruzioni caratterizzate da
      un codice operativo e da al più tre indirizzi per gli
      operandi.
      Fra le istruzioni disponibili troviamo le operazioni
  ◮
      logico/aritmetiche binarie e unarie, le istruzioni di
      salto (condizionato o incondizionato), il semplice
      assegnamento, la chiamata di procedura e la
      gestione di array lineari.
      Ogni istruzione può essere preceduta da una o più
  ◮
      etichette (stringhe letterali)
LFC
Istruzioni del three-address code
                                                                 Il front-end di un
                                                                 compilatore
      Più precisamente, allo scopo di presentare gli             Architettura del front-end
  ◮                                                              Symbol table

      esempi, noi considereremo solo le seguenti                 Traduzione guidata dalla
                                                                 sintassi

      istruzioni, il cui significato dovrebbe risultare chiaro:   Generazione del
                                                                 three-address code


            A ← B op C, dove op ∈ {+, −, ×, /} e A, B e C sono
        ◮

            identificatori definiti dall’utente nel programma
            sorgente oppure temporanee generate dal parser;
            A ← B, dove A e B sono definiti come al punto
        ◮

            precedente;
            goto L, dove L è un’etichetta generata dal parser;
        ◮

            if A goto L, dove A è un identificatore definito
        ◮

            dall’utente oppure una temporanea generata dal
            parser ed L è un’etichetta generata dal parser;
            ifFalse A goto L, dove A ed L sono definiti come
        ◮

            al punto precedente.
LFC
Ulteriori assunzioni
      Ipotizzeremo che il parser possa invocare una
  ◮
                                                                    Il front-end di un
      funzione per generare identificatori univoci, come             compilatore
      pure una funzione per generare etichette univoche.            Architettura del front-end
                                                                    Symbol table

      Assumeremo inoltre la disponibilità di una funzione,          Traduzione guidata dalla
  ◮                                                                 sintassi
                                                                    Generazione del
      che chiameremo emit(), che stampa la stringa                  three-address code


      passata come parametro (che rappresenta una
      porzione del programma in three-address code) su
      un opportuno supporto di output.
      Assumeremo infine che il generico nodo dell’abstract
  ◮
      syntax tree abbia la seguente struttura:
            un campo label che caratterizza il tipo di nodo
        ◮

            (assegnamento, operatore, comando if, ...);
            se significativo (ad esempio nel caso di identificatore
        ◮

            o operatore), un puntatore alla symbol table per il
            corrispondente valore lessicale, accessibie mediante
            la funzione lexval;
            uno o più puntatori ai nodi che rappresentano i
        ◮

            componenti del costrutto, accessibili mediante i
            campi c1, c2, ...
LFC
La struttura generale del traduttore
      Con le ipotesi fatte, il traduttore (da syntax tree a
  ◮                                                           Il front-end di un
                                                              compilatore
      three-address code) può essere organizzato come         Architettura del front-end

      procedura ricorsiva il cui corpo è costituito           Symbol table
                                                              Traduzione guidata dalla
                                                              sintassi
      essenzialmente da una “grossa” istruzione case (o,      Generazione del
                                                              three-address code

      se si preferisce, switch), che analizza il nodo p
      passato come parametro e, a seconda del tipo,
      esegue operazioni diverse.
      Data la struttura del parse tree, la generazione del
  ◮
      codice per un dato nodo implicherà poi una o più
      chiamate ricorsive per la generazione del codice
      associato ai nodi figli.
      La procedura, che chiameremo gencode, riceve un
  ◮
      ulteriore parametro (RES) che è una stringa
      (eventualmente vuota) alla quale (vista come nome
      di variabile) deve essere assegnato il risultato
      calcolato dal codice generato per il nodo p.
LFC




Procedure 1 gencode(string RES, AST ∗ p)   Il front-end di un
                                           compilatore
  tag ← (p → label)                        Architettura del front-end
                                           Symbol table

  case tag of                              Traduzione guidata dalla
                                           sintassi
                                           Generazione del
    id : . . .                             three-address code


    assignment : . . .
    comparison : . . .
    binaryop : . . .
    seq : . . .
    if : . . .
    ifElse : . . .
    while : . . .
  ...
    default : error ()
  end
Linguaggi Formali e Compilazione: Frontend
Linguaggi Formali e Compilazione: Frontend
Linguaggi Formali e Compilazione: Frontend
Linguaggi Formali e Compilazione: Frontend
Linguaggi Formali e Compilazione: Frontend
Linguaggi Formali e Compilazione: Frontend
Linguaggi Formali e Compilazione: Frontend
Linguaggi Formali e Compilazione: Frontend
Linguaggi Formali e Compilazione: Frontend
Linguaggi Formali e Compilazione: Frontend
Linguaggi Formali e Compilazione: Frontend
Linguaggi Formali e Compilazione: Frontend
Linguaggi Formali e Compilazione: Frontend
Linguaggi Formali e Compilazione: Frontend

More Related Content

What's hot

عربي
عربيعربي
عربيmalakgh
 
معايير الجودة في العملية التعليمية
معايير الجودة في العملية التعليميةمعايير الجودة في العملية التعليمية
معايير الجودة في العملية التعليميةOsama El Shareif
 
الادب والنصوص للصف الرابع الادبي
الادب والنصوص للصف الرابع الادبيالادب والنصوص للصف الرابع الادبي
الادب والنصوص للصف الرابع الادبيAyad Haris Beden
 
إعادة هندسة منظومات العملBusiness process re engineering
إعادة هندسة منظومات العملBusiness process re engineeringإعادة هندسة منظومات العملBusiness process re engineering
إعادة هندسة منظومات العملBusiness process re engineeringBeta-Research.org
 

What's hot (6)

عربي
عربيعربي
عربي
 
معايير الجودة في العملية التعليمية
معايير الجودة في العملية التعليميةمعايير الجودة في العملية التعليمية
معايير الجودة في العملية التعليمية
 
الاحتياجات التدريبية
الاحتياجات التدريبيةالاحتياجات التدريبية
الاحتياجات التدريبية
 
الادب والنصوص للصف الرابع الادبي
الادب والنصوص للصف الرابع الادبيالادب والنصوص للصف الرابع الادبي
الادب والنصوص للصف الرابع الادبي
 
إعادة هندسة منظومات العملBusiness process re engineering
إعادة هندسة منظومات العملBusiness process re engineeringإعادة هندسة منظومات العملBusiness process re engineering
إعادة هندسة منظومات العملBusiness process re engineering
 
15/4/2015 محاضرة الرسم القرآني - للشيخ عبدالرحمن مارديني
15/4/2015 محاضرة الرسم القرآني - للشيخ عبدالرحمن مارديني15/4/2015 محاضرة الرسم القرآني - للشيخ عبدالرحمن مارديني
15/4/2015 محاضرة الرسم القرآني - للشيخ عبدالرحمن مارديني
 

Viewers also liked

5 Trasporto Affidabile Teoria
5 Trasporto Affidabile Teoria5 Trasporto Affidabile Teoria
5 Trasporto Affidabile TeoriaMajong DevJfu
 
Sistemi Operativi: Struttura - Lezione 04
Sistemi Operativi: Struttura - Lezione 04Sistemi Operativi: Struttura - Lezione 04
Sistemi Operativi: Struttura - Lezione 04Majong DevJfu
 
Sistemi Operativi: Meccanismi - Lezione 03
Sistemi Operativi: Meccanismi - Lezione 03Sistemi Operativi: Meccanismi - Lezione 03
Sistemi Operativi: Meccanismi - Lezione 03Majong DevJfu
 
esercizio sigda n 11
esercizio sigda n 11esercizio sigda n 11
esercizio sigda n 11Majong DevJfu
 
2 sistemi informativi d azienda
2 sistemi informativi d azienda2 sistemi informativi d azienda
2 sistemi informativi d aziendaMajong DevJfu
 
Architettura dei Calcolatori Subroutines80x86
Architettura dei Calcolatori Subroutines80x86Architettura dei Calcolatori Subroutines80x86
Architettura dei Calcolatori Subroutines80x86Majong DevJfu
 
4 Livello Ip Parte1 Color
4 Livello Ip Parte1 Color4 Livello Ip Parte1 Color
4 Livello Ip Parte1 ColorMajong DevJfu
 
Traffic Shaping Su Linux
Traffic Shaping Su LinuxTraffic Shaping Su Linux
Traffic Shaping Su LinuxMajong DevJfu
 
Sistemi Operativi: Struttura avanzata - Lezione 05
Sistemi Operativi: Struttura avanzata - Lezione 05Sistemi Operativi: Struttura avanzata - Lezione 05
Sistemi Operativi: Struttura avanzata - Lezione 05Majong DevJfu
 
Sistemi Operativi: Processi - Lezione 07
Sistemi Operativi: Processi - Lezione 07Sistemi Operativi: Processi - Lezione 07
Sistemi Operativi: Processi - Lezione 07Majong DevJfu
 
Calcolo Numerico - 2 - Numeri Di Macchina
Calcolo Numerico - 2 - Numeri Di MacchinaCalcolo Numerico - 2 - Numeri Di Macchina
Calcolo Numerico - 2 - Numeri Di MacchinaMajong DevJfu
 
Introduzione a Linguaggi formali e compilazione
Introduzione a Linguaggi formali e compilazioneIntroduzione a Linguaggi formali e compilazione
Introduzione a Linguaggi formali e compilazioneMajong DevJfu
 
Sistemi Operativi: Introduzione - Lezione 01
Sistemi Operativi: Introduzione - Lezione 01Sistemi Operativi: Introduzione - Lezione 01
Sistemi Operativi: Introduzione - Lezione 01Majong DevJfu
 
Linguaggi Formali e Compilazione: Automi
Linguaggi Formali e Compilazione: AutomiLinguaggi Formali e Compilazione: Automi
Linguaggi Formali e Compilazione: AutomiMajong DevJfu
 

Viewers also liked (20)

5 Trasporto Affidabile Teoria
5 Trasporto Affidabile Teoria5 Trasporto Affidabile Teoria
5 Trasporto Affidabile Teoria
 
Sistemi Operativi: Struttura - Lezione 04
Sistemi Operativi: Struttura - Lezione 04Sistemi Operativi: Struttura - Lezione 04
Sistemi Operativi: Struttura - Lezione 04
 
E6 Concorre
E6 ConcorreE6 Concorre
E6 Concorre
 
3 H2 N Parte3
3 H2 N Parte33 H2 N Parte3
3 H2 N Parte3
 
Sistemi Operativi: Meccanismi - Lezione 03
Sistemi Operativi: Meccanismi - Lezione 03Sistemi Operativi: Meccanismi - Lezione 03
Sistemi Operativi: Meccanismi - Lezione 03
 
esercizio sigda n 11
esercizio sigda n 11esercizio sigda n 11
esercizio sigda n 11
 
2 sistemi informativi d azienda
2 sistemi informativi d azienda2 sistemi informativi d azienda
2 sistemi informativi d azienda
 
Architettura dei Calcolatori Subroutines80x86
Architettura dei Calcolatori Subroutines80x86Architettura dei Calcolatori Subroutines80x86
Architettura dei Calcolatori Subroutines80x86
 
8 Www2009 Parte1
8 Www2009 Parte18 Www2009 Parte1
8 Www2009 Parte1
 
4 Livello Ip Parte1 Color
4 Livello Ip Parte1 Color4 Livello Ip Parte1 Color
4 Livello Ip Parte1 Color
 
Traffic Shaping Su Linux
Traffic Shaping Su LinuxTraffic Shaping Su Linux
Traffic Shaping Su Linux
 
Sistemi Operativi: Struttura avanzata - Lezione 05
Sistemi Operativi: Struttura avanzata - Lezione 05Sistemi Operativi: Struttura avanzata - Lezione 05
Sistemi Operativi: Struttura avanzata - Lezione 05
 
6 Dns Parte2
6 Dns Parte26 Dns Parte2
6 Dns Parte2
 
9 Ftp Telnet Email
9 Ftp Telnet Email9 Ftp Telnet Email
9 Ftp Telnet Email
 
esercizio sigda n 8
esercizio sigda n 8esercizio sigda n 8
esercizio sigda n 8
 
Sistemi Operativi: Processi - Lezione 07
Sistemi Operativi: Processi - Lezione 07Sistemi Operativi: Processi - Lezione 07
Sistemi Operativi: Processi - Lezione 07
 
Calcolo Numerico - 2 - Numeri Di Macchina
Calcolo Numerico - 2 - Numeri Di MacchinaCalcolo Numerico - 2 - Numeri Di Macchina
Calcolo Numerico - 2 - Numeri Di Macchina
 
Introduzione a Linguaggi formali e compilazione
Introduzione a Linguaggi formali e compilazioneIntroduzione a Linguaggi formali e compilazione
Introduzione a Linguaggi formali e compilazione
 
Sistemi Operativi: Introduzione - Lezione 01
Sistemi Operativi: Introduzione - Lezione 01Sistemi Operativi: Introduzione - Lezione 01
Sistemi Operativi: Introduzione - Lezione 01
 
Linguaggi Formali e Compilazione: Automi
Linguaggi Formali e Compilazione: AutomiLinguaggi Formali e Compilazione: Automi
Linguaggi Formali e Compilazione: Automi
 

More from Majong DevJfu

9 - Architetture Software - SOA Cloud
9 - Architetture Software - SOA Cloud9 - Architetture Software - SOA Cloud
9 - Architetture Software - SOA CloudMajong DevJfu
 
8 - Architetture Software - Architecture centric processes
8 - Architetture Software - Architecture centric processes8 - Architetture Software - Architecture centric processes
8 - Architetture Software - Architecture centric processesMajong DevJfu
 
7 - Architetture Software - Software product line
7 - Architetture Software - Software product line7 - Architetture Software - Software product line
7 - Architetture Software - Software product lineMajong DevJfu
 
6 - Architetture Software - Model transformation
6 - Architetture Software - Model transformation6 - Architetture Software - Model transformation
6 - Architetture Software - Model transformationMajong DevJfu
 
5 - Architetture Software - Metamodelling and the Model Driven Architecture
5 - Architetture Software - Metamodelling and the Model Driven Architecture5 - Architetture Software - Metamodelling and the Model Driven Architecture
5 - Architetture Software - Metamodelling and the Model Driven ArchitectureMajong DevJfu
 
4 - Architetture Software - Architecture Portfolio
4 - Architetture Software - Architecture Portfolio4 - Architetture Software - Architecture Portfolio
4 - Architetture Software - Architecture PortfolioMajong DevJfu
 
3 - Architetture Software - Architectural styles
3 - Architetture Software - Architectural styles3 - Architetture Software - Architectural styles
3 - Architetture Software - Architectural stylesMajong DevJfu
 
2 - Architetture Software - Software architecture
2 - Architetture Software - Software architecture2 - Architetture Software - Software architecture
2 - Architetture Software - Software architectureMajong DevJfu
 
1 - Architetture Software - Software as a product
1 - Architetture Software - Software as a product1 - Architetture Software - Software as a product
1 - Architetture Software - Software as a productMajong DevJfu
 
10 - Architetture Software - More architectural styles
10 - Architetture Software - More architectural styles10 - Architetture Software - More architectural styles
10 - Architetture Software - More architectural stylesMajong DevJfu
 

More from Majong DevJfu (20)

9 - Architetture Software - SOA Cloud
9 - Architetture Software - SOA Cloud9 - Architetture Software - SOA Cloud
9 - Architetture Software - SOA Cloud
 
8 - Architetture Software - Architecture centric processes
8 - Architetture Software - Architecture centric processes8 - Architetture Software - Architecture centric processes
8 - Architetture Software - Architecture centric processes
 
7 - Architetture Software - Software product line
7 - Architetture Software - Software product line7 - Architetture Software - Software product line
7 - Architetture Software - Software product line
 
6 - Architetture Software - Model transformation
6 - Architetture Software - Model transformation6 - Architetture Software - Model transformation
6 - Architetture Software - Model transformation
 
5 - Architetture Software - Metamodelling and the Model Driven Architecture
5 - Architetture Software - Metamodelling and the Model Driven Architecture5 - Architetture Software - Metamodelling and the Model Driven Architecture
5 - Architetture Software - Metamodelling and the Model Driven Architecture
 
4 - Architetture Software - Architecture Portfolio
4 - Architetture Software - Architecture Portfolio4 - Architetture Software - Architecture Portfolio
4 - Architetture Software - Architecture Portfolio
 
3 - Architetture Software - Architectural styles
3 - Architetture Software - Architectural styles3 - Architetture Software - Architectural styles
3 - Architetture Software - Architectural styles
 
2 - Architetture Software - Software architecture
2 - Architetture Software - Software architecture2 - Architetture Software - Software architecture
2 - Architetture Software - Software architecture
 
1 - Architetture Software - Software as a product
1 - Architetture Software - Software as a product1 - Architetture Software - Software as a product
1 - Architetture Software - Software as a product
 
10 - Architetture Software - More architectural styles
10 - Architetture Software - More architectural styles10 - Architetture Software - More architectural styles
10 - Architetture Software - More architectural styles
 
Uml3
Uml3Uml3
Uml3
 
Uml2
Uml2Uml2
Uml2
 
6
66
6
 
5
55
5
 
4 (uml basic)
4 (uml basic)4 (uml basic)
4 (uml basic)
 
3
33
3
 
2
22
2
 
1
11
1
 
Tmd template-sand
Tmd template-sandTmd template-sand
Tmd template-sand
 
26 standards
26 standards26 standards
26 standards
 

Linguaggi Formali e Compilazione: Frontend

  • 1. LFC Il front-end di un compilatore Architettura del front-end Symbol table Traduzione guidata dalla sintassi Generazione del Linguaggi formali e compilazione three-address code Corso di Laurea in Informatica A.A. 2008/2009
  • 2. LFC Linguaggi formali e compilazione Il front-end di un compilatore Architettura del front-end Symbol table Traduzione guidata dalla sintassi Generazione del three-address code Il front-end di un compilatore Architettura del front-end Symbol table Traduzione guidata dalla sintassi Generazione del three-address code
  • 3. LFC Linguaggi formali e compilazione Il front-end di un compilatore Architettura del front-end Symbol table Traduzione guidata dalla sintassi Generazione del three-address code Il front-end di un compilatore Architettura del front-end Symbol table Traduzione guidata dalla sintassi Generazione del three-address code
  • 4. LFC Componenti del front-end Il front-end di un compilatore Architettura del front-end Symbol table Traduzione guidata dalla Il front-end di un compilatore (cioè la parte che ◮ sintassi Generazione del termina con la creazione di una rappresentazione three-address code intermedia del programma) è costituita dai seguenti moduli: analizzatore lessicale; ◮ parser; ◮ symbol table; ◮ generatore di codice intermedio. ◮ Per ragioni di tempo non ci potremo occupare, in ◮ questo corso, della gestione degli errori.
  • 5. LFC Il modello di front-end Il front-end di un compilatore Architettura del front-end Symbol table Traduzione guidata dalla La seguente figura illustra un tipico schema di ◮ sintassi Generazione del organizzazione del front-end di un compilatore (tratto three-address code da Aho, Lam, Sethi, Ullman (2007)). Richiesta token three-addreess Programma Albero code sorgente Generatore di Analizzatore sintattico Parser codice intermedio lessicale Token Gestore della symbol table
  • 6. LFC Analizzatore lessicale Il front-end di un L’analizzatore lessicale ha il fondamentale ruolo di ◮ compilatore Architettura del front-end leggere la sequenza di caratteri che forma l’input file Symbol table Traduzione guidata dalla e inviare al parser una sequenza di token, oggetti sintassi Generazione del astratti che coincidono essenzialmente con i simboli three-address code terminali del linguaggio (ma vedremo subito dopo una definizione precisa). Ci sono però altri compiti che deve svolgere ◮ l’analizzatore lessicale: riconoscere e “filtrare” commenti, spazi e altri ◮ caratteri di separazione; associare gli eventuali errori trovati da altri moduli del ◮ compilatore (in particolare dal parser) alle posizioni (righe di codice) dove tali errori si sono verificati allo scopo di emettere appropriati messaggi diagnostici; procedere all’eventuale espansione delle macro (se il ◮ compilatore le prevede).
  • 7. LFC Interazione parser-lexical analyzer Il front-end di un L’analizzatore lessicale è normalmente programmato compilatore ◮ Architettura del front-end come “routine” del parser. Symbol table Traduzione guidata dalla sintassi Quando il parser deve leggere il prossimo simbolo di ◮ Generazione del three-address code input (si ricordino gli esempi di parser, top down o bottom up indifferentemente, visti nel gruppo di lucidi relativi al parsing di linguaggi liberi) esegue in realtà una chiamata all’analizzatore lessicale; quest’ultimo si attiva leggendo la “prossima” ◮ porzione di input fino a quando non riconosce un simbolo terminale della grammatica, che viene restituito al parser; ciò che viene propriamente restituito è un token ◮ name, una delle due componenti che costituiscono il token vero e proprio.
  • 8. LFC Token Il front-end di un compilatore Un token è un oggetto astratto caratterizzato da due Architettura del front-end ◮ Symbol table attributi, un token name (obbligatorio) e un valore Traduzione guidata dalla sintassi opzionale. Generazione del three-address code Ecco alcuni esempi di sequenze di caratteri ◮ riconosciuti come token da un compilatore C++: “0.314E+1”, cui corrisponde la coppia (token) in cui ◮ il nome è number e il valore è il numero razionale 3.14, opportunamente rappresentato; “x1”, cui corrisponde un token in cui il nome è id e il ◮ cui valore è, a sua volta, un insieme di informazioni elementari (la stringa di caratteri che forma l’identificatore, il suo tipo, il punto del programma dove è stato definito); “else”, cui corrisponde il solo token name else. ◮
  • 9. LFC Token name Il front-end di un Il nome del token serve principalmente al parser, in compilatore ◮ Architettura del front-end quanto sono propriamente i token name che Symbol table Traduzione guidata dalla coincidono con i simboli terminali della grammatica. sintassi Generazione del three-address code Il valore dei token è invece (in prima istanza) non ◮ rilevante dal punto di vista della correttezza sintattica del programma. Ad esempio, le frasi “x<10” e “pippo!=35” sono del ◮ tutto equivalenti e riconducibili alla frase generica in cui il valore di un identificatore è confrontato con un numero. Una tale frase può quindi essere astrattamente ◮ descritta come “id comparison number”, dove abbiamo utilizzato il nome comparison per il token che descrive gli operatori relazionali.
  • 10. LFC Token value Il front-end di un compilatore Architettura del front-end Symbol table Il valore di un token gioca il ruolo principale nella ◮ Traduzione guidata dalla sintassi traduzione del codice. Generazione del three-address code Con riferimento agli esempi del lucido precedente: ◮ di un identificatore è importante sapere (tra gli altri) il ◮ tipo, perché da questo dipende (in particolare) la quantità di memoria allocare; di un operatore di confronto è necessario sapere ◮ esattamente di quale confronto si tratta, per poter predisporre il test e l’eventuale istruzione di salto opportuna; di una costante numerica è necessario sapere il ◮ valore per effettuare l’inizializzazione corretta.
  • 11. LFC Token (continua) Il front-end di un compilatore Architettura del front-end Il progettista del linguaggio stabilisce quali debbano ◮ Symbol table Traduzione guidata dalla essere considerati i simboli terminali del linguaggi, e sintassi Generazione del quindi i token. three-address code Naturalmente, a tali simboli potranno corrispondere ◮ (in generale) molte stringhe alfanumeriche nel codice sorgente (si pensi ai token che descrivono numeri o identificatori). Il progettista deve quindi stabilire esattamente anche ◮ la mappatura fra stringhe e token name/terminali del linguaggio. Tale mappatura viene tipicamente descritta, come ◮ già sappiamo, mediante espressioni regolari.
  • 12. LFC Pattern, lessemi e terminali Il front-end di un compilatore Architettura del front-end Symbol table Traduzione guidata dalla Una descrizione ben formalizzata di quanto appena sintassi ◮ Generazione del three-address code ricordato può essere fatta utilizzando i concetti di lessema e pattern. Un pattern è una descrizione (formale o informale, ◮ ma tipicamente esprimibile mediante espressioni regolari) delle stringhe che devono essere riconosciute (dall’analizzatore lessicale) come istanze di un determinato simbolo terminale. Le stringhe stesse sono dette lessemi. ◮
  • 13. LFC Pattern, lessemi e terminali (continua) Il front-end di un compilatore Architettura del front-end Symbol table Traduzione guidata dalla La seguente tabella illustra, mediante alcuni esempi, ◮ sintassi Generazione del i concetti che stiamo analizzando. three-address code Token name Pattern Esempio di lessema id letter(letter | digit)∗ pippo1 number [+ | -]nzd digit∗ [.]digit∗ -3.14 comparison <, >, <=, >=, =, != < literal “[:alpha:]” “Pi greco” if if if while while while Si noti che, per ragioni di spazio, il pattern che descrive il token number non prevede la notazione scientifica.
  • 14. LFC Linguaggi formali e compilazione Il front-end di un compilatore Architettura del front-end Symbol table Traduzione guidata dalla sintassi Generazione del three-address code Il front-end di un compilatore Architettura del front-end Symbol table Traduzione guidata dalla sintassi Generazione del three-address code
  • 15. LFC Symbol table Come evidenziato dalla struttura del front-end, ◮ Il front-end di un l’analizzatore lessicale presenta al parser una compilatore Architettura del front-end funzione, get-next-token(), che il parser invoca Symbol table Traduzione guidata dalla quando deve leggere il prossimo simbolo terminale sintassi Generazione del della grammatica. three-address code Il risultato esplicito della get-next-token() è il ◮ nome del prossimo token. Nei casi in cui è significativo anche il valore (fanno ◮ eccezione le keyword, simboli di separazione come il punto e virgola e gli operatori), l’analizzatore lessicale restituisce anche (tipicamente) un puntatore alla symbol table dove è stato memorizzato il valore stesso. In pratica, la funzione get-next-token() può ◮ restituire una coppia nome, valore , oppure (come nel caso di Lex/Yacc) il valore viene restituito attraverso una variabile globale.
  • 16. LFC Esempio Il front-end di un Nel caso del seguente frammento di programma C++ ◮ compilatore Architettura del front-end while (i<10) { Symbol table Traduzione guidata dalla sintassi i=i+1; Generazione del three-address code } l’analizzatore lessicale restituisce al parser la seguente sequenza di token: while id, pointer leftpar assign_op id, pointer id, pointer comparison, pointer plus_op number, pointer number, pointer rightpar semicolon leftbrace rightbrace
  • 17. LFC Symbol table (continua) Il front-end di un Come già detto, le informazioni raccolte nella symbol compilatore ◮ Architettura del front-end table, relative ai token ai quali è associato un valore, Symbol table Traduzione guidata dalla sono fondamentali nella generazione del codice, ma sintassi Generazione del three-address code non solo. Mediante tali informazioni il front-end può anche ◮ completare l’analisi sintattica del programma e, dicendo questo, intendiamo procedere a chiarire un aspetto che potrebbe essere rimasto oscuro, In precedenza abbiamo affermato che, dal punto di ◮ vista sintattico, frasi come i < 10 o j < 10 sono del tutto equivalenti, Questo non è del tutto vero e non solo perché esse ◮ (in termini di codice generato) significano cose diverse.
  • 18. LFC Symbol table (continua) Infatti, in molti linguaggio di programmazione ◮ Il front-end di un moderni, una variabile deve essere dichiarata prima compilatore Architettura del front-end di poter essere utilizzata. Symbol table Traduzione guidata dalla sintassi Se, con riferimento all’esempio appena fatto, ◮ Generazione del three-address code l’identificatore i fosse stato dichiarato mentre j no, allora la presenza della seconda frase genererebbe un errore. Usare una variabile non definita è un errore ◮ sintattico? Si e no. Teoricamente sarebbe possibile inserire nella ◮ grammatica il vincolo che ogni variabile deve essere dichiarata prima dell’uso. Questo però richiederebbe l’uso di produzioni (e ◮ quindi di grammatiche) non context-free, per le quali non è neppure garantita l’esistenza di algoritmi di parsing.
  • 19. LFC Symbol table (continua) La soluzione adottata è quindi di non inserire tali ◮ Il front-end di un regole nella grammatica ma di controllare il compilatore Architettura del front-end soddisfacimento dei vincoli in altro modo, cioè Symbol table Traduzione guidata dalla proprio mediante l’uso della symbol-table. sintassi Generazione del three-address code Più precisamente, quando il parser incontra un ◮ identificatore nel contesto, poniamo, di un’espressione, va a verificare nella symbol table se tale identificatore è già presente (ed eventualmente con quale tipo è stato dichiarato). Se non è presente (ovvero se il tipo non è quello ◮ “giusto”) segnala una condizione di errore, che considereremo di natura semantica. Un altro esempio di vincolo non facilmente ◮ descrivibile con produzioni libere è quello della concordanza fra numero di parametri formali di una procedura e argomenti ad essa passati nelle varie chiamate.
  • 20. LFC Symbol table (continua) Il front-end di un compilatore Precisiamo bene come tali controlli vengono svolti ◮ Architettura del front-end Symbol table dal front-end usando la symbol table, che può essere Traduzione guidata dalla sintassi strutturata in modo tale da soddisfare la più ampia Generazione del three-address code esigenza rappresentata dalla implementazione delle regole di ambiente o di visibilità dei simboli (in inglese scoping rule). Ricordiamo che lo scope di una variabile (e della ◮ dichiarazione che tale variabile introduce) è la porzione di programma in cui tale variabile è visibile (e dunque utilizzabile). Lo stesso identificare (un caso comune è i) può ◮ essere definito più volte in un programma, ma ad esso saranno associati ambienti diversi.
  • 21. LFC Symbol table (continua) Nei moderni linguaggi di programmazione l’ambiente ◮ Il front-end di un compilatore di una variabile è “quasi sempre” determinabile in Architettura del front-end modo statico, cioè guardando il testo del programma Symbol table Traduzione guidata dalla sintassi (se escludiamo alcuni casi ricercati). Generazione del three-address code In particolare, le regole di visibilità degli identificatori ◮ riflettono la struttura di annidamento dei blocchi di programma (incluse le procedure e le classi, se previste). Come detto, in quaesto caso si parla di static (o ◮ lexical) scoping. Dal punto di vista del compilatore, la nozione di ◮ ambiente può essere realizzata definendo una nuova symbol table all’ingresso di ogni blocco di programma e “concatenando” le simbol table in una lista, in cui la testa è sempre l’ultima symbol table definita.
  • 22. LFC Esempio Il front-end di un compilatore Architettura del front-end Symbol table Si consideri, come esempio, il seguente semplice ◮ Traduzione guidata dalla sintassi frammento di programma C++: Generazione del three-address code 1. { int i,j; 2. cin » i » j; { int i; float x; 3. 4. i=1; 5. x=2.0*j; 6. cout « i « “: ” « x; } 7. 8. cout « i « “: ” « j; 9. }
  • 23. LFC Esempio (continua) Chiameremo ambiente la lista di symbol table. ◮ Il front-end di un compilatore All’ingresso del blocco più esterno viene creata una ◮ Architettura del front-end symbol table S1 (essenzialmente un dizionario) che Symbol table Traduzione guidata dalla sintassi diviene la testa di ambiente. Generazione del three-address code Ogni volta che viene incontrata una definizione, ◮ l’identificatore viene inserito nella symbol table alla testa di ambiente. Ne consegue che gli identificatori i e j di riga 1 ◮ vengono inseriti in S1 . All’ingresso del blocco interno (riga 3) viene creata ◮ (e inserita in testa ad ambiente) una seconda symbol table, S2 , nella quale vengono poi inseriti gli identificatori i e x di riga 3 (col loro tipo). All’uscita di ogni blocco, il puntatore alla testa di ◮ ambiente viene fatto avanzare, con l’effetto di rendere inaccessibile la symbol table di testa.
  • 24. LFC Esempio (continua) Il front-end di un compilatore Architettura del front-end Quando nel programma si incontra un riferimento ad ◮ Symbol table Traduzione guidata dalla una variabile, l’identificatore relativo viene cercato sintassi Generazione del nella lista ambiente, seguendo il naturale ordine. three-address code Ad esempio, il riferimento alla variabile i di riga 4 ◮ viene risolto con un lookup alla symbol table di testa (S2 ), che contiene un’entry con chiave i. Si noti quindi che, nel blocco interno, la variabile i ◮ definita alla riga 1 diviene inaccessibile. Il riferimento alla variabile j di riga 5 richiede invece ◮ il lookup di due symbol table, nell’ordine in cui sono presenti nella lista ambiente: S2 (search miss) ed S1 (search hit).
  • 25. LFC Symbol table (continua) Il front-end di un compilatore Architettura del front-end Come già sottolineato, oltre alla implementazione Symbol table ◮ Traduzione guidata dalla sintassi dello scope (e alla verifica del soddisfacimento di Generazione del three-address code vincoli non facilmente esprimibili a livello grammaticale), le symbol table mantengono informazioni fondamentali per la generazione del codice Ad esempio, la entry per un identificatore contiene: ◮ la sequenza di caratteri che ne costituisce il lessema ◮ (che può essere usata come chiave per l’accesso alla tabella); il tipo della variabile; ◮ l’indirizzo di memoria della variabile. ◮
  • 26. LFC Linguaggi formali e compilazione Il front-end di un compilatore Architettura del front-end Symbol table Traduzione guidata dalla sintassi Generazione del three-address code Il front-end di un compilatore Architettura del front-end Symbol table Traduzione guidata dalla sintassi Generazione del three-address code
  • 27. LFC Le due parti del compilatore Il passaggio dal codice sorgente ad un codice Il front-end di un ◮ compilatore macchina efficiente viene tipicamente spezzato in Architettura del front-end Symbol table due parti. Traduzione guidata dalla sintassi Generazione del La prima parte, gestita dal front-end del compilatore, ◮ three-address code termina con la generazione di un opportuno codice intermedio ed è chiaramente dominata dalle caratteristiche del linguaggio sorgente. La seconda parte, gestita dal back-end, è invece ◮ specializzata ad ottenere un codice macchina efficiente in funzione della particolare architettura. La suddivisione netta del progetto di un compilatore ◮ in front-end e back-end (la prima indipendente dall’architettura e la seconda indipendente dal linguaggio) ha, fra le altre proprietà, un notevole impatto in termini di economicità di progetto.
  • 28. LFC Rappresentazioni intermedie Il front-end di un Le rappresentazioni intermedie più importanti sono i compilatore ◮ Architettura del front-end syntax tree e il cosiddetto three-address code. Symbol table Traduzione guidata dalla sintassi Per ora ci concentriamo sui syntax tree, posponendo ◮ Generazione del three-address code alla sezione sulla generazione del codice intermedio la definizione di three-address code. Un abstract syntax tree per un linguaggio L è un ◮ albero in cui: i nodi interni rappresentano costrutti di L; ◮ i figli di un nodo che rappresenta un costrutto C ◮ rappresentano a loro volta le componenti significative di C; le foglie sono “costrutti elementari” (non ◮ ulteriormente decomponibili) caratterizzati da un valore lessicale (tipicamente un numero o un puntatore alla symbol table).
  • 29. LFC Esempio Il front-end di un Un abstract syntax tree per la frase id + id × id è ◮ compilatore Architettura del front-end + Symbol table Traduzione guidata dalla sintassi Generazione del × id, ptr three-address code id, ptr id, ptr Abstract syntax tree e parse tree sono oggetti diversi. ◮ E E + T × T T F F F id id id
  • 30. LFC Rappresentazioni intermedie Per alcuni semplici linguaggi interpretati, una Il front-end di un ◮ compilatore rappresentazione finale basata su una struttura ad Architettura del front-end Symbol table albero (da dare poi in input all’interprete) può essere Traduzione guidata dalla sintassi sufficiente. Generazione del three-address code Questo è il caso, ad esempio, della realizzazione di ◮ un semplice “desk calculator” (interattivo) che accetta in input formule che seguono la sintassi classica (con operatori infissi). In questo caso, infatti, è semplice scrivere un ◮ interprete per la formula rappresentata come abstract syntax tree. In generale, tuttavia, i syntax tree costituiscono solo ◮ un passaggio intermedio verso al generazione di codice intermedio (tipicamente il three-address code).
  • 31. LFC I vari passaggi in dettaglio Per chiarezza didattica, possiamo “in prima battuta” Il front-end di un ◮ compilatore immaginare che tutte le strutture intermedie che Architettura del front-end Symbol table portano alla generazione del three-address code Traduzione guidata dalla sintassi vengano effettivamente costruite. Generazione del three-address code A partire dalla sequenza di token restituita ◮ dall’analizzatore sintattico, possiamo quindi immaginare (per il momento) che il parser costruisca esplicitamente tanto il parse tree quanto l’abstract syntax tree delle stringhe “corrette”. In un compilatore reale, tuttavia, il parser emette il ◮ three-address code senza costruire “esplicitamente” il syntax tree ne’ il parse tree. In realtà, anche nell’ambito della nostra trattazione ◮ semplificata vedremo come almeno la generazione del parse tree sia (quasi sempre) evitabile.
  • 32. LFC Traduzione guidata dalla sintassi La generazione dell’abstract syntax tree, il nostro ◮ Il front-end di un obiettivo attuale, può essere eseguita mediante compilatore l’applicazione di una tecnica nota come Traduzione Architettura del front-end Symbol table guidata dalla sintassi (in inglese Syntax-Directed Traduzione guidata dalla sintassi Generazione del Translation). three-address code In altre parole, l’output desiderato (un abstract syntax ◮ tree) viene generato mediante un processo che è guidato dalla stessa grammatica e, in particolare, dalle sue produzioni. Tipicamente, quando viene applicata una produzione ◮ (nel parsing top-down) oppure viene effettuata una riduzione (nel parsing bottom-up) viene anche effettuata una qualche azione di costruzione dell’abstract syntax tree. La modalità specifica che vedremo per la ◮ realizzazione del processo di traduzione è quella degli schemi di traduzione (syntax-directed translation scheme).
  • 33. LFC Syntax-directed translation scheme Uno schema di traduzione prevede l’inserimento di ◮ Il front-end di un frammenti di codice nel corpo (parte destra) delle compilatore Architettura del front-end produzioni. Symbol table Traduzione guidata dalla sintassi Per ogni produzione, il codice viene eseguito dal ◮ Generazione del three-address code parser in un preciso momento che può essere prima, durante o dopo l’applicazione della produzione stessa. Negli esempi di costruzione del syntax tree che ◮ svilupperemo le azioni saranno essenzialmente operazioni di costruzione di nodi e manipolazione di puntatori. L’aspetto importante che dovremo chiarire è relativo ◮ al preciso ordine di esecuzione dei vari frammenti di codice. Dobbiamo però preliminarmente descrivere quali dati ◮ vengono manipolati dagli schemi di traduzione.
  • 34. LFC Attributi Il front-end di un Un qualunque processo di calcolo richiede la ◮ compilatore Architettura del front-end manipolazione di informazione, che deve essere Symbol table Traduzione guidata dalla memorizzata “da qalche parte”. sintassi Generazione del three-address code Nel caso degli schemi di traduzione, l’informazione ◮ manipolata è costituita da attributi associati ai simboli della grammatica. Naturalmente abbiamo bisogno di una notazione per ◮ poter fare riferimento a tale informazione. Se X è un simbolo della grammatica e a è il nome di ◮ un suo attributo, useremo dunque la scrittura X .a per indicare il valore dell’attributo a di X . Il valore di un attributo può essere di qualunque ◮ natura: stringa, numero, albero, tabella, tipo di dato, ecc.
  • 35. LFC Attributi (continua) Il front-end di un compilatore Si noti tuttavia che nel processo di derivazione di una ◮ Architettura del front-end Symbol table stringa, (e quindi nel corrispondente parse tree) un Traduzione guidata dalla sintassi simbolo X della grammatica può apparire più volte. Generazione del three-address code Dobbiamo quindi precisare che la scrittura X .a si ◮ applica non tanto al “generico” simbolo X bensì ad una sua occorrenza nel parse tree. In altri termini, se la scrittura X .a nel frammento di ◮ codice associato (poniamo) alla produzione A → XYZ si dovrà intendere come riferita ad ogni specifica applicazione della regola (che introduce il simbolo X ), bisognerà comunque tenere conto del fatto che, in applicazioni diverse della regola, il valore di X .a potrà essere (e in generale sarà) diverso.
  • 36. LFC Attributi (continua) Il front-end di un compilatore Architettura del front-end Symbol table Nel nostro caso, i valori degli attributi saranno ◮ Traduzione guidata dalla sintassi puntatori al “costruendo” syntax tree. Generazione del three-address code Per questa ragione, associato ad ogni non terminale ◮ della grammatica si troverà sempre un attributo di nome node. In alcuni casi ci potranno essere anche ulteriori ◮ attributi Siamo ora in grado di presentare il nostro primo ◮ esempio di schema di traduzione, relativo alla “solita” grammatica delle espressioni aritmetiche.
  • 37. LFC Esempio Il front-end di un {E .node ← new node(′ +′ , E1 .node, → E1 + T E compilatore Architettura del front-end T .node)} Symbol table Traduzione guidata dalla →T {E .node ← T .node} E sintassi Generazione del {T .node ← new node(′ ×′ , T1 .node, → T1 × F T three-address code F .node)} →F {T .node ← F .node} T → (E ) {F .node ← E .node} F → id {F .node ← new leaf(id, id.lexval)} F Si noti che: ◮ ai non terminali è associato il solo attributo node; ◮ ai terminali è associato l’attributo lexval (tipicamente, ◮ un puntatore alla entry della symbol table). l’aggiunta dell’indice ai non terminali E e T , nella ◮ parte destra delle produzioni E → E + T e T → T × F , serve unicamente a disambiguare i riferimenti nelle corrispondenti azioni.
  • 38. LFC Calcolabilità degli schemi Il front-end di un Come si passa dalla descrizione “statica” delle azioni ◮ compilatore associate alle singole produzioni ad un processo di Architettura del front-end Symbol table calcolo precisamente definito? Traduzione guidata dalla sintassi Generazione del Se immaginiamo (come più volte affermato) che il three-address code ◮ parser costruisca effettivamente il parse tree, possiamo pensare che le azioni descritte dallo schema vengano eseguite nell’ambito di un “opportuno” attraversamento del parse tree stesso. In questo modo, specificando il tipo di ◮ attraversamento, otteniamo la descrizione cercata. Infatti, una volta stabilito l’attraversamento (ad ◮ esempio, in ordine posticipato) rimane univocamente determinato l’ordine di visita ai nodi del parse tree con conseguente esecuzione delle azioni specificate dallo schema.
  • 39. LFC Calcolabilità degli schemi (continua) Si noti che, in generale, il valore di un attributo Il front-end di un ◮ compilatore presente in un nodo del parse tree dipende dal Architettura del front-end Symbol table valore degli attributi presenti in altri nodi. Traduzione guidata dalla sintassi Generazione del L’ordine di attraversamento dell’albero deve quindi ◮ three-address code garantire il soddisfacimento della fondamentale proprietà che il valore di ogni attributo presente nello schema sia calcolato prima di essere utilizzato. Ad esempio, con riferimento allo schema introdotto, i ◮ valori degli attributi T1 .node e F .node dovranno essere disponibili nel momento in cui viene eseguito l’assegnamento T .node ← new node(‘×′ , T1 .node, F .node). Si tratta di un punto molto delicato, al quale ◮ dobbiamo dedicare attenzione specifica utilizzando anche la terminologia appropriata.
  • 40. LFC Il ruolo del parse tree Prima di procedere, vogliamo però soffemarci sul Il front-end di un ◮ compilatore ruolo del parse tree nei processi di calcolo dei quali Architettura del front-end Symbol table stiamo discutendo. Traduzione guidata dalla sintassi Generazione del Tale ruolo è essenzialmente di “raccordo”, in quanto ◮ three-address code il processo di parsing consente di generare in modo naturale prorpio il parse tree e non (direttamente) il syntax tree. Il parse tree offre, da un lato, un “posto” per ◮ memorizzare gli attributi (cioè l’informazione manipolata dagli schemi), dall’altro (come abbiamo appena visto) un modo per descrivere l’ordine di esecuzione delle azioni dello schema. Va comunque da se che, qualora si trovino modi più ◮ efficienti per risolvere questi due problemi, la costruzione esplicita del parse tree diviene superflua.
  • 41. LFC Tipi di attributi Il front-end di un compilatore Tornando al nostro problema (garantire cioè che il ◮ Architettura del front-end Symbol table calcolo del valore di un attributo venga eseguito solo Traduzione guidata dalla sintassi quando tutti gli attributi ai quali esso fa riferimento Generazione del three-address code siano stati a loro volta calcolati), definiamo le due diverse tipologie di attributi utilizzate nelle applicazioni reali: gli attributi sintetizzati e gli attributi ereditati. Con riferimento ad una generica produzione ◮ A → X1 . . . Xi−1 Xi . . . Xk un attributo per A si dice sintetizzato se il suo valore dipende solo dal valore degli attributi dei simboli Xi nella parte destra della produzione.
  • 42. LFC Tipi di attributi (continua) Il front-end di un compilatore Architettura del front-end Più precisamente, un attributo che etichetta un nodo ◮ Symbol table Traduzione guidata dalla del parse tree è sintetizzato se il suo valore dipende sintassi Generazione del solo dal valore degli attributi nei nodi figli. three-address code Ci si può facilmente rendere conto che questa ◮ condizione vale per tutti gli attributi dell’unico schema di traduzione finora visto come esempio. Uno schema in cui tutti gli attributi sono sintetizzati è ◮ detto S-attributed. Se associamo ad uno schema S-attributed un ordine ◮ di attraversamento posticipato del parse tree, la proprietà fondamentale viene automaticamente soddisfatta.
  • 43. LFC Esempio Attraversando in ordine anticipato il parse tree Il front-end di un ◮ compilatore relativo alla stringa di input id + id × id si ottiene un Architettura del front-end Symbol table processo che costruisce correttamente il syntax tree. Traduzione guidata dalla sintassi Generazione del Il risultato completo del processo (incluso il valore ◮ three-address code associato a tutti gli attributi utilizzati nello schema) può essere descritto mediante il cosiddetto annotated parse tree, che mostriamo di seguito. E E.node <,> E E.node + T T.node <,> T T.node T T.node F F.node F F.node F F.node id <id,id.lexval> id <id,id.lexval> id <id,id.lexval>
  • 44. LFC Esempio (continua) Il front-end di un compilatore Architettura del front-end Nell’annotated parse tree sono evidenziati gli attributi ◮ Symbol table Traduzione guidata dalla presenti ai vari nodi nodi e il loro valore, che (in sintassi Generazione del questo caso) è un puntatore ai nodi dell’abstract three-address code syntax tree. Per semplificare l’interpretazione della figura, ◮ abbiamo usato frecce diverse per rappresentare derivazioni (e dunque puntatori ai nodi del parse tree) e valori degli attributi (puntatori ai nodi dell’abstract syntax tree). Comprendere il modo in cui si arriva alla situazione ◮ illustrata eseguendo le azioni è, in questo caso, molto semplice.
  • 45. LFC Esempio (continua) Il front-end di un compilatore Architettura del front-end Seguendo l’ordine di attraversamento posticipato ad ◮ Symbol table Traduzione guidata dalla ogni nodo A si esegue l’azione prevista dallo schema sintassi Generazione del in corrispondenza della produzione A → XYZ , dove three-address code X , Y e Z sono i figli del nodo A. Si noti che, in realtà, non è necessario neppure ◮ seguire strettamente l’ordine di attraversamento posticipato; è infatti sufficiente procedere in modo bottom up, dalle foglie verso la radice, con notevoli gradi di libertà sull’ordine preciso di esecuzione delle azioni descritte dallo schema. Si noti infine che il syntax tree completo è accessibile ◮ attraverso l’attributo E .node nella radice parse tree.
  • 46. LFC Tipi di attributi (riprende) Ancora con riferimento alla generica produzione ◮ Il front-end di un compilatore A → X1 . . . Xi−1 Xi . . . Xk Architettura del front-end Symbol table Traduzione guidata dalla un attributo per Xi si dice ereditato se il suo valore sintassi Generazione del three-address code dipende (eventualmente) dal valore di attributi ereditati di A e di attributi (ereditati o sintetizzati) di X1 , . . . , Xi−1 . Con riferimento al parse tree, un attributo a un nodo ◮ è ereditato se il suo valore dipende dal valore di attributi ereditati al nodi genitore o attributi (di qualunque tipo) ai nodi che sono “fratelli maggiori”. Questa definizione non è la più generale ma, di fatto, ◮ è quella operativa. In particolare, schemi di traduzione in cui vi sia (oltre ◮ ad eventuali attributi sintetizzati) almeno un attributo ereditato secondo questa definizione vengono detti L-attributed.
  • 47. LFC Esempio Il seguente schema di traduzione, relativo alla ◮ Il front-end di un compilatore grammatica per le espressioni modificata (per Architettura del front-end renderla adatta al parsing a discesa ricorsiva), è Symbol table Traduzione guidata dalla sintassi L-attributed. Generazione del three-address code Si noti che alcuni nodi hanno due attributi, ◮ sintetizzato (node) ed ereditato (inh). E → TE ′ {E ′ .inh ← T .node; E .node ← E ′ .node} E ′ → +TE1 ′ {E1 .inh ← new node(′ +′ , E ′ .inh, T .node); ′ ′ .node ← E ′ .node} E 1 E′ → ǫ {E ′ .node ← E ′ .inh} T → FT ′ {T ′ .inh ← F .node; T .node ← T ′ .node} T ′ → ×FT1 ′ {T1 .inh ← new node(′ ×′ , T ′ .inh, F .node); ′ T ′ .node ← T1 .node} ′ T′ → ǫ ′ .node ← T ′ .inh} {T F → (E ) {F .node ← E .node} F → id {F .node ← new leaf(id, id.lexval)}
  • 48. LFC Calcolabilità degli schemi (riprende) Il front-end di un compilatore Architettura del front-end Vedremo un poco più avanti la logica che sta dietro ◮ Symbol table Traduzione guidata dalla lo schema L-attributed dell’esempio appena visto. sintassi Generazione del three-address code Qui vogliamo riconsiderare per un istante il problema ◮ generale della calcolabilità degli schemi. Esiste infatti un rischio insito nella definizione di ◮ attributi con dipendenze “arbitrarie”, e cioè che non esista nessun ordine di esecuzione delle azioni in grado si soddisfare la proprietà fondamentale. Comunque, sempre nel caso generale, la ◮ determinazione dell’esistenza di un tale ordine di esecuzione costituisce problema algoritmico molto difficile.
  • 49. LFC Calcolabilità degli schemi (continua) Il front-end di un compilatore Architettura del front-end Symbol table Traduzione guidata dalla sintassi È alla luce di ciò che dobbiamo “apprezzare” la bontà Generazione del ◮ three-address code degli schemi S-attributed e L-attributed. Infatti, l’esistenza (e la immediata determinazione) di ◮ un ordine di esecuzione delle azioni previste dagli schemi è garantita proprio nei seguenti due casi: la grammatica ammette parsing di tipo LR e lo ◮ schema è S-attributed, oppure la grammatica ammette parsing di tipo LL e lo ◮ schema è L-attributed.
  • 50. LFC Parsing LR La prima combinazione, di grammatica analizzabile ◮ Il front-end di un compilatore con parser LR unitamente a schema S-attributed, è Architettura del front-end più semplice da comprendere e, di fatto, l’abbiamo Symbol table Traduzione guidata dalla sintassi già vista. Generazione del three-address code Infatti, in un parser LR, il parse tree viene costruito ◮ (anche solo implicitamente) dal basso verso l’alto. Questo implica che, nel momento in cui viene ◮ eseguita una riduzione A → α, è possibile calcolare il valore degli attributi (sintetizzati) definiti nel nodo corrispondente ad A. Tali valori dipendono infatti solo dal valore di attributi ◮ definiti ai nodi corrispondenti ai simboli di α, che sono stati a loro volta già calcolati. In altri termini, la situazione che abbiamo visto ◮ nell’esempio è del tutto generale per grammatiche LR e schemi S-attributed.
  • 51. LFC Esempio Il front-end di un compilatore Riconsideriamo in dettaglio l’esempio relativo al ◮ Architettura del front-end Symbol table parsing della stringa id + id × id, secondo lo schema Traduzione guidata dalla sintassi (S-attributed) introdotto. Generazione del three-address code Usando il parser SLR(1) che abbiamo discusso a ◮ suo tempo, l’albero di parsing viene costruito dal basso verso l’alto. Se le azioni indicate dallo schema vengono eseguite ◮ nel momento in cui si eseguono le corrispondenti riduzioni, i valori degli attributi sono tutti calcolabili (cioè le dipendenze sono tutte soddisfatte) come illustrato nelle due seguenti trasparenze (nelle quali, per semplicità, non sono state disegnate foglie isolate).
  • 52. LFC Esempio (continua) E E.node Il front-end di un T T.node T T.node compilatore Architettura del front-end Symbol table F F.node F F.node F F.node Traduzione guidata dalla sintassi Generazione del id <id,id.lexval> id <id,id.lexval> id <id,id.lexval> three-address code Dopo la riduzione F−>id Dopo T−>F Dopo E−>T E E.node T T.node F F.node F F.node id <id,id.lexval> id <id,id.lexval> Dopo F−>id E E.node T T.node T T.node F F.node F F.node id <id,id.lexval> id <id,id.lexval> Dopo T−>F
  • 53. LFC Esempio (continua) E E.node Il front-end di un compilatore T T.node T T.node F F.node Architettura del front-end Symbol table F F.node F F.node id <id,id.lexval> Traduzione guidata dalla sintassi Generazione del id <id,id.lexval> id <id,id.lexval> three-address code Dopo F−>id E E.node T T.node <,> T T.node T T.node F F.node F F.node F F.node id <id,id.lexval> id <id,id.lexval> id <id,id.lexval> Dopo T−>T*F E E.node <,> E E.node + T T.node <,> T T.node T T.node F F.node F F.node F F.node id <id,id.lexval> id <id,id.lexval> id <id,id.lexval> Dopo E−>E+T
  • 54. LFC Aspetti implementativi Nel caso di parsing LR e schema S-attributed è ◮ Il front-end di un relativamente semplice mostrare come la compilatore costruzione esplicita del parse tree non sia richiesta. Architettura del front-end Symbol table Se infatti è vero che gli attributi sono logicamente Traduzione guidata dalla ◮ sintassi Generazione del definiti ai nodi del parse tree, è però immediato three-address code convincersi del fatto che, non appena un nodo N è stato collegato al padre P (a seguito di una riduzione), gli attributi definiti in N non servono più. Nella pratica è sufficiente una modifica allo stack ◮ utilizzato dal parser LR, in modo che esso non memorizzi soltanto stati ma anche gli attributi associati al simbolo che corrisponde a quello stato. A quest’ultimo riguardo, possiamo notare come ogni ◮ stato s dell’automa possa essere associato ad un simbolo della grammatica (si riconsideri, ad esempio, l’automa LR(0) relativo alla nostra grammatica per le espressioni, che riportiamo per comodità nella successiva trasparenza).
  • 55. LFC Aspetti implementativi (continua) accept Il front-end di un compilatore $ Architettura del front-end + Symbol table T I6 I1 Traduzione guidata dalla sintassi Generazione del three-address code F + E I3 I9 id F ( ) id I0 I8 I5 I11 F id × ( id T F I4 T ( ( F I10 I2 I7 ×
  • 56. LFC Schema di traduzione modificato Il front-end di un compilatore Architettura del front-end Presentiamo di seguito lo schema di traduzione per ◮ Symbol table Traduzione guidata dalla la grammatica delle espressioni, modificato in modo sintassi Generazione del da utilizzare lo stack per memorizzare gli attributi. three-address code Più precisamente, lo stack memorizza ora coppie ◮ s, p , dove s è uno stato (un numero compreso fra 0 e 11), mentre p è il valore della proprietà node (in pratica, un puntatore ad un nodo dell’abstract syntax tree in costruzione) del simbolo associato ad s. Si noti che non è più necessario distinguere differenti ◮ occorrenze dello stesso simbolo (in quanto non vengono più indicati esplicitamente gli attributi nelle azioni dello schema).
  • 57. LFC Schema di traduzione modificato Il front-end di un compilatore → E +T {p2 ← pop().p; pop(); p1 ← pop().p; E Architettura del front-end Symbol table s ← GOTO(top().s, E ); Traduzione guidata dalla sintassi Generazione del push( s, new node(′ +′ , p1 , p2 ) )} three-address code →T {p ← pop().p; s ← GOTO(top().s, E ); E push( s, p )} → T ×F {p2 ← pop().p; pop(); p1 ← pop().p; T s ← GOTO(top().s, T ); push( s, new node(′ ×′ , p1 , p2 ) )} →F {p ← pop().p; s ← GOTO(top().s, T ); T push( s, p )} → (E ) {pop(); p ← pop().p; pop(); F s ← GOTO(top().s, F ); push( s, p )} → id {p ← pop().p; s ← GOTO(top().s, F ); F push( s, new leaf(id, id.lexval) }
  • 58. LFC Parsing LL Passiamo ora ad unalizzare il secondo caso di Il front-end di un ◮ compilatore calcolabiltà efficiente degli schemi di traduzione, e Architettura del front-end Symbol table precisamente quello relativo a schemi L-attributed in Traduzione guidata dalla sintassi unione con grammatiche di tipo LL. Generazione del three-address code Anche in questo caso, l’esempio che ci guiderà sarà ◮ quello relativo alla grammatica per le espressioni modificata con l’eliminazione della ricorsione a sinistra. Abbiamo già visto come lo schema sia L-attributed. ◮ Ciò che ci resta da stabilire è dunque come debbano ◮ essere sequenzializzati i frammenti di codice associati alle varie produzioni in modo da ottenere un processo di calcolo che produca effettivamente (ed efficientemente) l’output desiderato (l’abstract syntax tree).
  • 59. LFC Un semplice esempio Per capire il processo generale ci aiutiamo con il ◮ Il front-end di un semplice esempio di parsing della frase id1 + id2 : compilatore Architettura del front-end E Symbol table Traduzione guidata dalla sintassi Generazione del three-address code E′ T T′ F + T E’ T′ ǫ ǫ id1 F ǫ id2 Come già per gli schemi S-attributed, le dipendenze ◮ funzionali fra gli attributi (e dunque il flusso di dati necessario affinché il calcolo proceda in modo corretto) possono essere evidenziati utilizzando un annotated parse tree.
  • 60. LFC Esempio (continua) In questo caso, nel disegno dell’annotated parse tree ◮ Il front-end di un abbiamo utilizzato una notazione semplificativa. compilatore Architettura del front-end Symbol table Ciò è dovuto al fatto che la maggior parte dei nodi ha ◮ Traduzione guidata dalla sintassi due attributi i quali, se scritti esplicitamente, Generazione del three-address code avrebbero reso la figura difficile da “leggere”. Abbiamo quindi usato la seguente convenzione: ◮ una freccia indica un arco del parse tree ◮ (queste frecce sono sempre orientate verso il basso); una freccia (orientata verso l’alto) indica che ◮ l’attributo sintetizzato al nodo di partenza è usato per calcolare l’attributo sintetizzato al nodo di arrivo; una freccia (orientata verso il basso) indica ◮ che l’attributo ereditato al nodo di partenza è usato per calcolare l’attributo ereditato al nodo di arrivo; infine, una freccia (orientata verso destra) ◮ indica che l’attributo sintetizzato al nodo di partenza è usato per calcolare l’attributo ereditato al nodo di arrivo.
  • 61. LFC Esempio (continua) L’annotated parse tree: Il front-end di un ◮ compilatore Architettura del front-end Symbol table E Traduzione guidata dalla sintassi Generazione del three-address code T E’ + F T’ T E’ ε ε id 1 F T’ ε id 2
  • 62. LFC Organizzazione concreta del calcolo Nel caso di implementazione del parser LL mediante ◮ Il front-end di un compilatore procedure ricorsive, un’implementazione dello Architettura del front-end schema di traduzione che rispetti le dipendenze Symbol table Traduzione guidata dalla sintassi funzionali è alquanto semplice. Generazione del three-address code Consideriamo infatti l’utilizzo di una generica ◮ produzione A → X1 . . . Xi−1 Xi . . . Xk . Nel momento in cui la procedura per Xi viene ◮ chiamata (dalla procedura per A), le procedure per X1 , . . . , Xi−1 sono già state chiamate e dunque eventuali valori da esse produtti possono essere passati in input a Xi . Questo consente proprio di implementare il flusso ◮ dati richiesto dalla definizione di attributo ereditato, nel senso che, qualora Xi abbia attributi ereditati, questi dipendono da valori associati a (e producibili dalle procedure per) A e/o X1 , . . . , Xi−1 .
  • 63. LFC Organizzazione concreta del calcolo (continua) Il front-end di un compilatore In realtà, è proprio la struttura delle procedure ◮ Architettura del front-end Symbol table ricorsive di un parser LL ad aver suggerito la nostra Traduzione guidata dalla sintassi definizione di attributo ereditato. Generazione del three-address code Tornando alla nostra grammatica per le espressioni, ◮ consideriamo, ad esempio, la procedura per E e le corrispondenti azioni dello schema: E ′ .inh ← T .node e E .node ← E ′ .node. La procedura invocherà quindi dapprima la routine T ◮ (che non necessita di “informazione ereditata”). Il valore restituito da quest’ultima (T .node) sarà ◮ passato in input a E ′ come valore ereditato. Il valore restituito dalla chiamata ad E ′ sarà poi ◮ anche il valore restituito da E .
  • 64. LFC Organizzazione concreta del calcolo (continua) Il front-end di un compilatore Architettura del front-end Symbol table Traduzione guidata dalla sintassi Generazione del three-address code Si noti quindi che le procedure corrispondenti ai non ◮ terminali sono più utilmente realizzate mediante funzioni. La funzione per E può quindi essere sinteticamente ◮ espressa nel modo seguente: Tnode ← T () 1: return E ′ (Tnode ) 2:
  • 65. LFC Organizzazione concreta del calcolo (continua) Il front-end di un compilatore Consideriamo, come altro esempio, la routine per E ′ . ◮ Architettura del front-end Symbol table Abbiamo appena visto che essa riceve in input un ◮ Traduzione guidata dalla sintassi argomento (attributo ereditato) che supporremo Generazione del three-address code venga assegnato al parametro formale inh. Il corpo della funzione può essere espresso nel ◮ modo seguente (si ricordi la tabella di parsing per la produzione E ′ → +TE ′ ): 1: if t ← get_next_token() =′ +′ then Tnode ← T () 2: inh1 ← new node(′ +′ , inh, Tnode ) 3: return E ′ (inh1 ) 4: ′ 5: else if t =′ ) or t =′ $′ then return inh 6: 7: else error() 8:
  • 66. LFC Organizzazione concreta del calcolo (continua) Il front-end di un Nel caso di implementazione ricorsiva del parser, compilatore ◮ Architettura del front-end non è dunque necessario che venga Symbol table Traduzione guidata dalla preliminarmente costruito il parse tree. sintassi Generazione del three-address code Nel caso il parse tree sia disponibile non è ◮ comunque difficile rendersi conto di come le dipendenze funzionali presenti negli schemi siano soddisfattibili mediante un attraversamento in ordine anticipato (preordine) del parse tree medesimo. Infatti, l’attaversamento in ordine anticipato consente ◮ (sempre con riferimento alla generica produzione A → X1 . . . Xi−1 Xi . . . Xk ) di “raccogliere” l’informazione (attributi) relativa ad A e X1 , . . . , Xi−1 prima di usarla (come informazione ereditata) nel calcolo degli attributi per Xi Si veda al riguardo l’annotated parse tree relativo ◮ alla frase id1 + id2 .
  • 67. LFC Linguaggi formali e compilazione Il front-end di un compilatore Architettura del front-end Symbol table Traduzione guidata dalla sintassi Generazione del three-address code Il front-end di un compilatore Architettura del front-end Symbol table Traduzione guidata dalla sintassi Generazione del three-address code
  • 68. LFC Il completamenento del front-end Obiettivo di quest’ultima parte del corso è la ◮ Il front-end di un presentazione di alcuni semplici esempi di compilatore Architettura del front-end generazione di codice intermedio. Symbol table Traduzione guidata dalla Non intendiamo presentare una descrizione sintassi ◮ Generazione del completa, sia per ragioni di tempo sia perché (a three-address code nostro avviso) questa attività è molto meno interessante dal punto di vista delle idee sottostanti. Gli esempi hanno dunque il solo scopo di illustrare ◮ l’approccio base per la generazione di codice intermedio, ignorando quegli aspetti che richiederebbero un ragionamento più approfondito. Faremo inoltre tre ipotesi molto forti, e precisamente: ◮ (1) di disporre dell’abstract syntax tree delle stringhe da tradurre; (2) che il risultato debba essere presentato sotto forma di file alfanumerico, (3) che esista un unico tipo di dato nei programmi sorgenti (ad esempio, numeri interi).
  • 69. LFC Il modello di computer intermedio Il codice intermedio nel quale dovrà essere tradotto il ◮ Il front-end di un programma sorgente sarà il cosiddetto three-address compilatore code. Architettura del front-end Symbol table Naturalmente, prima di descrivere gli esempi di Traduzione guidata dalla ◮ sintassi Generazione del traduzione, dobbiamo precisare che cosa si intenda three-address code con codice a tre indirizzi, cioè a quale modello astratto di calcolatore tale codice faccia riferimento. Ipotizziamo dunque che la macchina astratta sia in ◮ grado di eseguire semplici istruzioni caratterizzate da un codice operativo e da al più tre indirizzi per gli operandi. Fra le istruzioni disponibili troviamo le operazioni ◮ logico/aritmetiche binarie e unarie, le istruzioni di salto (condizionato o incondizionato), il semplice assegnamento, la chiamata di procedura e la gestione di array lineari. Ogni istruzione può essere preceduta da una o più ◮ etichette (stringhe letterali)
  • 70. LFC Istruzioni del three-address code Il front-end di un compilatore Più precisamente, allo scopo di presentare gli Architettura del front-end ◮ Symbol table esempi, noi considereremo solo le seguenti Traduzione guidata dalla sintassi istruzioni, il cui significato dovrebbe risultare chiaro: Generazione del three-address code A ← B op C, dove op ∈ {+, −, ×, /} e A, B e C sono ◮ identificatori definiti dall’utente nel programma sorgente oppure temporanee generate dal parser; A ← B, dove A e B sono definiti come al punto ◮ precedente; goto L, dove L è un’etichetta generata dal parser; ◮ if A goto L, dove A è un identificatore definito ◮ dall’utente oppure una temporanea generata dal parser ed L è un’etichetta generata dal parser; ifFalse A goto L, dove A ed L sono definiti come ◮ al punto precedente.
  • 71. LFC Ulteriori assunzioni Ipotizzeremo che il parser possa invocare una ◮ Il front-end di un funzione per generare identificatori univoci, come compilatore pure una funzione per generare etichette univoche. Architettura del front-end Symbol table Assumeremo inoltre la disponibilità di una funzione, Traduzione guidata dalla ◮ sintassi Generazione del che chiameremo emit(), che stampa la stringa three-address code passata come parametro (che rappresenta una porzione del programma in three-address code) su un opportuno supporto di output. Assumeremo infine che il generico nodo dell’abstract ◮ syntax tree abbia la seguente struttura: un campo label che caratterizza il tipo di nodo ◮ (assegnamento, operatore, comando if, ...); se significativo (ad esempio nel caso di identificatore ◮ o operatore), un puntatore alla symbol table per il corrispondente valore lessicale, accessibie mediante la funzione lexval; uno o più puntatori ai nodi che rappresentano i ◮ componenti del costrutto, accessibili mediante i campi c1, c2, ...
  • 72. LFC La struttura generale del traduttore Con le ipotesi fatte, il traduttore (da syntax tree a ◮ Il front-end di un compilatore three-address code) può essere organizzato come Architettura del front-end procedura ricorsiva il cui corpo è costituito Symbol table Traduzione guidata dalla sintassi essenzialmente da una “grossa” istruzione case (o, Generazione del three-address code se si preferisce, switch), che analizza il nodo p passato come parametro e, a seconda del tipo, esegue operazioni diverse. Data la struttura del parse tree, la generazione del ◮ codice per un dato nodo implicherà poi una o più chiamate ricorsive per la generazione del codice associato ai nodi figli. La procedura, che chiameremo gencode, riceve un ◮ ulteriore parametro (RES) che è una stringa (eventualmente vuota) alla quale (vista come nome di variabile) deve essere assegnato il risultato calcolato dal codice generato per il nodo p.
  • 73. LFC Procedure 1 gencode(string RES, AST ∗ p) Il front-end di un compilatore tag ← (p → label) Architettura del front-end Symbol table case tag of Traduzione guidata dalla sintassi Generazione del id : . . . three-address code assignment : . . . comparison : . . . binaryop : . . . seq : . . . if : . . . ifElse : . . . while : . . . ... default : error () end