• No results found

ANSI-C defines a number of mathematical functions such as sin(), sqrt(), exp(), etc. As another exercise in inheritance, we are going to add library functions with a sin- gle double parameter and a double result 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 from Minus and the Name class, but there is an easier way. We extend struct Name into struct Math as 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 like sin() 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 )

MATHis the common token for all functions entered by initMath(). This translates into the following addition to factor() 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;

}

symbol first contains the symbol table element for a function like sin(). We save the pointer and build the expression tree for the function argument by calling sum(). Then we use Math, the type description for the function, and let new() build the following node for the expression tree:

• • • struct Bin sum • "sin" MATH sin() struct Math mkBin() doMath() freeMath() Math

We 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 has Math as a type description, i.e., the methods doMath() and freeMath() will be called to exe- cute and delete the node, respectively.

The Math node is still constructed with mkBin() 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 a Math node 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 Summary

5.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;

};

For keywords, the union is unnecessary. User defined functions would require a much more elaborate description, and referencing parts of the union is cumber- some.

Inheritance permits us to apply the symbol table functionality to new entries without changing existing code at all. Dynamic linkage helps in many ways to keep the implementation simple: symbol table elements for constants, variables, and functions can be linked into the expression tree without fear that we delete them inadvertently; an execution function concerns itself only with its own arrangement of nodes.

5.10 Exercises

New keywords are necessary to implement things like while or repeat loops, if statements, etc. Recognition is handled in stmt(), but this is, for the most part, only a problem of compiler construction, not of inheritance. Once we have decided on the type of statement, we will build node types like While, Repeat, or IfElse, and the keywords in the symbol table need not know of their existence.

A bit more interesting are functions with two arguments like atan2() in the mathematical library of ANSI-C. From the point of view of the symbol table, the functions are handled just like simple functions, but for the expression tree we need to invent a new node type with three descendants.

User defined functions pose a really interesting problem. This is not too hard if we represent a single parameter by $ and use a node type Parm to point back to the function entry in the symbol table where we can temporarily store the argument value as long as we do not permit recursion. Functions with parameter names and several parameters are more difficult, of course. However, this is a good exercise to investigate the benefits of inheritance and dynamic linkage. We shall return to this problem in chapter 11.

6

Class Hierarchy

Maintainability