Action Walkthrough, part 1

This post series explores an Action in detail with emphasis on the Instance objects, especially the dynamic (temporary) ones. Posts are broken up mainly for size considerations.

The description that follows assumes this BOOL Action:

@@hypot
>>  *float a
>>  *float b
<<  *float h
.   set:h @sqrt add: mul:a a mul:b b

The Action takes two *float input parameters, a and b, and has one *float output (named h in the Action and returned on the stack). All three are Instance objects declared in the Action header. There are also five hidden dynamic Instance objects created by the four Messages and one Actor.

The Action implements the expression:  square-root(square(a) + square(b))

Two observations to begin: Firstly, the expression could be moved from the execution list to an initialization list on the output parameter:

<<  *float h = @sqrt add: mul:a a mul:b b

That would make the execution list unnecessary. The output parameter ends the Action’s clause list, so an empty execution list isn’t even required to prevent the next Action from binding to this one. The reason for using one here is to separate the parts of the Action to make the description more clear.

Secondly, the example implements the square root operation with an Action (rather than a Message) mainly to provide an example of an Actor, but also to make the semi-colon “null object” unnecessary as it would be for a sqrt: Message object. If the entire expression used Messages, it would look like this:

… sqrt: add: mul:a a mul:b b ;

Implementing any singleton Message — especially one appearing frequently in code — as an Action is suggested BOOL idiom. (It would be especially cool if doing so could tie directly to the underlying Model Action!)

The @hypot Action, compiled to Python, looks like this:

[0000] (INST, 'float', 0, NULL, ('name','a'))
[0001] (INST, 'float', 1, NULL, ('name','b'))
[0002] (INST, 'float', 2, NULL, ('name','c'))
[0003] (TEMP, 'any', 3)
[0004] (MSG, 'mul', @0000, [@0000], @0003)
[0005] (TEMP, 'any', 4)
[0006] (MSG, 'mul', @0001, [@0001], @0005)
[0007] (TEMP, 'any', 5)
[0008] (MSG, 'add', @0004, [@0006], @0007)
[0009] (TEMP, 'float', 6)
[000A] (TOR, 'sqrt', [('sqrt',@0008)], @0009)
[000B] (TEMP, 'any', 7)
[000C] (MSG, 'set', @0002, [@000A], @000B)
[000D] (ACT, 'hypot', [('hypot',2,@000C)], [@0000,@0001,@0002], [@0002])

The @0001 nomenclature is shorthand for a local address. Normally an address is a tuple, such as ('foo',offset), where offset is in the Action ‘foo‘. A local address refers to the immediate codeblock (regardless of name) and looks like ('@', offset).

An invoking Actor might look like this:

*float x
set:x @hypot 3 4

Which would sets Instance object x (of type *float) to the value five (as a float value). This code snippet compiles to something like:

[0000] (INST, 'float', dx, null)
[0001] (TEMP, 'float', dx+1)
[0002] (TOR, 'hypot', [('hypot',constant(3),constant(4))], @0001)
[0003] (TEMP, 'any', dx+2)
[0004] (MSG, 'set', @0000, [@0002], @0003)

The Actor finds and invokes its Action, and when it does it passes its parameter list:

[ ('hypot', constant(3), constant(4)) ]

The parameters are always addresses of objects in the code space. Specifically, any temporary object passed as a parameter requires a code address. What’s passed in the example above are the addresses of two constant Instance objects.

The parameter list is matched to the Action’s clause list:

[ ('hypot', 2, exec-list) ]

The clause list specifies a single clause, named hypot, which takes two input objects. The third member of a clause indicates an execution list to perform.

When an Actor invokes an Action, the Action creates a Call Frame (CF) with “slots” for objects. The CF is analogous to a local stack frame. The Instance Objects in the code, both declared and temporary, all have an assigned slot in the CF.

The CF slots contain the actual object instance or — for input objects — the address of some object. Input objects are always addresses, because the Actor can only pass addresses. This is different from Messages, which take objects from the stack, and those objects can be objects or addresses.

Conversely, output objects are usually newly created objects (typically Instance objects), but can be the address of any existing object (especially when a Model Action returns the self object). Temporary objects are essentially Action outputs, so they, too, are usually new Instance objects (but can be addresses).

BOOL addresses are passed on the stack as special objects:

(ADDR, action-name, address)

Instance objects appear as fully expressed objects:

(OBJ, 'float', dkey)

Note that composite Instance objects appear as composites:

(OBJ, 'point', [(OBJ, 'int', dkey), (OBJ, 'int', dkey)] )

The dkey is the handle to the actual data value maintained by the Model in its data pool. There is some consideration of allowing the dkey to be the actual value in the case of some native objects (such as *int, *long, *float, *double and *string). This immediately makes those objects less object-y by making them pass-by-value.

The CF for the @hypot Action, after initialization, would look like this:

[0] (ADDR, 'system', 0005)  --1st input
[1] (ADDR, 'system', 0006)  --2nd input
[2] (OBJ, 'float', dkey-1)  --output
[3] empty
[4] ...

There are eight slots, but only three from declared Instance objects, so the last five slots are empty when the Action begins. (An assumption for this illustration is that the address of two constants was passed to the @hypot Action. All constants are defined in the @system Action.)

When the Action completes, the Call Frame looks more like this:

[0] (ADDR, 'system', 0005)
[1] (ADDR, 'system', 0006)
[2] (OBJ, 'float', dkey-1)
[3] (OBJ, 'float', dkey-2) --result of mul:
[4] (OBJ, 'float', dkey-3) --result of mul:
[5] (OBJ, 'float', dkey-4) --result of add:
[6] (OBJ, 'float', dkey-5) --result of @sqrt
[7] (OBJ, 'float', dkey-6) --result of set:

The temporary objects are released when the Action ends and destroys the CF. The presumption is that temporary objects, other than any explicitly passed back as an output, can be cleaned up on Action exit. (Again, the CF is similar to a standard stack frame.)

That’s the basic set up. Next, a detailed look at the Action invoke protocol.

Advertisements
%d bloggers like this: