Sperimentazione di Traduttori

Universitá di Torino

Dipartimento d’Informatica

Anno accademico 1998-99

Prof. Maria Luisa Sapino


Andrea Caddori

Rosario Marino

Vittorio Cascio

 

Guida all’Uso

Complimenti per aver scelto l’ultima versione del nostro PCCHAL, Pascal, Cross Compiler for HAL.

Licenza e condizioni d’uso

Il nostro compilatore è bugware! Cioè di libera distribuzione, copia e modifica, ma se ritenete il software interessante e ne vorreste una versione totalmete priva di bug, dovrete pagare!

Installazione

Il pacchetto PCCHAL comprede i seguenti files:

Per compilare il tutto è sufficiente eseguire make, o nell’ordine:

  1. lex lexer.l
  2. yacc parser.y
  3. gcc –o pcchal y.tab.c symtbl.c hal.c

Tutorial

Da linea di comando è sufficiente digitare

pcchal nomefile

per compilare il vostro programma pascal e verificarne la correttezza sintattica.

Se volete vedere il listato del codice intermedio prodotto occore digitare

pcchal –l nomefile

se invece volete eseguire il codice generato occorre invocare l’emulatore HAL9000 col comando

pcchal –r nomefile

l’opzione –h offre un help in linea e la versione del software, mentre con –v si attiva il modo verbose del compilatore che offre informazioni sulla symbol table, generate durante il parsing.

Se si vuol attivare l’opzione di trace di yacc occorre –t, disponibile solo dopo un’opportuna compilazione con di yacc –t.

Per attivare il trace dell’emulatore e visualizzare lo stack del record di attivazione corrente, occorre l’opzione –s.

Il tutto, anziché essere stampato su video puó essere rediretto su un file di output con l’opzione –o

pcchal –o nomefile_di_otput

in modo analogo è possibile stampare gli errori in un file a parte con l’opzione -e

pcchal –e nomefile_di_errori

Guida di riferimento

{ commento delimitato da parentesi graffe }

(* commento con parentesi tonde-asterico *)

programma ->

program id (lista_id); dichiarazioni procedure begin lista_istruzioni end.

lista_id -> id | lista_id , iddichiarazioni -> const_decls var_declsvar_decls -> VAR id lista_id_tipata | var_decls ; var_decls

|

lista_id_tipata -> , id lista_id_tipata

| : tipo

tipo -> integer

| real

procedure -> procedure procedura ;

|

procedura -> testa_procedura dichiarazioni procedure begin lista_istruzioni end

testa_procedura -> procedure id argomenti ;

argomenti ->

| ( lista_parametri )

lista_parametri -> id lista_par_typed

| lista_parametri ; lista_parametri

lista_par_typed -> , id lista_par_typed

| : tipo

lista_istruzioni -> istruzione

| lista_istruzioni ; istruzione

istruzione -> id := s_expr

| if espressione then istruzione

| if espressione then istruzione else istruzione

| repeat lista_istruzioni until espressione

| while espressione do istruzione

| for id := s_expr to s_expr do istruzione

| begin lista_istruzioni end

| procedure_call

| read ( id )

| write ( id )

procedure_call -> id

| id ( Elist )

Elist -> Elist , id

| id

espressione -> espressione or espressione

| espressione and espressione

| not espressione

| ( espressione )

| s_expr relop s_expr

| true

| false

const_decls -> const lista_const ; const_decls

|

lista_const -> cds

| lista_const , cds

cds -> id = num

s_expr -> s_expr addop term

| addop s_expr

| term

term -> term mulop factor

| term mod factor

| factor

factor -> num

| ( s_expr )

| id

La traduzione in codice intermedio avviene in un’unica passata con backpatching, facendo uso di una symbol table che funge da stack per le procedure dichiarate ricorsivamente ed implementa cosí le regole di visibilitá del pascal. Parallelamente alla symbol table sono stati mantenuti alcuni stack che conservano le informazioni riguardanti gli offset correnti, la numerazione dei temporanei, il numero dei simboli dichiarati etc, che crescono col crescere del livello d’annidamento delle procedure. Abbiamo cercato di non generare codice intermedio inutile, effettuando ove possibile in fase di compilazione i calcoli tra costanti. Abbiamo inoltre cercato di ridurre l’uso di temporanei, riutilizzando quelli non piú utili. Nel caso di

id : = s_expr

l’attributo place di s_expr conterrá il risultato, tipicamente un temporaneo, per tradurre l’assegnamento, anziché generare una quadrupla IASSIGN o RASSIGN, si modifica quella precedente reindirizzando il risultato sulla variabile.

Per lo sviluppo del parser è stato usato bison, che in mancanza di un token utile ad una riduzione ne segnala l’assenza, yacc invece si limita a segnalare un syntax error rendendo piú difficile individuare l’errore. La gestione degli errori potrebbe dunque sembrare troppo scarna se si usa yacc per generare il parser.

 

Il codice intermedio e’memorizzato in un vettore di quadruple definito come variabile globale in tipi.h

quadrupla quadvect[MAX_ISTR];

con quadrupla definita come

typedef struct quad

{

istr_opcode opcode;

operandi op1, op2, op3;

} quadrupla;

dove istr_opcode e’un tipo enum che identifica univocamente le istruzioni e operandi è il tipo dei possibili operandi indirizzo o immediato e se immediato intero o reale secondo le seguenti definizioni:

typedef union tipi_numero

{

double reale;

int intero;

} num_type;

typedef struct op_ind

{

int level;

int offset;

} op_indir;

typedef struct op_f

{

num_type imm;

op_indir ind;

} operandi;

Per quanto riguarda il tipo double si assume che abbia la mantissa di ampiezza almeno quanto un intero, per non causare errori nel confronto di variabili di tipo diverso.

Il formato della generica istruzione è:

OPCODE

OP1

OP2

OP3

Per compatibilitá col nostro emulatore (J !) abbiamo cercato di porre il risultato sempre di seguito ai rispettivi operandi, nella stampa del codice intermedio l’operando destinazione appare in ultima colonna.

IASSIGN

OP1

OP2

 

RASSIGN

OP1

OP2

 

Saranno interpretate come OP2:=OP1

IADD

OP1

OP2

OP3

IGTR

OP1

OP2

OP3

Saranno interpretate come OP3:=OP1+OP2

If OP1 > OP2 goto OP3

e stampate in un ipotetico programma come :

1 : IASSIGN <#1> <0, 2>

3 : IADD <#10> <0, 1> <0,1>

4 : IGTR <#10> <0, 1> <#2001>

IUMIN

OP1

OP2

 

OP2:=(-OP1)

READ

OP1

   

GOTO

ADDR

   

HALT

     

Gli operandi immediati, costituiti cioè da una costante disponibile a tempo di compilazione sono distinti, da un level non valido pari a NOLEVEL

Definito come:

#define NOLEVEL –1

e stampati nel listato del codice intermedio come <#costante>.

Molte istruzioni come quelle di salto non controlleranno che il level sia negativo, aspettandosi come operando una costante.

 

Per ogni dubbio chiarimento o contributo:

caddori.andrea@educ.di.unito.it

marino.rosario@educ.di.unito.it

cascio.vittorio@educ.di.unito.it