Programmazione orientata agli oggetti in ANSI-C. Un framework applicativo – Filter

Analizzare una linea di comando è un problema generale comune a tutti i programmi filtro. Dobbiamo riconoscere due segni meno – ossia la fine della lista delle opzioni – oppure un singolo segno meno, dobbiamo riconoscere i valori delle opzioni, dobbiamo riconoscere quale è lo standard input e potremmo dover leggere lo standard input oppure un file di argomenti.

Ogni programma filtro contiene più o meno lo stesso codice per realizzare tutto ciò, ossia interpretare comandi e opzioni da linea di comando, e macro come MAIN [SCH87, capitolo 15] o funzioni come getopt(3) aiutano a mantenere uno standard di comportamento, ma perchè riproporre sempre lo stesso codice più o meno all'inizio del programma.

La classe Filter è stata progettata per fornire un'implementazione uniforme dell'analisi della linea di comando per tutti i programmi filtro. Può essere chiamata da un framework applicativo perchè stabilisce le regole base e la struttura di partenza per una famiglia più ampia di applicazioni.

Il metodo mainLoop() contiene la linea di comando da analizzare una volta per tutte e utilizza le funzioni di callback per permettere al client di avere a che fare direttamente con gli argomenti estratti.

% mainLoop { // (self, argv)
%casts
self —> progname = * argv ++;

while (* argv && ** argv == ’—’)
{ switch (* ++ * argv) {
case 0: // single —
—— * argv; // ... is a filename
break; // ... and ends options
case ’—’:
if (! (* argv)[1]) // two ——
{ ++ argv; // ... are ignored
break; // ... and end options
}
default: // rest are bundled flags
do
if (self —> flag)
{ self —> argv = argv;
self —> flag(self —> delegate,
self, ** argv);
argv = self —> argv;
}
else
{ fprintf(stderr,
"%s: —%c: no flags allowed\n",
self —> progname, ** argv);
return 1;
}
while (* ++ * argv);
++ argv;
continue;
}
break;
}

Il loop più esterno analizza gli argomenti finchè raggiungiamo il puntatore nullo che termina l'array argv[] o finchè un argomento non inizia con segno meno. Uno o due segni meno terminano il loop più esterno con un'istruzione break.

Il ciclo più interno analizza ogni carattere di un argomento tramite la funzione flag fornita dal delegato. Se il delegato decide che un flag introduce un'impzione con un valore, il metodo argval() fornisce un callback dal delefato al filtro per recuperare il valore dell'opzione:

 % argval { // (self)
const char * result;
%casts
assert(self —> argv && * self —> argv);
if ((* self —> argv)[1]) // —fvalue
result = ++ * self —> argv;
else if (self —> argv[1]) // —f value
result = * ++ self —> argv;
else // no more argument
result = NULL;

while ((* self —> argv)[1]) // skip text
++ * self —> argv;
return result;
}

Il valore dell'opzione è o il resto dell'argomento flag o il successivo argomento se ve ne sono. self->argv è avanzato così che l'inner loop di mainLoop() termina.

Una volta che le opzioni sono state prelevate dalla linea di comando, resta l'argomento nome del file. Se non ve ne sono, il programma filtro funziona con lo standard input. MainLoop() continua come segue:

if (* argv)
do
result = doit(self, * argv);
while (! result && * ++ argv);
else
result = doit(self, NULL);
if (self —> quit)
result = self —> quit(self —> delegate, self);
return result;
}

Facciamo in modo che il metodo doit() si occupi di un argomento nome del file alla volta. Un puntatore nullo rappresenta la situazione per cui non vi sono argomenti. Doit() produce un codice di errore: solo se è zero dovremmo analizzare più argomenti:

% doit { // (self, arg)
FILE * fp;
int result = 0;
%casts
if (self —> name)
return self —> name(self —> delegate, self, arg);
if (! arg || strcmp(arg, "—") == 0)
fp = stdin, clearerr(fp);
else if (! * arg)
{ fprintf(stderr, "%s: null filename\n",
self —> progname);
return 1;
}
else if (! (fp = fopen(arg, "r")))
{ perror(arg);
return 1;
}

Il client può fornire una funzione per analizzare l'argomento nome del file. Altrimenti, doit() si collega con lo stdin per via di un puntatore nullo o per via di un segno meno passato come argomento; gli altri filenames sono aperti in lettura. Una volta che il file viene aperto il client può passare oltre con un'altra funzione di callback ooppure doit() si preccupa di allocare un buffer dinamico e inizia quindi a leggere le linee:

if (self —> file)
result = self —> file(self —> delegate, self, arg, fp);
else
{ if (! self —> buf)
{ self —> blen = BUFSIZ;
self —> buf = malloc(self —> blen);
assert(self —> buf);
}
while (fgets(self —> buf, self —> blen, fp))
if (self —> line && (result =
self —> line(self —> delegate, self, arg,
self —> buf)))
break;
if (self —> wrap)
result = self —> wrap(self —> delegate, self, arg);
}
if (fp != stdin)
fclose(fp);
if (fflush(stdout), ferror(stdout))
{ fprintf(stderr, "%s: output error\n", self —> progname);
result = 1;
}
return result;

Con altre due funzioni di callbacks il client può ricevere ogni linea di testo ed effettuare le opportune azioni di pulizia una volta che il file è completato. Queste sono le funzioni che wc utilizza. Doit() ricicla il file pointer e verifica che l'output è stato scritto con successo.

Se una classe client implementa un callbacks orientato alla linea dalla classe Filter, dovrebbe essere a conoscenza del fatto che ha a che fare con linee di testo. Fgets() legge l'input finchè il buffer non è pieno oppure finchè un carattere di a capo viene trovato. Codice aggiuntivo in doit() estende il buffer dinamico come richiesto, ma si limita a passare il contenuto del buffer al client: non si occupa di variare la dimensione del buffer. Fgets() non restituisce il numero di caratteri letti, ossia se c'è un byte null nell'imput il client non ha modo di accorgersene perchè il byte null potrebbe marcare la fine del buffer precedente di un file che non ha una una newline a terminazione.

Scarica subito una copia gratis

Scrivi un commento

Seguici anche sul tuo Social Network preferito!

Send this to a friend