Capitolo 7

L'ambiente per lo sviluppo dei programmi


7.1 Editor

L'editor permette di creare o di modificare un file di testo. Viene attivato dalla linea di comando "edit" a cui può seguire un parametro. Questo parametro viene interpretato come il nome del file su cui lavorare; qualora questo file esista già, viene aperto e letto; se non esiste, viene creato. Se l'editor viene attivato senza nessun parametro, allora verrà creato un file chiamato nonamex, in cui la x viene sostituita da un numero progressivo.
L'ambiente interno è ispirato al vecchio wordstar. I comandi disponibili sono riportati nella tabella 7.1.

Comando Descrizione
Ctrl-B inizio riga
Ctrl-E fine riga
Ctrl-U pagina su
Ctrl-D pagina giù
Ctrl-Y cancella riga
Ctrl-R leggi file
Ctrl-A visualizza un piccolo aiuto
Ctrl-K B inizio blocco
Ctrl-K K fine blocco
Ctrl-K C copia blocco
Ctrl-K V muovi blocco
Ctrl-K Y cancella blocco
Ctrl-K R leggi blocco
Ctrl-K W scrivi blocco
Esc X esce dall'editor
Esc Z salva ed esce
Tabella 7.1: Lista comandi dell'editor

L'editor funziona in modo interattivo, si può muovere facilmente il cursore, oltre che con i comandi riportati nella tabella {editcommands, anche con l'utilizzo dei tasti "freccia"; molto utile è anche il tasto di cancellazione all'indietro (Back Space).
Le colonne del testo possono essere al massimo 140, mentre il numero di righe è potenzialmente illimitato. Qualora un file letto contenga dei caratteri di tabulazione orizzontali (TAB, codice ASCII 9) questi vengono sostituiti da altrettanti spazi (codice ASCII 32).
La struttura dati in sostanza è composta da una lista concatenata bidirezionale i cui elementi vengono allocati dinamicamente. Per conoscere in ogni momento la posizione del cursore, sarebbe stato meglio utilizzare una tabella. Purtroppo nelle versioni precedenti dell'editor la tabella occupava tantissimo spazio (si arrivava relativamente vicini al limite dello stack) e imponeva un pesante limite massimo di 200 righe.
La struttura dati, per quanto comporti problemi per il mantenimento del numero di riga, è molto vantaggiosa per quanto concerne l'inserimento di nuove righe, la cancellazione di righe e le operazioni sui blocchi.

7.2 Compilatore Tiny

Il compilatore Tiny deriva in buona parte da un tutorial di Jack W. Crenshaw [13]. L'intento di questo tutorial è prettamente didattico: l'autore insegna come costruire un compilatore da un punto di vista molto pratico (con solo qualche breve richiamo alla teoria). Il suo motto è: "imparare facendo".
Il tutorial è caratterizzato da numerosissimi esempi, fin dalle prime pagine si può testare un codice perfettamente funzionante (anche se di scarsa utilità).
Tutti i codici riportati come esempi sono scritti in Pascal. I sorgenti sono particolarmente chiari e comprensibili, ciò è dovuto soprattutto al fatto che le procedure sono tutte brevissime e con un buon identificatore mnemonico; una descrizione con pseudocodice sarebbe quasi superflua.
Il linguaggio riconosciuto e compilato è una via di mezzo fra Pascal e C ma tende un po' verso il Pascal, tuttavia nel mio lavoro ho cercato di renderlo un sottoinsieme del C il più ampio possibile. Il codice generato è scritto in Assembly per la famiglia Motorola 68000.
Il materiale è suddiviso in 16 articoli in ciascuno dei quali viene affrontato un problema (ad esempio espressioni algebriche, procedure, tipi, ecc.). Quasi in ogni articolo è riportato il codice di un microcompilatore in cui la parte trattata viene espansa adeguatamente, anzi spesso vengono riportate alcune varianti (C-like o Pascal-like), mentre tutte le altre parti sono allo stato "larvale".
Negli ultimi due articoli l'autore stesso suddivide il codice prodotto in tre parti principali (oltre l'I/O) che ricalcano la realizzazione classica dei compilatori: Scanner (analizzatore lessicale), Parser (analizzatore sintattico e semantico) e CodeGen (generatore di codice oggetto o assembler). Tuttavia questa suddivisione non è così rigorosa, anzi è puramente logica ed esplicativa. Non esiste neanche un linguaggio intermedio (IL) tra il Parser e il generatore di codice, tuttavia viene emulato tramite la chiamata di funzioni opportune; se si sostituisse ad ogni chiamata un apposito codice, si avrebbe un IL.
Ho verificato che un grosso pregio del codice scritto da Crenshaw è l'espandibilità: non ho avuto grossi problemi nell'aggiungere parti anche molto sostanziose nel codice sorgente la cui lunghezza è più che raddoppiata dalla prima versione ad oggi. Ritengo che siano possibili ancora moltissimi sviluppi del codice e stimo che possa essere necessaria solo qualche piccola modifica della struttura dati attuale.
Nel mio lavoro sono partito dall'articolo XI in cui viene fatto una specie di "riassunto" degli articoli precedenti, fornendo una versione completa, ma molto rudimentale, del compilatore (Tiny 1.1). Ho proceduto nel mio lavoro secondo lo schema seguente:
  • Ho utilizzato un traduttore automatico che purtroppo ha dato risultati scadenti e quindi ho dovuto terminare il lavoro a mano
  • Questa parte è stata piuttosto facile in quanto l'utilizzo dell'I/O avviene attraverso pochissime routine piuttosto semplici
  • È stato necessario eliminare le variabili globali presenti nel sorgente del compilatore Tiny per poter attivare il compilatore contemporaneamente da due o più processi diversi senza avere conflitti, inoltre questo lavoro è stato utile per facilitare il passaggio del compilatore da embedded a non embedded. Per ottenere questo risultato ho posto tutte le variabili locali in una struttura che ho chiamato TIMP Tiny Inter Module Packet; nella funzione principale, tiny(), ho dichiarato una variabile di questo tipo e ho passato a quasi tutte le funzioni un puntatore a questa variabile. In tutti i casi in cui ci fosse un conflitto di identificatori con altre parti del sistema operativo, ho aggiunto una 't' come abbreviazione di Tiny.
  • Ho semplicemente applicato quanto suggerito nel capitolo 13 del tutorial. L'unica difficoltà è stata nell'adattare le funzioni preparate per un compilatore "scheletrico" a un compilatore un po' più corposo. Ho dovuto cambiare il registro usato come frame pointer, A6, perché dava conflitti con l'uso che ne fa il compilatore usato per tradurre Tiny: A6 viene usato per accedere alle variabili globali mentre si usa A5 come frame pointer.
  • Ho definito i tipi interi a 32 bit, int, a 16 bit, short, e a 8 bit, char. Nel tutorial viene implementata la conversione di tipi solo per le espressioni algebriche in un ambiente ridotto all'osso. Io ho introdotto una sintassi C-like nelle dichiarazioni, la conversione nel passaggio di parametri alle procedure, la definizione di variabili locali di diversi tipi, la conversione di tipo in espressioni booleane ed in operazioni di confronto.
  • Ho implementato la read e la write, quest'ultima anche con stringhe costanti; la mul32bit per spezzare la moltiplicazione di numeri a 32 bit in più moltiplicazioni di numeri a 16 bit, la div32bit per gestire la divisone con operandi a 32 bit e le chiamate a sistema exec.
  • aggiunta di una libreria per il supporto dei thread.] La libreria è inglobata come parte integrante del compilatore e riconosce le seguenti parole chiave: thcreate, thexit, thcancel, thself, thmutexi, thmutexd, thlock, thunlock.
  • aggiunta di operatori su puntatori e adeguamento della sintassi al C. Ho aggiunto gli operatori sui puntatori '&' e '*' pur non avendo a disposizione dei veri e propri puntatori, questi vengono "surrogati" dagli interi a 32 bit. Il tipo int può essere inteso anche come puntatore a carattere. Tuttavia su questo tipo sono disponibili tutte le operazioni sugli interi, anche quelle che non dovrebbero esserlo per i puntatori come ad esempio la moltiplicazione e la divisione. L'operatore * può comparire oltre che in qualsiasi espressione anche a sinistra del segno = in un assegnamento; ciò ha creato una piccola rivoluzione nella sintassi ibrida che aveva il linguaggio compilato in quanto poteva essere inteso come un operatore moltiplicativo che continua l'espressione precedente. Per questo motivo ho deciso di rendere la sintassi il più possibile vicina al C, anche se mancano ancora molti elementi.

    7.2.1 Descrizione linguaggio compilato (BNF)

    Le parole riservate sono le seguenti:
    int, short, char, void, if, else, while, read, write.
    int, short, char si possono dichiarare variabili solo di tipo intero a 32, 16 o 8 bit con segno, gli identificatori possono essere al massimo di 8 caratteri, non si fa distinzione fra lettere maiuscole o minuscole, in qualsiasi espressione o assegnazione che usi tipi diversi si attua la conversione automatica. Gli int possono essere usati come puntatori a carattere (char *).
    void le funzioni possono essere dichiarate solo di tipo void, in sostanza si tratta di procedure; i parametri formali sono passati tutti per valore, si possono dichiarare variabili locali ma la somma del numero di parametri formali e di variabili locali è limitata a 10. Nella chiamata si devono mettere comunque le parentesi aperta e chiusa. È ammessa la ricorsione ma non possono essere dichiarate procedure locali.
    if, else sintassi classica, l'else si riferisce sempre all'if più vicino al quale non sia già associato un else.
    while sintassi classica
    read può leggere al massimo un carattere alla volta che viene restituito come intero
    write può scrivere una sequenza di espressioni e di stringhe costanti separate da parentesi.
    I commenti sono racchiusi tra i delimitatori "/*", "*/" e non possono essere innestati. Le istruzioni possono essere terminate da un punto e virgola, ma spesso può essere omesso. Si deve evitare di usare identificatori uguali alle parole chiave dell'assembler 68000 o alle etichette usate dal compilatore (una L seguita da un numero). Il numero complessivo di identificatori è limitato a 100. Le assegnazioni usano il solo segno uguale =.
    Le espressioni possono essere una qualsiasi combinazione valida dei seguenti elementi:
    +, -, *, /, (, ), |, ~, =, <, >, &, !
    


    Tiny 1.6 BNF
    <program> ::= <var-decl> <proc-decl> 
    <var-decl> ::= [<type> <var-list>]*
    <type> ::= INT | SHORT | CHAR
    <var-list> ::= <ident> [',' <ident>]*
    <ident> ::= <letter> [<letter> | <digit>]*
    <proc-decl> ::= [<proc>]*
    <proc> ::= VOID <ident> '(' <param-list> ')' <var-decl> 
               <begin-block>
    <param-list> ::= <type> <ident> [',' <type> <ident>]* | null
    <begin-block> ::= '{' <block> '}'
    <block> ::= [<statement>]*
    <statement> ::= <simple-stat> | <struct-stat> | <begin-block>
    <simple-stat> ::= <assignement> | <proc-call> | null
    <assignement> ::= ['*'] <ident> '=' <b-expression>
    <proc-call> ::= <ident> '(' <exp-list> ')'
    <exp-list> ::= <b-expression> [',' <b-expression>]* | null
    <struct-stat> ::= <if-stat> | <while-stat>
    <if-stat> ::= IF '(' <condition> ')' <statement> 
                           [ELSE <statement>] 
    <while-stat> ::= WHILE '(' <condition> ')' <statement>
    <condition> ::= <b-expression>
    <b-expression> ::= <b-term> [<orop> <b-term>]*
    <b-term> ::= <not-factor> ['&' <not-factor>]*
    <not-factor> ::= ['!'] <relation>
    <relation> ::= <expression> [<relop> <expression>]
    <expression> ::= <term> [<addop> <term>]*
    <term> ::= <signed factor> [<mulop> <factor>]*
    <signed factor>::= [<addop>] <factor>
    <factor> ::= [<punop>] <variable> | <integer> 
                 | '(' <b-expression> ')'
    <orop> ::= '|' | '~'
    <relop> ::= '==' | '<' | '<=' | '<>' | '>' | '>=' 
    <addop> ::= '+' | '-'
    <mulop> ::= '*' | '/'
    <punop> ::= '*' | '&'
    <variable> ::= <ident>
    <integer> ::= [<digit>]*
    

    7.2.2 La Struttura Dati

    La struttura dati è basata su tre tabelle di stringhe: una è usata per memorizzare le parole riservate, un'altra per gli identificatori di variabili globali e di funzioni, la terza tabella memorizza i parametri formali e le variabili locali. A ciascuna di queste tabelle è associata un'altra tabella di caratteri; questi caratteri sono utilizzati o come identificatori della parola riservata (per la prima tabella) o come indicatore di tipo (per le altre due tabelle). Ecco le definizioni:
        typedef char symbol[SYMBOLSIZE];
    symbol kwlist[NKW]; /* Lista Parole Chiave */ char kwcode[NKW+1]; /* Abbreviazioni Parole Chiave */ symbol st[MAXENTRY]; /* Tabella dei Simboli */ char stype[MAXENTRY+1]; /* Tipi dei Simboli */ symbol params[MaxParam]; /* Tabella dei Parametri */ char paramtyp[MaxParam]; /* Tipi dei Parametri */

    Gli elementi di queste tabelle sono memorizzati secondo l'ordine di arrivo e la ricerca è svolta in modo sequenziale. Gli sviluppi di questo compilatore dovranno esaminare la possibilità di cambiare queste strutture in tabelle ordinate con ricerca binaria o in tabelle Hash o in alberi o quanto di più sofisticato sia adatto per questo programma.

    7.2.3 Input, output e error

    Le funzioni di ingresso sono molto semplici, leggono un carattere alla volta dal flusso d'ingresso che può essere sia un file che la console. Per l'uscita invece vengono scritte in una volta sola intere stringhe che spesso coincidono con un'intera riga. Sono disponibili tre tipi di messaggi di errore: Expected, Undefined e Duplicate. L'I/O è stato predisposto per essere eseguito su due piattaforme dotate di sistemi operativi completamente diversi: Theos e DOS. Quest'ultima versione è stata approntata solo per fini di sviluppo.

    7.2.4 Scanner, analizzatore lessicale

    L'analizzatore lessicale raggruppa i singoli caratteri in "token", questa parte del codice distingue i caratteri numerici, alfabetici e speciali e li raggruppa se necessario, in particolare vengono riconosciuti gli identificatori come un carattere alfabetico seguito da caratteri alfanumerici.

    7.2.5 Parser, analizzatore sintattico e semantico

    Programma principale
    Il programma principale deve includere, nell'ordine, la dichiarazione di variabili e la definizione di procedure. Una delle procedure deve essere chiamata "main". Tutti gli identificatori devono essere dichiarati prima di essere usati.
    Funzioni utilizzate: main, topdecls
    Dichiarazioni di variabili
    Le dichiarazioni di variabili globali sono costituite da una serie lunga a piacere di identificatori di tipo seguiti, ciascuno, da una lista di variabili. I tipi possono essere scelti solo tra uno dei tre tipi predefiniti, cioè int, short e char. I primi due sono trattati con segno mentre l'ultimo senza segno. La lista di variabili è costituita da un identificatore che può essere seguito da una serie di identificatori preceduti da una virgola ciascuno.
    Funzioni utilizzate: topdecls, talloc, addentry, allocvar
    Definizioni di procedure
    Le definizioni di procedure sono una serie (anche vuota) di procedure. Ciascuna procedura deve incominciare con la parola chiave "void" a cui deve seguire un identificatore ed una lista di parametri formali; poi ci può essere la dichiarazione di variabili locali e, infine, ci deve essere un blocco di istruzioni.
    La lista di parametri formali è composta da una parentesi tonda aperta e da un numero arbitrario di identificatori di tipo, ciascuno seguito da un identificatore di variabile; alla fine ci deve essere una parentesi tonda chiusa. Anche se le variabili di tipo diverso occupano quantità diverse di memoria, tuttavia i parametri sullo stack occupano tutti la dimensione massima, cioè 4 byte. Ciò è dovuto al fatto che quando si chiama una procedura, il compilatore tiny non ha nessuna informazione sul numero e sul tipo di parametri; infatti nella symbol table viene memorizzato solo l'identificatore della procedura.
    La lista di variabili locali segue la stessa sintassi delle dichiarazioni di variabili globali ma viene allocato spazio sullo stack; perciò si provvede a sommare lo spazio utilizzato da ciascuna variabile per saper quanto spazio occupare con l'istruzione assembly "LINK". Gli identificatori delle variabili locali vengono posti su una tabella dei simboli locali assieme agli identificatori dei parametri formali, pertanto devono essere distinti da questi ultimi, ma possono essere identici ad identificatori di variabili globali.
    Funzioni utilizzate: formallist, locdecls, procprolog, beginblock, procepilog, clearparams, formalparam, renumparams, locdecl, addparam
    Blocco di istruzioni
    Un blocco di istruzioni incomincia con il simbolo '{' prosegue con un numero arbitrario di istruzioni e si conclude con il simbolo ". Ciascuna istruzione può essere un'istruzione semplice, un'istruzione composta o un blocco di istruzioni.
    Funzioni utilizzate: beginblock, block, statement
    Istruzioni semplici
    Le istruzioni semplici sono le assegnazioni e le chiamate di procedure. In entrambe i casi l'istruzione inizia con un identificatore. Si verifica, per prima cosa, che questo identificatore sia contenuto nella tabella locale dei simboli, se non lo si trova lo si va a cercare nella tabella globale dei simboli.
    Dalla tabella dei simboli si ricava se l'identificatore è riferito ad una variabile; in questo caso si cerca il segno uguale '=' e poi si valuta un'espressione, infine si chiama la routine per memorizzare il risultato dell'istruzione nella variabile.
    Se il primo identificatore si riferisce ad una procedura, viene letta la lista di parametri che verranno messi sullo stack, viene chiamata la subroutine desiderata e, infine, viene riportato lo stack nelle condizioni iniziali. La lista parametri consiste in una serie, anche vuota, di espressioni separate da virgola e racchiuse fra parentesi tonde. I risultati di tutte le espressioni, di qualunque tipo siano, vengono estesi a 32 bit prima di essere spinti sullo stack, perché non è noto con quali parametri è stata definita la procedura. Comunque anche le variabili vengono passate per valore.
    Funzioni utilizzate: assignorproc, assignment, callproc, typeof, paramlist, param
    Istruzioni composte
    Le istruzioni composte iniziano con una parola riservata e si dividono in due tipi: le istruzioni che controllano il flusso (if-else e while) e le chiamate di procedure predefinite.
    L'istruzione if riconosce dopo l'identificatore "IF", una condizione che non è altro che un'espressione racchiusa fra parentesi tonde; a questo punto crea una nuova etichetta e pone l'istruzione di salto nel caso l'espressione dia risultato nullo, a questo punto viene riconosciuta un'istruzione (che può essere composta da un blocco di istruzioni racchiuse fra parentesi graffe); se vi è una parola riservata "ELSE" che non è stata riferita ad un if più vicino, allora si crea una seconda etichetta, si pone un salto incondizionato alla seconda etichetta, si inserisce la prima etichetta e si legge un blocco di istruzioni. Infine quando termina l'istruzione, si inserisce l'ultima etichetta generata.
    L'istruzione while riconosce dopo l'identificatore "WHILE" una condizione che non è altro che un'espressione racchiusa da parentesi tonde, a questo punto crea due nuove etichette, inserisce la prima etichetta e pone l'istruzione di salto alla seconda etichetta nel caso l'espressione ritorni risultato nullo. In seguito viene riconosciuto un'istruzione che può essere composta da un blocco di istruzioni racchiuse tra parentesi graffe. In fine si pone un salto incondizionato alla prima etichetta seguito dalla seconda etichetta.
    Le procedure predefinite hanno sintassi analoga a quella delle procedure ma godono di alcune particolarità nel riconoscimento dei parametri (in alcuni casi ben determinati sono passati per indirizzo, in altri casi possono essere passate stringhe costanti). Di norma dopo aver riconosciuto i parametri e averli posti nei registri opportuni pongono una "TRAP" seguita da un opportuno codice. Come esempio porto il codice della funzione docreate che serve ad attivare la chiamata di sistema che crea un nuovo thread:
    /*--------------------------------------------*/
    /* Process a Thread_Create Statement */
    void docreate (e) TIMP *e;
    { next(e); /* leggi prossimo token */ matchstring(e,"("); /* verifica se token = parentesi */ if (typeof(e, e->value)=='p') /* se parametro e' una procedura */ createit(e, e->value); /* emetti chiamata exec */ else expected(e, "Procedure Descriptor"); next(e); /* leggi prossimo token */ matchstring(e,")"); /* verifica se e' un parentesi */
    }

    Funzioni utilizzate: doif, dowhile, doread, readvar, dowrite, writetoken, doexec, docreate, domutexcreate, domutexdestroy, dolock, dounlock, doexit, dokill.
    Espressioni
    la valutazione di un'espressione avviene mediante sette gradi di priorità con ricorsione. La forma generale per ciascun livello è:
    	<espressione> ::= <operando1> <operatore> <operando2>
    

    Questa sintassi vale per un'operazione binaria, nel caso di un'operazione unaria vale lo stesso schema con le dovute semplificazioni. Questa operazione viene tradotta in una forma generalizzata dal seguente pseudocodice:
    tipo1 = valuta(operando1)
    while (operatore) {
        push(tipo1)
        tipo2 = valuta(operando2)
        pop(tipo1)
        tipo1 = converti(tipo1,tipo2)
        operazione(tipo1)
    }
    restituisci tipo1
    

    a titolo di esempio si consideri l'operazione di prodotto logico (AND) la cui BNF è:
    	<b-term> ::= <not-factor1> ['&' <not-factor>]*
    

    Il codice corrispondente è:
    /*-------------------------------------------*/
    /* Parse and Translate a Boolean Term */
    char boolterm (e) TIMP *e; { char typ; typ = notfactor(e); while (e->token == '&') { char t; push(e, typ); next(e); t = notfactor(e); pop(e, typ); typ = sametype(e, typ, t); popand(e, typ); } return typ; }
    Conversione di tipo
    In ogni operazione i tipi degli operandi sono convertiti nel tipo del risultato. In genere il tipo del risultato è uguale al tipo più grande degli operandi. L'unica eccezione si ha nelle operazioni di moltiplicazione per cui il risultato è a 16 bit solo se entrambi gli operandi sono a 8 bit, altrimenti il risultato è a 32 bit. Il primo operando si trova nel registro D7 (vi viene messo dalla pop che lo preleva dallo stack) mentre il secondo è in D0 (tutti i risultati parziali vengono messi in D0). Vengono testati entrambe gli operandi per verificare se sia necessaria l'estensione. Se è necessaria, allora per ciascun operando si controlla se è di tipo byte, nel qual caso si estende a word ponendo a zero gli otto bit alti (si presume che le variabili di tipo char siano senza segno), poi si testa se l'altro operando è di tipo long, nel qual caso si estende tenendo conto del segno.
    Funzioni utilizzate: sametype, promote

    7.2.6 CodeGen, generatore di codice oggetto assembler

    Il generatore di codice è composto da una cinquantina di funzioni, ciascuna delle quali emette il codice assembler corrispondente ad una funzione elementare. In particolare la push sposta un operando dal registro D0 alla cima dello stack mentre la pop opera tra lo stack e il registro D7. Le funzioni di confronto estendono sempre il risultato da 8 a 16 bit perché l'istruzione assembler Scc genera un risultato ad 8 bit ma con segno (0 se la condizione è falsa, -1 se vera), pertanto se si dovesse estendere il risultato secondo le convenzioni utilizzate altrove in tiny (cioè considerando i numeri ad 8 bit senza segno) si otterrebbe 255 invece di -1. Come esempio riporto la setequal.
    /*-----------------------------------------------*/
    /* Set D0 If Compare was = */
    void setequal (e) TIMP *e; { emitln(e,"SEQ D0"); emitln(e,"EXT.W D0"); }

    7.3 Assemblatore

    L'assemblatore incluso in questo sistema è stato scritto da Paul McKee nel 1986. L'autore ha dichiarato che il programma dovrebbe essere portabile (con modifiche ragionevoli) in qualsiasi ambiente che usi il linguaggio C con interi a 32 bit. Tuttavia ci sono state grosse difficoltà nell'adattare al sistema operativo Theos questo assemblatore. Al momento l'assemblatore talvolta può fornire risultati scorretti o inaccurati, fortunatamente ci si può rendere conto facilmente se l'esecuzione è andata a buon fine oppure no. Un importante sviluppo futuro consisterà nel reperire un altro assemblatore che si adatti meglio al sistema.
    Questo programma è un assemblatore a due passi per i microprocessori 68000 e 68010. Riconosce l'intero insieme delle istruzioni e un ragionevole gruppo di direttive. Il file prodotto in uscita normalmente è un file binario eseguibile con lo stesso nome del file d'ingresso ma con estensione modificata in ".h68". È possibile ottenere anche un file di verifica (listing file) mediante l'opzione "-ln". Questo file, che ha estensione ".lis", riporta sia il codice in linguaggo macchina sia quello in assembly visualizzato riga per riga.
    Direttiva Descrizione
    ORG fissa la locazione iniziale
    EQU definisce simboli costanti
    SET definisce costanti ridefinibili
    REG definisce lista di registri
    DC definisce costante
    DCB definisce blocco costante
    DS definisce blocco non inizializzato
    END fine del file sorgente
    Tabella 7.2: Lista direttive dell'assemblatore

    Il file d'ingresso è un file di testo contenente istruzioni, direttive dell'assemblatore e commenti. Ciascuna riga può essere lunga al massimo 256 caratteri. Il file d'ingresso può avere qualsiasi estensione ma, per motivi d'ordine, tutti i file contenenti codice assembly hanno estensione ".a". L'assemblatore non distingue fra le lettere maiuscole e minuscole.
    Le direttive dell'assemblatore sono riportate nella tabella 7.2

    7.4 Esecuzione di processi

    Per mandare in esecuzione un file eseguibile basta digitarne il nome completo (non è riconosciuto nessun tipo di estensione). Eventualmente può essere utile terminare il comando con carattere ' che attiverà il processo in background.
    In sintesi i passi per creare una nuova applicazione sono i seguenti:
    1. scrivere un file sorgente utilizzando l'editor di testo. Per attivarlo la riga di comando sarà del tipo:
        programma.c 
      Si scrive il testo del programma nel linguaggio simile al C riconosciuto dal compilatore. Si termina la scrittura salvando il file con il comando
        Esc-z 
    2. compilare il file sorgente usando il compilatore tiny. La riga di comando dovrà includere anche il nome del file destinatario:
        programma.c programma.a 
    3. assemblare il file assembly scrivendo:
        programma.a 
    4. mandare in esecuzione il file eseguibile generato dall'assemblatore digitandone il nome:
        programma.h68 
      


    [Home] Back to Lucio's Home page.