• No results found

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 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 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 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

unionis 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