Progetto Laboratorio Programmazione
Gestione di eoln/eof e funzioni getch/ungetch

Stefano Guerrini

A.A. 2000/01

Dall'analisi delle soluzioi inviate. Uno dei punti che sembrano meno chiari nella gestione dell'input del progetto è l'uso delle funzioni getch e ungetch.

Come già discusso a lezione e nel testo della prima parte del progetto, lo scopo principale di getch e ungetch è permettere una certa reversibilità nell'acquisizione dell'input. In certi casi, l'analizzatore lessicale si trova nella situazione di poter decidere se un token è terminato solo dopo aver letto il primo carattere del successivo token. La coppia di funzioni getch/ungetch assicura una bufferizzazione dell'input ad (almeno) un carattere e permette di reinserire in testa allo stream di input il carattere erroneamente letto. Di conseguenza, usando in modo appropriato getch e ungetch si può assicurare che il primo carattere disponibile in testa allo stream di input o nel buffer sia sempre quello iniziale del token che si deve acquisire.

Va però detto che l'uso della bufferizzazione dell'input può causare problemi se utilizato in modo improprio o in modo non pulito. In particolare, è molto facile scrivere programmi complicati o errati se si combinano letture dirette dall'input mediante read o readln e letture bufferizzate mediante la getch. A tale scopo è consigliabile che, se si usa la getch, tutte le letture dell'input avvengano per mezzo di tale funzione; in pratica, che il programma non contenga altre read/readln oltre a quelle necessarie per implementare la getch. Analogamente, è anche consigliabile programmare la getch in modo che la gestione del fine-file o del fine-linea avvengano attraverso il carattere letto dalla getch.

Implementazione di getch

Vediamo ora in dettaglio una possibile soluzione poer l'implementazione di getch/ungetch che permetta di gestire anche fine-linea e fine-file.

Per prima cosa, come già detto a lezione, usiamo una variabile globale per il buffer attraverso cui avviene la lettura. Le corrispondenti definizioni saranno:

type
   tipo_buffer = record
                    empty: boolean;
                    val  : char
                 end;
var
   buf: tipo_buffer;
L'idea per la gestione del fine-file e del fine-linea è:
  1. se si cerca di leggere un carattere quando il buffer è vuoto e ci si trova alla fine di una riga del file di input, allora getch ritorna il carattere CR (corrispondente al cosiddetto carriage return o ritorno carrello) e si posiziona all'inizio della successiva riga (si osservi che occorre garantire che alla prossima chiamata di getch il carattere ritornato sia il primo carattere della nuova riga).
  2. se si cerca di leggere un carattere quando il buffer è vuoto e ci si trova alla fine del file di input, allora getch ritorna il carattere NULL, ovvero il carattere con numero di ordine 0.
Vediamo in dettaglio il codice delle funzioni che realizzano il comportamento sopra descritto. Per prima cosa, vediamo le definizioni per le due costanti CR e NULL di tipo char.
const
   NULL = chr(0);
   CR   = chr(13);

Quindi vediamo le definizioni delle funzioni. La ungetch è molto semplice e non richiede particolari commenti. Si osservi solo che non viene effettuato alcun controllo sul contenuto del buffer. In pratica, se si fa l'ungetch di un carattere quando il buffer è pieno, il contenuto del buffer al momento dell'ungetch è perso.

procedure ungetch(ch : char);
begin
   buf.val := ch;
   buf.empty := false;
end;

Passiamo ora a vedere la getch, supponendo che la lettura dell'input avvenga dal file assegnato alla variabile fl di tipo TEXT.

function getch: char;
var
   ch : char; { var temporanea per la lettura del carattere }
begin
   if (buf.empty) then
   begin
      if (eof(fl)) then
         ch := NULL  { segnala il fine-file }
      else if (eoln(fl)) then
      begin
         readln(fl); { avanza alla riga successiva }
         ch := CR    { segnala il fine-linea       }
      end
      else begin
         read(fl, ch); { legge il successivo carattere nel file }
      end
   end
   else begin            { il buffer non e' vuoto        }
      buf.empty := true; { svuota il buffer              }
      ch := buf.val      { legge il contenuto del buffer }
   end;
   getch := ch
end;

Si noti in particolare il caso in cui ci si trova a fine-linea: il comando readln(fl) assicura il posizionamento all'inizio della riga successiva. Metodi che si basano sulla conoscenza della particolare maniera in cui il fine-linea è rapresentato nel file di testo non sono da considerare corretti, dato che fanno dipendere il comportamento del programma dal sistema su cui lo si compila e utilizza. Ad esempio, in DOS il fine-linea è rappresentato da una coppia di caratteri (carriage-return seguito da line-feed), mentre in altri sistemi la rappresentazione del fine-linea è un solo carattere (nel MAC solo line-feed, in UNIX solo carriage-return). La tecnica presentata nell'esempio funziona correttamente indipendentemente dalla raprresentazione del fine-linea e quindi indipendentemente dal sistema su cui si compila ed esegue il programma.

Un esempio completo

Riportiamo qui di seguito il codice di un semplice programma che usa le funzioni getch e ungetch utilizzando il valore di ritorno della getch per gestire fine-linea e fine-file.

Per rendere il programma più generale, si è assunto che il file da cui leggere l'input e il buffer da utilizzare siano dei parametri delle funzioni getch e ungetch. In questo modo è possibile pensare ad una generalizzazione delle funzioni al caso in cui l'input è preso simultaneamente da più file.

L'esempio mostra anche come mantenere l'informazione relativa alla posizione corrente del carattere (riga e colonna).

program prova_getch(input, output);

{ Prova la gestione di un input bufferizzato ad un carattere mediante la   }
{ coppia di funzioni getch/ungetch.                                        }
{ Fa vedere come gestire eof e eoln direttamente attraverso la getch.      }
{ Mostra come realizzare le funzioni in modo che possano essere utilizzate }
{ su pi\`u file e con buffer diversi.                                      }
{ Mostra anche come contare il numero di riga e di colonna dell'input.     }
 
{ Input: legge mediante getch una sequenza di righe di testo fino          }
{ alla fine del file.                                                      }
{ Output: stampa i caratteri letti, rimpiazzandone alcuni con degli spazi  }
{ bianchi; il rimpiazzamenteo viene fatto usando la ungetch. Alla fine     }
{ della riga viene stampato il numero di riga corrente e la sua lunghezza. }

const
   NULL = chr(0);      { carattere usato per il fine file   }
   CR   = chr(13);     { carriage return o ritorno carrello }

type
   tipo_buffer = record
                    empty : boolean;
                    val   : char
                 end;

var            
   buf      : tipo_buffer;
   ch       : char;
   col, rig : integer;

{ Le funzioni getch e ungetch prendono come parametro il buffer  }
{ da utilizzare.                                                 }
{ La getch prende come parametro anche il file da cui leggere.   }
{ Notare che tali parametri devono essere passati per variabile. }

procedure ungetch(var buf : tipo_buffer; ch : char);
begin
   buf.val := ch;
   buf.empty := false;
end;

function getch(var buf : tipo_buffer; var fl: TEXT): char;
var
   ch : char; { var temporanea per la lettura del carattere }
begin
   if (buf.empty) then
   begin
      if (eof(fl)) then
         ch := NULL  { segnala il fine-file }
      else if (eoln(fl)) then
      begin
         readln(fl); { avanza alla riga successiva }
         ch := CR    { segnala il fine-linea       }
      end
      else begin
         read(fl, ch); { legge il successivo carattere nel file }
      end
   end
   else begin            { il buffer non e' vuoto        }
      buf.empty := true; { svuota il buffer              }
      ch := buf.val      { legge il contenuto del buffer }
   end;
   getch := ch
end;

begin
   buf.empty := true;        { inizializza il buffer a vuoto }
   ch := getch(buf, input);  { legge il primo carattere      }
   col := 1; rig := 1;       { posizione iniziale col=rig=1  }
   while (ch <> NULL) do
   begin { il file non e' finito }
      while ((ch <> CR) and (ch <> NULL)) do 
         { NB Attenzione al controllo su NULL.                    }
         { Serve a gestire un file che finisce senza un fine riga }
      begin { si trova all'interno della riga }
         if (ch in ['.',',',';',':']) then
         begin { prova a fare una ungecth }
            ungetch(buf, ' ');
            col := col-1; { ritorna indietro di una colonna }
         end else 
            write(ch);            { stampa il carattere letto     }
         ch := getch(buf, input); { legge il carattere successivo }
         col := col+1;            { avanza di una colonna         }
      end;
      { si trova a fine riga }
      rig := rig+1;                         { avanzo il numero di riga     }
      writeln('<', rig-1, ',', col-1, '>'); { stampo riga e num col totali }
      ch := getch(buf, input); { legge il primo carattere della nuova riga }
      col := 1;                { si riporta alla prima colonna }
   end
end.

About this document ...

Progetto Laboratorio Programmazione
Gestione di eoln/eof e funzioni getch/ungetch

This document was generated using the LaTeX2HTML translator Version 99.2beta8 (1.42)

Copyright © 1993, 1994, 1995, 1996, Nikos Drakos, Computer Based Learning Unit, University of Leeds.
Copyright © 1997, 1998, 1999, Ross Moore, Mathematics Department, Macquarie University, Sydney.

The command line arguments were:
latex2html -split 0 -nonavigation getch.tex

The translation was initiated by Stefano Guerrini on 2001-06-20


Stefano Guerrini 2001-06-20