BOOL Action objects are the “callable code objects” of BOOL. In other languages they are called “sub-routines” or “functions” or “procedures” or what-have-you.
A BOOL Action has a header, inputs and outputs, and one or more execution lists. There are generic Actions and Model Actions. The latter are bound to a Model and implement the Message behaviors of that Model.
@@hypot >> *float a >> *float b << *float h = set:h sqrt: add: mul:a a mul:b b ;
The above defines an Action, named
@hypot, which takes two input parameters (which must be of type
*float). The Action returns a
There may be any number (including zero) of input parameters. Currently, there may be only zero or one output parameter (Actions with no declared output return a Null object).
Actions can have multiple execution lists, when there are secondary clauses. A named execution list can (and should) exist for each sub-clause.
@@if >> *(*) if-expr >> *() if-list @@elif /optional /multi >> *(*) elif-expr >> *() elif-list @@else /optional >> *() else-list << *bool flag =[if] set:flag true:if-expr; . = flag . . if-list =[elif] not:flag; . set:flag true:elif-expr; . = flag . . elif-list =[else] not:flag; . else-list
The above illustrates how the
@if-@elif-@else Action might be defined. The secondary clauses are both optional. The
@elif secondary clause can appear multiple times. An Action only executes the secondary clause sections if parameters for that section are provided.
@if flag . statement-1 . statement-2
In the above, neither secondary clause is used, so the Action skips those sections.
The basic algorithm for an Action is:
Action::Invoke (action, inputs): cf = create-call-frame() for obj in action.initlist: SendMessage("X:", obj) curr-param = inputs.first-param() for clause in action.clauses: if curr-param is None: break // no more input objects while curr-param.name == clause.name: copy-params-to-call-frame(action, cf, curr-param) SendMessage("X:", clause.execlist) curr-param = inputs.next-param() for obj in action.exitlist: SendMessage("Q:", obj) destroy-call-frame(cf)
Keep in mind that, for now, the exitlist can only have zero or one object.
The kicker has to do with the copy-params-to-call-frame() function, which copies parameters from the inputs to the Call Frame (CF). The kicker part involves keeping track of which CF slots receive input parameters.
The first CF slots are assigned to the inputs, in declaration order. The next CF slots are assigned to the outputs, again in declaration order. Thereafter, CF slots are assigned sequentially to objects declared in Action execution lists.
(ACT, name, cfsize, ins, outs, clauses, codeblock, namtab, props)
The cfsize is the size of the Action’s Call Frame.
The ins and outs lists consist of addresses of input and output objects.
The clauses list consists of clause objects, which each have the format:
(clause-name, parameter-count, execution-list)
- clause-name: binds input parameters to their Action clause
- parameter-count: how many parameters the clause takes
- execution-list: address of the clause code list
The codeblock list consists of a list of compiled BOOL objects comprising the code of the Action. The addresses in the ins and outs lists, as well as the addresses in the clauses list, refer to offsets in the codeblock.
The namtab dictionary contains the
name=address pairs of public objects in the Action codeblock. (Note: Mainly used in debugging. Most Actions don’t make internal objects public. The dictionary can be empty, and namtab is allowed to be null.)
The props dictionary contains
name=property pairs describing or defining the Action. (All properties are optional. The dictionary may be empty, and props is allowed to be null.)