Python Actions

Picking up from the previous post, I want to get into the Python binding for Actions in more detail. I’ve got several examples to help demonstrate various aspects of what the parts of the Action object mean. The design appears solid enough to make it official and move on to the next stage.

Speaking of which: huge milestone in the last few days! The bottom line is that, for the first time, I can generate BOOL objects from a higher-level language. Essentially, I wrote a kind of “macro-assembler” that reads a fairly simplistic language and emits (Python) BOOL objects. It’s a major step in bootstrapping the run-time environment objects necessary as well as providing examples for working on the run-time engine!

It rates its own article soon. For now, Actions ala Python:

Here again is the definition of a Python Action (Level 3):

act = (ACT, name, initlist, exitlist, argslist, codelist, nametable)

Recall that all objects here are Python tuples, the first member of which determines the meta-type of the object. There are just under a dozen BOOL meta-types:

  • LST: List
  • MSG: Message
  • OBJ: Object
  • REF: Reference
  • ACT: Action
  • TOR: Actor
  • MOD: Model
  • SYS: System
  • DEV: Device
  • EXT: External
  • USR: User

The last four are not fully specified yet and will be discussed in the future when they are. The first seven were all mentioned last post. The Level 3 specification does include symbolic definitions for the meta-types, and those definitions are used here.

The Action Name is itself a three-member tuple of strings. The first member is the key name of the Action. For many Actions, the key name is the whole name, but Actions with multiple clauses have a separate key name, the first clause name. This does mean that multiple-clause Actions cannot share a first name!

The second and third members of the Action Name tuple are the simple- and full-signature strings. The simple-signature allows a parser to quickly determine how many objects an Action takes (and returns) as parameters. The full-signature contains type information, which is useful only at run-time (as type is often not known until then).[1]

At this point, it’s probably easier to explain starting from the back. The Name Table is name/address list (a Python dict) of anything declared by name in the Action. This table is used during compile to locate objects within the Action scope and isn’t really necessary at run-time. It has some value for debugging, hence its inclusion.

The Code List is just an array (Python list) of the objects in the Action. An address of an object within the Action is just an offset into this array. (For example, the addresses in the Name Table are all indexes into this array.)

The Args List may be the hardest to explain. The name doesn’t help, because the design has expanded it beyond its original use. At first, the Args List just listed the clause names and how many parameters each clause took. There was also an Exec List, also with clause names, that indexed the list of objects to execute in the course of the Action.

The two lists ended up being so similar that it made sense to combine them. (In fact, originally the Args List was more complicated — it included the address of the parameters rather than just a count.)

The Args List contains one member for each clause. That member is a three-member tuple: the name of the clause, the count of parameters, the address of the object list that comprises the clause.

Finally the  Init List and Exit List. Both are just arrays (Python lists) containing addresses of objects in the Action’s Code List.

The Init List enumerates both input parameter Reference objects and output return objects. Crucially, the order of parameters here, plus the clause parameter counts in the Args List, determines which parameters link with which clauses.

The Exit List enumerates the return objects. BOOL Actions can return multiple objects, although generally they only return one to keep things sane.

How the BOOL RTE uses these Action fields is a future topic (in part because I haven’t finished figuring it out, yet). For now, here are some examples of BOOL Actions and how their Python tuples look.

A BOOL Action that does nothing, takes no parameters, returns no values, and has no statements, would look like this (assuming you called it “dummy“):


Note that you need to be sure the object that follows is not a List, since any trailing List automatically binds to the Action definition (same as how a trailing List binds to an Object definition). This dummy Action compiles to this Level 3 Python tuple:

A_dummy = ('dummy', '', '')
act = (ACT, A_dummy, [], [], [('dummy',0,None)], [], {})

The Init List is empty, indicating no inputs or outputs (the empty Exit List is therefore expected). The Args List contains one clause, and that clause takes no parameters and has no list of objects to execute. The Action has no objects of any kind, so the Code List is empty, and so is the Name Table.

Here’s an Action that has no inputs, but does return a value:

@@ forty-two
<<  *int answer = 42

All of the “action” in this Action involves the initializer List for the exit parameter, answer; As with the first example, there is no Action clause List. But that initializer List is still an object, so the Code List won’t be empty this time:

A_fortytwo = ('forty-two', '<*', '<*int')
L1 = [(addr,1)]
L2 = [(addr,1)]
L3 = [('forty-two',0,None)]
L4 = [
    (LST, 1, (system,C_int_42))
,   (OBJ, 'int', 0, (addr,0))
NT = {'answer':(addr,1)}
act = (ACT, A_fortytwo, L1, L2, L3, L4, NT)

The Action contains two objects: the LST object that’s the initializer for the second object, and the return object itself. It’s also worth pointing out the constant’s symbolic address (C_int_42). In reality, the address would be the index of the constant in the system namespace.

Next up, an Action that takes and returns a *float parameter. You can see it’s just a wrapper for the sin: (sine) Message, however a @sin Action automatically includes a type-check, whereas the sin: Message can be sent to any object (potentially generating a run-time exception in an object that doesn’t do math, let alone trigonometry):

>>  *float angle
<<  *float answer = sin:angle;

This Action also has no body, but it does have more objects within. And we’ve added an input parameter.

A_sin = ('sin', '*<*', '*float<*float')
L1 = [(addr,0), (addr,3)]
L2 = [(addr,3)]
L3 = [('sin',1,None)]
L4 = [
    (REF, 'float', 0)
,   (MSG, 'sin', (addr,0), [])
,   (LST, 1, (addr,1))
,   (OBJ, 'float', 1, (addr,2))
NT = {'angle':(addr,0), 'answer':(addr,3)}
act = (ACT, A_sin, L1, L2, L3, L4, NT)

(Note how the signature gets more complex with each example!)

The final example is a simple @add Action that, as with the previous Action, really just wraps an identical Message. Again, there is the automatic type-checking factor, but the truth is it just provides a good example of a dead simple single-clause Action:

>>  *int a
>>  *int b
<<  *int c
=    set:c add:a b;;

The big addition this time is the Action body, an anonymous (but explicit) List following the Action signature definition. The List could have been preceded by an [add] Label, but it is unnecessary in a single-clause Action (plus it increases visual clutter). The anonymous List following the Action always takes the Action’s (first) name.

In this case, the return object isn’t initialized explicitly. Its value is set by the code below in the Action body. The Python objects look like this:

A_add = ('add', '**<*', '*int*int<*int')
L1 = [(addr,0), (addr,1), (addr,2)]
L2 = [(addr,2)]
L3 = [('add',2,(addr,5))]
L4 = [
    (REF, 'int', 0)
,   (REF, 'int', 1)
,   (OBJ, 'int', 2, None)
,   (MSG, 'add', (addr,0), [(addr,1)])
,   (MSG, 'set', (addr,2), [(addr,3)])
,   (LST, 1, (addr,4))
NT = {'a':(addr,0), 'b':(addr,1), 'c':(addr,2)}
act = (ACT, A_add, L1, L2, L3, L4, NT)

Note how the 'add' clause specifies two input parameters and the address of a List to execute.

And that’s all for this time!

[1] Actions can declare *any or *one or *list as generic parameters (*one and *list require inputs of those basic types). But Actions that declare parameters of a specific type generate a run-time exception if passed an incorrect parameter. (Likewise, passing a list to a parameter declared as *one generates an exception, and so does passing a single object to one declared *list.)

%d bloggers like this: