• No results found

Obviously,gate()is a dynamically bound method, because the subclasses ofIcuse

it to receive and process information.

Ic

itself owns

out, the pointer to another

object to which information must be sent.

Icitself is mostly an abstract base class,

butIc_gate()can accessoutand actually pass information on:

% Ic gate { %casts

return self —> out ? gate(self —> out, item) : reject; }

This is motivated by information hiding: if a subclass’gatemethod wants to send

information on, it can simply callsuper_gate().

wire()

is trivial: it connects an

Ic

to another object by storing the object

address inout:

% Ic wire { %casts

self —> out = to; }

Once the multiplexer class

Muxis invented, we realize that

wire(), too, must be

dynamically linked.

Muxoverwriteswire()to add its target object to aList:

% Mux wire { // add another receiver %casts

addLast(self —> list, to); }

Mux_gate()can be defined in various ways. Generally, it has to offer the incoming

information to some target objects; however, we can still decide in which order we

do this and whether or not we want to quit once an object accepts the information

— there is room for subclasses!

% Mux gate { // sends to first responder unsigned i, n;

enum react result = reject; %casts

n = count(self —> list); for (i = 0; i < n; ++ i)

{ result = gate(lookAt(self —> list, i), item); if (result == accept)

break; }

return result; }

This solution proceeds sequentially through the list in the order in which it was

created, and it quits as soon as one invocation ofgate()returnsaccept.

LineOutis needed so that we can test a computing chip without connecting it

to a graphical user interface.

gate()

has been defined leniently enough so that

LineOut_gate()is little more than a call toputs():

% LineOut gate {

%casts

assert(item);

puts(item); // hopefully, it is a string return accept;

}

Of course,LineOutwould be more robust, if we used aStringobject as input.

The classes built thus far can actually be tested. The following example

hello

connects anIcto aMuxand from there first to two moreIcobjects and then twice

to aLineOut. Finally, a string is sent to the firstIc:

int main ()

{ void * ic = new(Ic()); void * mux = new(Mux()); int i;

void * lineOut = new(LineOut()); for (i = 0; i < 2; ++ i) wire(new(Ic()), mux); wire(lineOut, mux); wire(lineOut, mux); wire(mux, ic); puto(ic, stdout);

gate(ic, "hello, world"); return 0;

}

The output shows the connections described byputo()and the string displayed by

theLineOut:

176

___________________________________________________________________________

14 Forwarding Messages — A GUI Calculator

$ hello Ic at 0x182cc wired to Mux at 0x18354 wired to [nil] list List at 0x18440 dim 4, count 4 { Ic at 0x184f0 wired to [nil] Ic at 0x18500 wired to [nil] LineOut at 0x184e0 wired to [nil] LineOut at 0x184e0 wired to [nil] } hello, world

Although theMuxobject is connected to theLineOutobject twice,hello, worldis

output only once, because the

Mux

object passes its information only until some

gate()returnsaccept.

Before we can implementButtonwe have to make a few assumptions about

the

Event

class. An

Event

object contains the information that is normally sent

from one

Ic

to another. Information from the keyboard can be represented as a

string, but a mouse click or a cursor position looks different. Therefore, we let an

Event

contain a number

kind

to characterize the information and a pointer

data

which hides the actual values:

% Event ctor { // new(Event(), 0, "text") etc.

struct Event * self = super_ctor(Event(), _self, app); self —> kind = va_arg(* app, int);

self —> data = va_arg(* app, void *); return self;

}

Now we are ready to design aButtonas an information filter: if the incoming

Eventis a string, it must match the button’s text; any other information is accepted

sight unseen, as it should have been checked elsewhere already. If the

Eventis

accepted,Buttonwill send its text on:

% Button ctor { // new(Button(), "text")

struct Button * self = super_ctor(Button(), _self, app); self —> text = va_arg(* app, const char *);

return self; }

% Button gate { %casts

if (item && kind(item) == 0

&& strcmp(data(item), self —> text)) return reject;

return super_gate(Button(), self, self —> text); }

This, too, can be checked with a small test program

button

in which a

Button

is

wired to aLineOut:

int main ()

{ void * button, * lineOut; char buf [100];

lineOut = new(LineOut()); button = new(Button(), "a"); wire(lineOut, button); while (gets(buf))

{ void * event = new(Event(), 0, buf); if (gate(button, event) == accept)

break; delete(event); }

return 0; }

buttonignores all input lines until a line contains thea

which is the text of the but-

ton:

$ button ignore a a

Only oneais input, the other one is printed by theLineOut.

LineOut

and

Button

were implemented mostly to check the computing chip

before it is connected to a graphical interface. The computing chipCalccan be as

complicated as we wish, but for starters we stick with a very primitive design:

digits are assembled into a value in the display; the arithmetic operators are exe-

cuted as soon as possible without precedence;

=

produces a total;

C

clears the

current value; andQterminates the program by callingexit(0);.

This algorithm can be executed by a finite state machine. A practical approach

uses a variablestateas an index selecting one of two values, and a variableopto

remember the current operator which will be applied after the next value is com-

plete:

%prot

typedef int values[2]; // left and right operand stack % IcClass Calc: Ic {

values value; // left and right operand

int op; // operator

int state; // FSM state %}

178

___________________________________________________________________________

14 Forwarding Messages — A GUI Calculator

The following table summarizes the algorithm that has to be coded:

input

state value[] op super_gate()

digit any v[any] *= 10

v[any] += digit value[any] 0 → 1 v[1] = 0 input

1 v[0] op= v[1] input value[0] + - * / v[1] = 0 0 v[0] = 0 1 → 0 v[0] op= v[1] value[0] = v[0] = 0

C any v[any] = 0 value[any]

And it really works:

$ run 12 + 34 * 56 = Q 1 12 3 34 46 5 56 2576

Summary

The

Icclasses are very simple to implement. A trivial

LineOutand an input loop,

which reads from the keyboard, enable us to checkCalcbefore it is inserted into a

complicated interface.

Calc

communicates with its input buttons and output display by callinggate().

This is coupled loosely enough so that

Calc

can send fewer (or even more) mes-

sages to the display than it receives itself.

Calc

operates very strictly bottom-up, i.e., it reacts to every input passed in

throughCalc_gate(). Unfortunately, this rules out the recursive descent technique

introduced in the third chapter, or other syntax-driven mechanisms such as

yacc

grammars, but this is a characteristic of the message-based design. Recursive des-

cent and similar mechanisms start from the main program down, and they decide

when they want to look at input. In contradistinction, message-driven applications

use the main program as a loop to gather events, and the objects must react to

these events as they are sent in.

If we insist on a top-down approach forCalc, we must give it its own thread of

control, i.e., it must be a coroutine, a thread under Mach and similar systems, or

even another process under

UNIX

, and the message paradigm must be subverted by

process communication.