Successivo: , Precedente: , Su: Funzioni di registrazione   [Contenuti][Indice]


17.4.5.4 Analizzatori di input personalizzati

Per default, gawk legge file di testo come input. Il valore della variabile RS è usato per determinare la fine di un record, e subito dopo la variabile FS (o FIELDWIDTHS o FPAT) viene usata per suddividerlo in campi (vedi la sezione Leggere file in input). Viene inoltre impostato il valore di RT (vedi la sezione Variabili predefinite).

Se lo si desidera, è possibile fornire un analizzatore di input personalizzato. Il compito di un analizzatore di input è di restituire un record al codice di gawk, che poi lo elaborerà, accompagnato, se necessario, da indicatori del valore e della lunghezza dei dati da usare per RT.

Per fornire un analizzatore personalizzato di input, occorre innanzitutto rendere disponibili due funzioni (dove XXX è un nome che fa da prefisso all’estensione intera):

awk_bool_t XXX_can_take_file(const awk_input_buf_t *iobuf);

Questa funzione esamina l’informazione disponibile in iobuf (che vedremo tra poco). Basandosi su tale informazione, decide se l’analizzatore di input personalizzato andrà usato per questo file. Se questo è il caso, dovrebbe restituire true. Altrimenti, restituirà false. Nessuno stato (valori di variabili, etc.) dovrebbe venire modificato all’interno di gawk.

awk_bool_t XXX_take_control_of(awk_input_buf_t *iobuf);

Quando gawk decide di passare il controllo del file a questo analizzatore di input, richiamerà questa funzione. Questa funzione a sua volta deve assegnare un valore ad alcuni campi nella struttura awk_input_buf_t e assicurarsi che alcune condizioni siano verificate. Dovrebbe poi restituire true. Se si verifica un errore di qualche tipo, i campi in questione non dovrebbero venire riempiti, e la funzione dovrebbe restituire false; in questo caso gawk non utilizzerà più l’analizzatore personalizzato di input. I dettagli sono descritti più avanti.

L’estensione dovrebbe raccogliere queste funzioni all’interno di una struttura awk_input_parser_t, simile a questa:

typedef struct awk_input_parser {
    const char *nome;   /* nome dell'analizzatore */
    awk_bool_t (*can_take_file)(const awk_input_buf_t *iobuf);
    awk_bool_t (*take_control_of)(awk_input_buf_t *iobuf);
    awk_const struct awk_input_parser *awk_const next;   /* per uso
                                                            di gawk */
} awk_input_parser_t;

I campi sono:

const char *nome;

Il nome dell’analizzatore di input. Questa è una normale stringa di caratteri del linguaggio C.

awk_bool_t (*can_take_file)(const awk_input_buf_t *iobuf);

Un puntatore alla funzione XXX_can_take_file().

awk_bool_t (*take_control_of)(awk_input_buf_t *iobuf);

Un puntatore alla funzione XXX_take_control_of().

awk_const struct input_parser *awk_const next;

Questa struttura è per uso di gawk; per questo motivo è marcata awk_const in modo che l’estensione non possa modificarla.

I passi da seguire sono i seguenti:

  1. Creare una variabile static awk_input_parser_t e inizializzarla adeguatamente.
  2. Quando l’estensione è caricata, registrare l’analizzatore personalizzato di input con gawk usando la funzione API register_input_parser() (descritta più sotto).

La definizione di una struttura awk_input_buf_t è simile a questa:

typedef struct awk_input {
    const char *nome;       /* nome file */
    int fd;                 /* descrittore di file */
#define INVALID_HANDLE (-1)
    void *opaque;           /* area dati privata
    			       per l'analizzatore di input */
    int (*get_record)(char **out, struct awk_input *iobuf,
                      int *errcode, char **rt_start, size_t *rt_len,
                      const awk_fieldwidth_info_t **field_width);
    ssize_t (*read_func)();
    void (*close_func)(struct awk_input *iobuf);
    struct stat sbuf;       /* buffer per stat */
} awk_input_buf_t;

I campi si possono dividere in due categorie: quelli che sono usati (almeno inizialmente) da XXX_can_take_file(), e quelli che sono usati da XXX_take_control_of(). Il primo gruppo di campi, e il loro uso, è così definito:

const char *nome;

Il nome del file.

int fd;

Un descrittore di file per il file. Se gawk riesce ad aprire il file, il valore di fd non sarà uguale a INVALID_HANDLE [descrittore non valido]. In caso contrario, quello sarà il valore.

struct stat sbuf;

Se il descrittore di file è valido, gawk avrà riempito i campi di questa struttura invocando la chiamata di sistema fstat().

La funzione XXX_can_take_file() dovrebbe esaminare i campi di cui sopra e decidere se l’analizzatore di input vada usato per il file. La decisione può dipendere da uno stato di gawk (il valore di una variabile definita in precedenza dall’estensione e impostata dal codice awk), dal nome del file, dal fatto che il descrittore di file sia valido o no, dalle informazioni contenute in struct stat o da una qualsiasi combinazione di questi fattori.

Una volta che XXX_can_take_file() restituisce true, e gawk ha deciso di usare l’analizzatore personalizzato, viene chiamata la funzione XXX_take_control_of(). Tale funzione si occupa di riempire il campo get_record oppure il campo read_func nella struttura awk_input_buf_t. La funzione si assicura inoltre che fd not sia impostato al valore INVALID_HANDLE. L’elenco seguente descrive i campi che possono essere riempiti da XXX_take_control_of():

void *opaque;

Questo campo è usato per contenere qualiasi informazione di stato sia necessaria per l’analizzatore di input riguardo a questo file. Il campo è “opaco” per gawk. L’analizzatore di input non è obbligato a usare questo puntatore.

int (*get_record)(char **out,
                  struct awk_input *iobuf,
                  int *errcode,
                  char **rt_start,
                  size_t *rt_len,
                  const awk_fieldwidth_info_t **field_width);

Questo puntatore a funzione dovrebbe puntare a una funzione che crea i record in input. Tale funzione è il nucleo centrale dell’analizzatore di input. Il suo modo di operare è descritto nel testo che segue questo elenco.

ssize_t (*read_func)();

Questo puntatore a funzione dovrebbe puntare a una funzione che ha lo stesso comportamento della chiamata di sistema standard POSIX read(). È in alternativa al puntatore a get_record. Il relativo comportamento è pure descritto nel testo che segue quest’elenco.

void (*close_func)(struct awk_input *iobuf);

Questo puntatore a funzione dovrebbe puntare a una funzione che fa la “pulizia finale”. Dovrebbe liberare ogni risorsa allocata da XXX_take_control_of(). Può anche chiudere il file. Se lo fa, dovrebbe impostare il campo fd a INVALID_HANDLE.

Se fd è ancora diverso da INVALID_HANDLE dopo la chiamata a questa funzione, gawk invoca la normale chiamata di sistema close().

Avere una funzione di “pulizia” è opzionale. Se l’analizzatore di input non ne ha bisogno, basta non impostare questo campo. In questo caso, gawk invoca la normale chiamata di sistema close() per il descrittore di file, che, quindi, dovrebbe essere valido.

La funzione XXX_get_record() svolge il lavoro di creazione dei record in input. I parametri sono i seguenti:

char **out

Questo è un puntatore a una variabile char * che è impostatata in modo da puntare al record. gawk usa una sua copia locale dei dati, quindi l’estensione deve gestire la relativa area di memoria.

struct awk_input *iobuf

Questa è la struttura awk_input_buf_t per il file. I campi dovrebbero essere usati per leggere i dati (fd) e per gestire lo stato privato (opaque), se necessario.

int *errcode

Se si verifica un errore, *errcode dovrebbe essere impostato a un valore appropriato tra quelli contenuti in <errno.h>.

char **rt_start
size_t *rt_len

Se il concetto “fine record” è applicabile, *rt_start dovrebbe essere impostato per puntare ai dati da usare come RT, e *rt_len dovrebbe essere impostata alla lunghezza di quel campo. In caso contrario, *rt_len dovrebbe essere impostata a zero. gawk usa una sua copia di questi dati, quindi l’estensione deve gestire tale memoria.

const awk_fieldwidth_info_t **field_width

Se field_width non è NULL, *field_width sarà inizializzato a NULL, e la funzione può impostarlo per puntare a una struttura che fornisca l’informazione sulla lunghezza del campo, che sarà utilizzata al posto di quella determinata dall’analizzatore di default del campo. Si noti che questa struttura non sarà copiata da gawk; inoltre essa deve rimanere disponibile almeno fino alla successiva chiamata a get_record o a close_func. Si noti inoltre che field_width vale NULL quando getline sta assegnando i risultati a una variabile, e quindi un’analisi del campo non è necessaria. Se l’analizzatore imposta *field_width, allora gawk usa questa descrizione per analizzare il record in input, e il valore di PROCINFO["FS"] sarà "API" finché questo record rimane corrente come $0. La struttura dati awk_fieldwidth_info_t è descritta qui di seguito.

Il codice di ritorno è la lunghezza del buffer puntato da *out oppure EOF, se è stata raggiunta la fine del file o se si è verificato un errore.

Poiché errcode è sicuramente un puntatore valido, non c’è bisogno di controllare che il valore sia NULL. gawk imposta *errcode a zero, quindi non c’è bisogno di impostarlo, a meno che non si verifichi un errore.

In presenza di un errore, la funzione dovrebbe restituire EOF e impostare *errcode a un valore maggiore di zero. In questo caso, se *errcode non è uguale a zero, gawk automaticamente aggiorna la variabile ERRNO usando il valore contenuto in *errcode. (In generale, impostare ‘*errcode = errno’ dovrebbe essere la cosa giusta da fare.)

Invece di fornire una funzione che restituisce un record in input, è possibile fornirne una che semplicemente legge dei byte, e lascia che sia gawk ad analizzare i dati per farne dei record. In questo caso, i dati dovrebbero essere restituiti nella codifica multibyte propria della localizzazione corrente. Una siffatta funzione dovrebbe imitare il comportamento della chiamata di sistema read(), e riempire il puntatore read_func con il proprio indirizzo nella struttura awk_input_buf_t.

Per default, gawk imposta il puntatore read_func in modo che punti alla chiamata di sistema read(). In questo modo l’estensione non deve preoccuparsi di impostare esplicitamente questo campo.

NOTA: Occorre decidere per l’uno o per l’altro metodo: o una funzione che restituisce un record o una che restituisce dei dati grezzi. Nel dettaglio, se si fornisce una funzione che prepara un record, gawk la invocherà, e non chiamerà mai la funzione che fa una lettura grezza.

gawk viene distribuito con un’estensione di esempio che legge delle directory, restituendo un record per ogni elemento contenuto nella directory (vedi la sezione Leggere directory. Questo codice sorgente può essere usato come modello per scrivere un analizzatore di input personalizzato.

Quando si scrive un analizzatore di input, si dovrebbe progettare (e documentare) il modo con cui si suppone che interagisca con il codice awk. Si può scegliere di utilizzarlo per tutte le letture, e intervenire solo quando è necessario, (come fa l’estensione di esempio readdir). Oppure lo si può utilizzare a seconda del valore preso da una variabile awk, come fa l’estensione XML inclusa nel progetto gawkextlib (vedi la sezione Il progetto gawkextlib). In quest’ultimo caso, il codice in una regola BEGINFILE può controllare FILENAME ed ERRNO per decidere se attivare un analizzatore di input (vedi la sezione I criteri di ricerca speciali BEGINFILE ed ENDFILE) oppure no.

Un analizzatore di input va registrato usando la seguente funzione:

void register_input_parser(awk_input_parser_t *input_parser);

Registra l’analizzatore di input puntato da input_parser con gawk.

Se si vuole ignorare il meccanismo di default per l’analisi dei campi per un determinato record, si deve riempire una struttura awk_fieldwidth_info_t che ha questo aspetto:

typedef struct {
        awk_bool_t     use_chars; /* falso ==> usare byte */
        size_t         nf;        /* numero di campi nel record (NF) */
        struct awk_field_info {
                size_t skip;      /* da ignorare prima dell'inizio di un campo */
                size_t len;       /* lunghezza del campo */
        } fields[1];              /* la dimensione effettiva dovrebbe essere nf */
} awk_fieldwidth_info_t;

I campi sono:

awk_bool_t use_chars;

Impostare ad awk_true se le lunghezze di campo sono specificate in unità di caratteri potenzialmente multi-byte, oppure impostarlo a awk_false se le lunghezze sono espresse in numero di byte. L’efficienza del programma sarà maggiore utilizzando la dimensione in byte.

size_t nf;

Impostare al numero di campi nel record in input, cioè a NF.

struct awk_field_info fields[nf];

Questo è un vettore di lunghezza variabile la cui dimensione effettiva dovrebbe essere nf. Per ogni campo, l’elemento skip dovrebbe essere impostato al numero di caratteri o byte, come richiesto dal flag use_chars, da saltare prima dell’inizio di questo campo. L’elemento len fornisce la lunghezza del campo. I valori in fields[0] forniscono l’informazione per $1, e così via, fino all’elemento fields[nf-1] che contiene l’informazione per $NF.

Una macro di utilità awk_fieldwidth_info_size(numfields) è disponibile per calcolare la dimensione appropriata della struttura a lunghezza variabile awk_fieldwidth_info_t che contiene numfields campi. Questa dimensione può essere usata come argomento per malloc() o in una struttura union per allocare spazio staticamente. Per un esempio si può vedere l’estensione di esempio readdir_test.


Successivo: , Precedente: , Su: Funzioni di registrazione   [Contenuti][Indice]