This is part two of a series commemorating a computer language I started designing somewhere around 1990. After 30 years of tinkering I’ve finally accepted that it’s just not meant to be, and I’m letting it go. These posts are part of that letting go process.
Last time I introduced BOOL, said a bit about about what motivated it, and started laying out what made it a language only a parent could love. Later I’ll explain why things didn’t work out, but for now I’d like to tell you about what BOOL was supposed to be:
A glorious deliberate useless Frankenstein’s Monster (insert mad laughter).
BOOL was much more an art project than a serious tool. A ship in a bottle rather than anything that actually floated.
I’ve shown you the simplest “Hello, World!” program possible in BOOL. Here’s a slightly more demonstrative version:
@@main >> *list argv >> *list env << *int retval =0 . *string message ="Hello, World!" . print:message; . set:retval 1
This example shows how BOOL passes parameters to and from Actions. After the Action name, there can be zero or more input parameters, each a line starting with >> (in BOOL, two punctuation symbols indicate a definition, a single one a reference).
In this case there are two input parameters. Both are required to be *list objects. If a caller passes something that isn’t a *list, BOOL barfs (throws an error). The two inputs are named argv and env.
Following the inputs there can be zero or one output (return) parameters.
[I explored the possibility of multiple return parameters, which are obviously syntactically provided for, but decided to leave that for a later version. Multiple return parameters is a heavy burden to put on a programmer. It doubles the parameter-passing issue of making sure function calls pass what the function expects. With multiple return parameters, the programmer also has to worry about matching what the function returns.]
In this case, the code defines a single return value, an *int object (that is, an integer value), named retval. It also defines a default value for it: zero.
After the parameters comes a list of code statements. The “dotted” lines are how BOOL creates block scope. Similar to Python, there are no “begin/end” characters or keywords. Different from Python, BOOL uses a visible character as part of the indenting (in fact the spaces are optional, it’s the dots that matter).
The first line creates a *string object and sets its default value to “Hello, World!” (Note that, when provided with default values, objects can restore these values if sent a reset: message object.)
The second line sends a print: message to the *string object, which causes it to print itself.
The third line sets retval to 1, which is what @main then returns (because there are no more statements to do).
The use of a required “main” function is common in many languages.
A program in such languages usually has a wrapper around it, or an environment supporting it, that, when the program is launched, does whatever common startup tasks are required by the system.
Then it calls the programmer’s “main” function, which is the entry point into the programmer’s code. When the program finishes, control returns to main, and main exits, usually returning some value to the wrapper or support environment.
That returned value usually makes its way back to the system which can treat it as a general error code. Canonically, returning zero means “a-okay!” whereas returning a non-zero value indicates an error of some kind.
Taking it one step further, many programmers (such as yours truly) return positive numbers as a successful completion status and negative numbers as error codes. This allows a bit more control when running the program.
BOOL follows this general architecture. True to the “you could do this in BOOL itself” design goal, much of the wrapper that calls @main could be written in BOOL (but is usually implemented in native code).
Specifically, BOOL first launches an Action called @system that initializes all the required library objects of BOOL (such as the @if Action and *int Model). It’s also responsible for any bindings to system I/O streams and importing system libraries.
The @system Action’s last step is to call the @global Action. This Action contains and initializes all the global objects defined by the programmer in the program. For instance, in BOOL, if you use the literal number 3 in your code, BOOL creates a global *int object with a read-only value of three (in other words, a constant).
Likewise, all global Actions and Models the programmer defines have their actual objects located in the @global Action. All names defined here go in a global dictionary for fast lookup.
Note that this means @system is a fixed Action dependent only on the system, whereas @global also depends on the program (hence there being two internal system Actions).
The last thing @global does is call @main, so the programmer’s code can take over.
The point of all this explanation to mention that @global does pass @main two parameters, a *list of input or command line parameters and a *list of environment variables. And it does expect to get an *int object in return.
As mentioned, the value of that *int object is passed back to the operating system.
In many regards, this is all there is to it, at least in terms of using BOOL as a procedural language.
Actually writing code requires knowing the standard Models (such as *list or *string) and standard Actions (such as @if and @while). I won’t list them, but they include all the basics along with a few exotics. (These are the ones that are all defined in @system.)
Perhaps of note are the anonymous Models, *one, *list, and *any.
These cannot be used to create new data objects, but they can be used to generically constrain input parameters. I’ve already mentioned that *list constrains the parameter to being a list type object. The *one Model requires it not be a list object. Passing the wrong type cause an error.
As you might guess, the *any Model accepts any type of object.
Note that when one of these binds to an input parameter, the Actions invoked by Messages of course are passed to the actual Model owning the object passed.
Here’s a bit of BOOL code to show you something more complex.
@@Shaker-Sort >> *list List << List . *int ix1 =0 . *int ix2 =1 . *int max =count:List; . *bool flag =TRUE . @do . . set:flag TRUE . . @while elt:ix2 max . . = @if gt:List/ix1 List/ix2 . . . = swap:List ix1,ix2 . . . . set:flag FALSE . . . incr: ix1,ix2; . . @if flag @return . . @while egt: ix1 1 . . = @if gt:List/ix1 List/ix2 . . . = swap:List ix1,ix2 . . . . set:flag FALSE . . . decr: ix1,ix2; . @until flag
This Action takes a *list object. It returns that same object after sorting it.
Of note, the use of “dotted lists” as BOOL’s form of block scope.
Of greater note, in BOOL the equals sign (=) does not mean either equality or assignment. To test whether two objects are equal, send an eq: message to one with the other as the parameter:
@if eq:A B . <statements>
To assign a value to an object, send a set: message:
set:X add:A B
That sets X to whatever you get when you add A and B.
In BOOL, the equals sign indicates a list. In the code above, it often replaces a dot to make it clear a given dotted list begins at that point (this can be necessary if two lists abut each other).
Generally, adding a new dot level implicitly begins a list. For instance the dot immediately below the @do Actor is the second dot on that line, and since it’s the first time the dot level jumps (from 1 to 2), it implicitly begins a list. An equals sign could be used to make it explicit.
The equals sign that follows a Model instance also begins a list:
*int foo =42
The presence of such a list tells BOOL a default value is provided. Some objects do require a list of default values (arrays, for instance), but single objects just take one default value.
BTW: defining an Action (two @@) results in an Action object (usually put in the @global Action). Using an Action (one @) creates an Actor object.
One last point (and then you’re ready to write BOOL code 😀 ).
When sending a Message object, there is always a target that receives it. There is also always a parameter object, but some messages don’t imply additional values. For instance, the square root function only takes one parameter.
For unary functions, the semi-colon provides a “null” object since the syntax always requires the parameter object. The eq: and add: and set: messages shown above are all binary functions that require the parameter.
But, for example, to use the square root Message (sqrt:) a semi-colon is required:
(When it comes to colons and semi-colons, spaces are optional.)
That’s more than enough for this time.
If it isn’t obvious, BOOL is a bit of an exercise in complexity for the sheer love of complexity. (For those with engineering minds, it’s almost sexual.)
BOOL was a way for me to ground some of that capriciousness and keep it the hell away from my professional code and designs.
Stay boolean, my friends!