ANSI
-C defines a number of mathematical functions such assin(),sqrt(),exp(), etc.
As another exercise in inheritance, we are going to add library functions with a sin-
gledoubleparameter and adoubleresult to our calculator.
These functions work pretty much like unary operators. We could define a new
type of node for each function and collect most of the functionality fromMinusand
theNameclass, but there is an easier way. We extend
struct Name
into
struct
Mathas follows:
struct Math { struct Name _; double (* funct) (double); };
53
___________________________________________________________________________
5.8 Mathematical Functions — ‘‘Math’’In addition to the function name to be used in the input and the token for recogni-
tion we store the address of a library function likesin()in the symbol table entry.
During initialization we call the following function to enter all the function
descriptions into the symbol table:
#include <math.h> void initMath (void)
{ static const struct Math functions [] = { { &_Math, "sqrt", MATH, sqrt }, ...
0 };
const struct Math * mp;
for (mp = functions; mp —> _.name; ++ mp) install(mp);
}
A function call is a factor just like using a minus sign. For recognition we need
to extend our grammar for factors:
factor : NUMBER | — factor | ...
| MATH ( sum )
MATH
is the common token for all functions entered by
initMath(). This translates
into the following addition tofactor()in the recognizer:
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;
}
symbolfirst contains the symbol table element for a function like
sin(). We save
the pointer and build the expression tree for the function argument by callingsum().
Then we use
Math, the type description for the function, and let
new()
build the
following node for the expression tree:
•
•
•
struct Binsum
•
"sin" MATH sin() struct Math mkBin() doMath() freeMath() MathWe let the left side of a binary node point to the symbol table element for the func-
tion and we attach the argument tree at the right. The binary node hasMathas a
type description, i.e., the methods
doMath()and
freeMath()will be called to exe-
cute and delete the node, respectively.
TheMathnode is still constructed withmkBin()because this function does not
care what pointers it enters as descendants.
freeMath(), however, may only
delete the right subtree:
static void freeMath (void * tree) {
delete(right(tree)); free(tree);
}
If we look carefully at the picture, we can see that execution of aMathnode is
very easy.
doMath()needs to call whatever function is stored in the symbol table
element accessible as the left descendant of the binary node from which it is called:
#include <errno.h>
static 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; }
The only problem is to catch numerical errors by monitoring the
errno
variable
declared in the
ANSI-C header file
errno.h. This completes the implementation of
mathematical functions for the calculator.
55
___________________________________________________________________________
5.9 Summary5.9 Summary
Based on a function
binary()
for searching and inserting into a sorted array, we
have implemented a symbol table containing structures with a name and a token
value. Inheritance permitted us to insert other structures into the table without
changing the functions for search and insertion. The elegance of this approach
becomes apparent once we consider a conventional definition of a symbol table ele-
ment for our purposes:
struct {
const char * name; int token;
union { /* based on token */
double value;
double (* funct) (double); } u;
};