Il C Ansi definisce una serie di funzioni matematiche come ad esempio sin(), cos(), sqrt(), exp() eccetera. Come un bell'esempio per impratichirsi con il meccanismo dell'ereditarietà, in questo paragrafo estenderemo le funzioni di libreria aggiungendo un parametro double e un risultato double al nostro calcolatore.
Queste funzioni lavorano più o meno come gli operatori unari. Dovremo definire un nuovo tipo di nodo per ogni funzioni e raccogliere la maggior parte delle funzionalità dalle classi Minus e Name, ma c'è una strada più semplice. Si estende struct Name in struct Math così come segue:
struct Math { struct Name _; double (* funct) (double); }; #define funct(tree) (((struct Math *) left(tree)) —> funct)
Oltre al nome della funzione da usare come input e il token per il riconoscimento dobbiamo memorizzare l'indirizzo della funzione di libreria nella tabella dei simboli. Durante l'inizializzazione chiamiamo la seguente funzione per inserire tutte le descrizioni delle funzioni nella tabella dei simboli.
#includevoid initMath (void) { static const struct Math functions [] = { { &_Math, "sqrt", MATH, sqrt }, ... 0 }; const struct Math * mp; for (mp = functions; mp —> _.name; ++ mp) install(mp); }
Una chiamata ad una funzione è un fattore, proprio come l'uso del segno meno. Per il riconoscitore dobbiamo estendere la nostra grammatica:
factor : NUMBER | — factor | ... | MATH ( sum )
MATH è il token per tutte le funzioni inizializzate da InitMath(). La porzione di codice seguente invece traduce invece ciò che deve essere aggiunto a factor() nel riconoscitore:
static void * factor (void) { void * result; ... switch (token) { case MATH: { const struct Name * fp = symbol; if (scan(0) != ’(’) error("expecting ("); scan(0); result = new(Math, fp, sum()); if (token != ’)’) error("expecting )"); break; }
Symbol contiene l'elemento della tabella dei simboli per funzioni come sin(). Salviamo il puntatore e costruiamo l'albero dell'espressione per l'argomento della funzione chiamando sum().
Lasciamo che il lato sinistro di un nodo binario punti al simbolo nella tabella degli elementi per la funzione e concateniamo l'argomento della funzione alla sua destra. Il nodo binario ha Math come descrittore di tipo ossia i metodi doMatch() e freeMath() saranno chiamato per eseguire e cancellare il nodo rispettivamente.
Il nodo Math è ancora una volta costruito con mkBin() perchè questa funzione non si occupa dei puntatori discendenti. FreeMath() comunque potrebbe cancellare il sottoalbero destro:
static void freeMath (void * tree) { delete(right(tree)); free(tree); }
Se guardiamo attentamente nella figura, possiamo vedere che l'esecuzione di un nodo Math è molto semplice. DoMath() chiama una qualsiasi funzione memorizzata nella tabella dei simboli accessibile come il discendente sinistro del nodo binario dalla quale è chiamata:
#includestatic double doMath (const void * tree) { double result = exec(right(tree)); errno = 0; result = funct(tree)(result); if (errno) error("error in %s: %s", ((struct Math *) left(tree)) —> _.name, strerror(errno)); return result; }
Il solo problema è rilevare gli errori numerici monitorando la variabile errno dichiarata nel file header errno.h, file header standard in ANSI-C. Ciò completa l'implementazione delle funzioni matematiche per il calcolatore.