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