I’ve been working on Actions recently, and have maybe finalized a few things that should allow me to move forward on an implementation design. It’s become very clear that Actions are central to that design.
In addition to their role as BOOL “sub-routines” and Model “methods” they also implement language flow control (via
@while and others). As such they end up being part of the design for most other areas of BOOL.
Given that Actions have been one of the more poorly defined areas of the language, it’s high time to figure out exactly how Actions behave!
One challenge involves passing objects into Actions and receiving values back from them. (BOOL — for now! — allows a Generic Action to return multiple objects. Model Actions can only return a single object, and this prohibition may be extended to all Actions.) A related challenge involves temporary variables created by the Action. In both cases, there is no local stack to use for variables!
There is a run-time Parameter Stack, which is a special object that manages instances of Parameter Stack Frames. A Frame encapsulates the references to passed variables. It supports the idea that an Action may not reference all passed objects, but more importantly the idea that an Action cannot reference an object it wasn’t passed (by reading beyond its stack frame).
It turns out that Actions have their own Address and Data spaces, which share some aspects in common with the Parameter Stack Frames. The two together provide a means that seems to resolve the design issues that have been a nag.
Consider the following (very trivial) [Generic] Action:
@@foo >> *int a >> *int b << *int c . set:c add:a b
This Action, named
@foo, takes two parameters (both required to be, or act like,
*int objects) respectively named
b. It also returns an
*int object (named
BOOL always passes by reference, so
b are references to objects “owned” by the caller (or one of its callers). However the
c object is created locally in this Action and then passed back to the caller (who may ignore it, use and discard it, or pass it back to its caller).
This run-time creation of new objects is a key challenge due to BOOL’s unusual execution model. The bottom line is that new objects created for return are created in the caller’s Address/Data space, whereas new objects created for local use are created in the local space (which is discarded when the Action exits).
This ends up meaning that objects declared in the signature are different than objects declared in the Action body. Specifically, declarations for passed in objects actually create reference objects, and declarations for returned objects imply an initialization step. In fact, to support default values, so do passed reference objects.
@@foo >> *int a = 0 >> *int b = 0 << *int c = set:c add:a b
This is the same code fragment as above, but re-written to include initial default values for the passed parameters. It also puts the trivial code into the return object’s initialize list rather than in the Action’s execute list.
Since none of these declarations are in the Action body, they are not in any Action execute list and require explicit initialization when the Action is invoked. This means Actions require an
initlist (oddly, they popped up in an earlier design but were discarded).
As a reference point, consider this Action signature:
@@foo >> *list list << list
This Action takes a (reference to a) list and returns (a reference to) that list. Presumably the Action body does something with the list.
This is very similar to a typical case with Model Actions: a “method” that returns a reference to the “self” (or “this”) object:
**point@@foo:: >> *int x >> *int y << !
This Model Action is bound to the
*point Model (implementing a
foo: Message for
*point objects). It takes two
y, and returns (a reference to) itself.
In both cases, the Action returns something it was passed, so nothing new is created in terms of returned object. [Or is it?! What if the Action adds new objects to the list? Somehow it needs to know to create them in the caller’s space, not its own local space! Action implementation design is clearly not out of the woods just yet! Drat!!]
Another possibility is something like this:
@@foo >> *int a >> *int b << add: a b
This Action doesn’t explicitly declare a return object, but the
add: Message does create one in this caller’s space.
This also presents an unresolved issue similar to the list problem mentioned above. It’s starting to look like some mechanism is necessary to move objects from a called space into a calling space. Back to the design board!
For now, it all means that Actions require three internal lists:
initlist: Object declarations to initialize
exitlist: Expression(s) to query for return object(s)
execlist: Statements to execute as the Action