ONBU
Zach Flynn
2021-11-18 Thu 08:42

ONBU

Table of Contents

1 Introduction

ONBU is a useful environment and language for creating personal programs. "Personal programs" are programs primarily designed for an audience of one. For example, suppose your job requires you to keep track of the hours you work for various clients. The Personal Program approach to solve this problem is to write a simple program that keeps track of your hours. ONBU is a programming language and environment that makes it easy to write these kinds of programs.

ONBU is an environment and programming language designed to make it easy to create personal programs to accomplish your computing. It is a language for manipulating values in a data structure called a "registry". A registry stores values at named data locations called "registers". ONBU code always operates inside a registry. Code modifies the contents of the registry and the registry the code operates inside defines the environment in which the code operates, i.e. the code's "scope". Code can be easily executed inside different environments making it possible to use code others have written in ways they might not expect, a powerful feature for the code user. For example, another programmer might have created a set of ONBU instructions that use the add operator to add numbers. You might want to use the code but wherever there is an add you want to use sub (subtraction). ONBU allows you to make such modifications without the author of the code ever deciding to allow that option.

While ONBU can be run in scripting mode, it is primarily intended to be used interactively. ONBU programs are written to provide new commands in the ONBU shell. For example, I use a program to keep track of the number of hours I work for each client at my day job. I created commands for switching which client I am currently working for and a command to stop working on that client. The commands automatically record how many hours I've worked for each client in a registry. The code allows me to save this registry to disk and output the required information for submitting my hours. Instead of the program having its own method of interacting with it, it simply uses the ONBU environment as its method of interaction. The program only needs code to do the useful things, not code to enable interaction.

ONBU's environment is useful for writing such programs because the language has a simple syntax that is similar to what you would type in an ordinary shell enabling the language itself to function as a command language. For example, the following would be valid ONBU code snippets:

... report /project "The Big One" /from "2020-01-01" /to "2020-12-31" /output "annual_report_2020.csv" .
... hours /day 14 /month 6 /year 2021 .

Where hours and report are ONBU commands that have been written by the user.

ONBU is short by one letter for ONBUS, which is where I wrote the language: on the bus to work.

2 The Language

2.1 Statements and syntax

A single line of ONBU code is called a statement. The first element of the statement is one of ONBU's operators and the other elements are arguments to the operator. Statements, like sentences, end with periods. For example,

... set /hello "Hello, world!" .

set is the ONBU operation to set a register to a certain value. The above code sets the register /hello to the String value "Hello, world!. Names that have a forward slash preceeding them are registers, like /hello above. Strings are characters enclosed in double quotation marks. The following code demonstrates some of the other data types in ONBU:

... set /days-in-week 7 .
... set /penny 0.01 .
... set /facts True .
... set /lies False .
... set /add-one ( add t 1 . ) .

An Integer is a number without a decimal part, a Real is a number with a decimal part, a Boolean value is either True or False, an Instruction is a collection of one or more statements quoted as code (created by enclosing code with open parenthesis, "(" and ")"). Instructions can be executed later inside registries perhaps different than the registries in which they were initially defined.

To use the value located in a registry, simply type the name of the register without the preceeding forward slash. For example,

... print days-in-week .
7

All code elements must be separated by whitespace in ONBU including the period that ends the statement and parenthesis. So it would not be valid to write,

' All are WRONG:
print days-in-week.
set /add-one (add t 1 .) .

There must be whitespace before the period. There also must be whitespace after and before parenthesis.

There are four parenthesis types in ONBU: open parentheis ("()"), closed parenthesis ("[]"), curly parenthesis ("{}"), and angle brackets ("<>"). The first three all contain groups of statements, but they differ in when the statements they contain are executed. The angle brackets are used for defining new operations from instructions.

2.1.1 Instructions

Open parenthesis store the code itself as an Instruction object to be executed later using the operation call. For example,

... set /add-one ( add t 1 . ) .
... call add-one /t 10 .
ans = 11

call takes an instruction and then assigns each of the registers that you specify to different values. You do not need to declare what arguments a function takes and you can specify any number of registers to set for any Instruction. For example,

... set /add-one ( add t 1 . ) .
... call add-one /t 10 .
ans = 11
... call add-one /t 10 /add sub .
ans = 9

Any object in an instruction can be replaced by any other (except for a literal value like 11 or "hello" ) when the Instruction is called. The person who wrote the code does not need to do anything to allow this. You can redefine any non-literal in the code by adding /name value in the call to the Instruction.

Because there is no ambiguity, you do not actually need to use the call operation directly. ONBU will insert it for you if the first argument is an instruction. So the following would work as well,

... add-one /t 10 .
ans = 11

Instruction objects can also be used in the while looping operation and in several other operations.

2.1.2 Greedy substatements and the /ans register

Closed parenthesis ("[]") execute the statements inside of them immediately and evaluate to whatever the code sets the /ans register to. The purpose of these statements is to avoid needing to define temporary registers for intermediate calculations. For example,

... add [ sub 5 3 . ] [ mul 2 3 . ] .
ans = 8

You may have noticed the ans = ... after entering some commands. This tells you the current value of the /ans register which is special in ONBU. Operations often write to the /ans register in the registry from which they are called. It is the way to "return" values after calling an operation or instruction. You can set any value to the /ans register in the current registry by using the answer operation.

... answer 10 .
ans = 10

The /ans register is special because, after an Instruction finishes running in its temporary registry, whatever the value at the /ans register is in that registry is moved to the /ans register in the registry that called the instruction. For example,

... add 10 12 .
ans = 22
... set /add-one ( add t 1 . ) .
... add-one /t 10 .
ans = 11
... print ans .
11

The other reason that the /ans register is special is, as mentioned, closed parenthesis evaluate to the value the statements inside them set the /ans register to. Because sub 5 3 . sets the /ans register to 2 and mul 2 3 . sets the /ans register to 6, add [ sub 5 3 . ] [ mul 2 3 . ] . sets the /ans register to 8.

2.1.3 Lazy substatements (Expressions)

Curly parenthesis ("{}") also enclose statements, but those statements are only evaluated if we try to use the value of that expression which is, like for the closed parenthesis, the value the statements set the /ans register to. This form of evaluation is often called "lazy evaluation". ONBU calls this data type an "Expression". We can think of closed parenthesis as marking statements we want to "greedily evaluate" and curly parenthesis as marking statements to "lazily evaluate". Lazy evaluation is useful for the if operation and other, similar instructions. For example,

 ... set /gt-4
 (
   set /str-t [ to-string t . ] . 
   if [ gt t 4 . ]
      { print [ combine str-t " is greater than 4.\n" . ] . }
      { print [ combine str-t " is less than or equal to 4.\n" . ] . } .
 ) .
... gt-4 /t 10 .
10 is greater than 4.
... gt-4 /t 3 .
3 is less than or equal to 4.

It works this way because the if operation returns the second value if the first value is True and the third value if the first value is False. So it tries to access the second value when t is 10, executing the statements in the second argument, and similarly for the third argument when t is 3.

If we had instead used closed parenthesis, both statements would execute everytime regardless of the the truth of the first argument to if. If we used open parenthesis, an Instruction object would be returned instead of actually doing the printing (the instruction object could be called to actually do the print later).

Unlike instructions, expressions are evaluated in the registry calling them instead of a temporary registry inside the calling registry. So, for example they can move registers in the calling registry:

... set /test ( set /x 2 . sit y . print z . ) .
... test /y { move /x /z . } .
2

Note: sit is an operation that does nothing but evaluate any substatements in its arguments.

2.1.4 Convert an instruction to an operation

Angle brackets (<>) transform an instruction object into an operation object. Instructions are code blocks formed using open parenthesis () that operate within whatever registry the caller of the instruction defines. Operations execute with a fixed set of positional arguments determining the registry the code executes in. All of the built-in features of the language are operations, but most programmer-defined code is an instruction. Occasionally, it is useful to transform instructions into operations. This transformation is a lot like a procedure definition in other languages. Angle brackets or the op operation provide two alternative ways of doing this. For example:

... set /fraction ( div t1 [ add t1 t2 . ] . ) .
... fraction /t1 1 /t2 3.0 .
ans = 0.25
... [ op fraction /t1 /t2 . ] 1 3.0 .
ans = 0.25
... < fraction /t1 /t2 > 1 3.0 .
ans = 0.25
... set /op-fraction < fraction /t1 /t2 > .
... op-fraction 1 3.0 .
ans = 0.25
... < fraction /t2 /t1 > 1 3.0 .
ans = 0.75

Note that there is no period used to end an operation definition in the angle brackets. The brackets call the instruction fraction and sets the register /t1 to the value of the first argument and the register /t2 to the second argument. The order would be switched if we had instead used: < fraction /t2 /t1 >.

2.1.5 Comments

The last syntax element is the comment. To write a comment in ONBU, use a single quote (') as the first character in a statement. You can think about this quote as a special operator. Comments end with a new line. The comment operator, like other syntax elements, needs whitespace after it, for example:

... ' This is a correct comment
... 'This is an incorrect comment, 'This is interpreted as a reference to data in the Register /'This

That is really it as far as syntax is concerned. Periods terminate statements, parenthesis enclose statements controlling when they are executed, and registers start with a forward slash and their values are obtained by omitting the slash. We now also know how to define our own instructions and how to call them and how to prepare statements for greedy or lazy evaluation.

2.1.6 Ending Last Statement in a Substatement

In a substatement, the last statement does not need to end with a period because there is no ambiguity. For example,

... add 1 [ mul 2 3 ] .

is a valid statement. If there are multiple statements, it is usually good practice to end all with a period, but the period can be omitted from the last one. For example,

... add 1 [ add 1 1 . mul ans 3 ] .

is valid.

2.2 Registries

In ONBU, code is executed inside registries. Registries are both data structures and define what registers (variables) are in scope. Because registries are full fledged data structures, you can switch scope easily or pass a scope as an argument. When you start the interpreter, you are already inside the default, top-level registry. To create a new one, use the registry operation,

... registry /hello "Hello, world!" /x 10 .

The registry operation puts a new registry in the /ans register. This registry has a /hello register bound to the value "Hello, world!" and the /x register bound to the value 10. Usually, you will want to bind the value to another register besides /ans which will be overwritten by subsequent statements. You can do either of:

... registry /hello "Hello, world!" /x 10 .
... move /ans /my-registry .
... ' Or:
... set /my-registry [ registry /hello "Hello, world!" /x 10 ] .

To access values in a registry, you can either use the get operation or the "colon notation":

... get /hello my-registry .
ans = Hello, world!
... print my-registry:/hello
Hello, world!
... print hello .
Error: Value at `/hello` not found.

From the last line, we can see that the value hello exists only in the registry located at /my-registry which we are not currently in. So we cannot access its /hello register directly. We can change which registry we are in with the go-in operation.

... go-in hello .
... print hello .
Hello, world!

We can go back to the registry we were in previously by using the go-out operation.

... go-out .
... print hello .
Error: Value at `/hello` not found.

We can import values from one registry into their corresponding registers in the current registry by using the import operation.

... import my-registry .
... print hello .
Hello, world!

We can also execute an instruction inside a certain registry by using the in command.

... in my-registry ( print [ combine hello " Goodbye!" ] . ) .
Hello, world! Goodbye!

Aside from being a useful data structure, registries are also how ONBU does namespaces. For example,

... set /math [ registry ] .
... set /factorial
       (
         set /i 2 .
         set /prod 1 .
         while ( lt-eq i t )
               (
                 set /prod [ mul i prod ] .
                 incr i .
               ) .
         answer prod .
       ) math .
... math:/factorial /t 5 .
ans = 120

You can assign instructions inside registries and then they can be called from that registry using the colon notation. This code block also demonstrates that the set operation optionally takes a third argument: the registry in which to set the register. When omitted, it sets the register in the current registry.

Of course, if you don't need to worry about name collisions, you can just import it.

... import math .
... factorial /t 5 .
ans = 120

Registries are flexible data structures. They can be used to represent structures with multiple kinds of values (like struct in C). Or they can be used as namespaces for libraries or different parts of a code base.

2.2.1 Additional details about colon notation

The colon notation can be nested.

... set /x [ registry /y [ registry /z 10 ] ] .
... print x:/y:/z .
10

It can also choose the register by referencing the value of another register.

... set /x [ registry /y 10 ] .
... set /z /y .
... print x:z .
10

But we cannot use subexpressions in the colon notation:

... set /x [ registry /y 10 ] .
... ' BAD! print x:[ answer /y ] .

2.3 Registers

Registers are the locations of data in Registries. They are data themselves and can be manipulated like other data objects in ONBU. For example, you can set other registers to registers and use those values wherever you would use registers.

... set /x /y .
... print x .
/y
... set x "hello" .
... print x .
/y
... print y .
hello

You can move data between Registers with the move command:

... set /x "hello" .
... move /x /y .
... print y .
hello
... print x .
Error: Value at register /x not found.

Registers are data denoting locations so they are often useful ways to manipulate the locations of values instead of the values themselves, as with the move and set operations.

2.4 Environment

2.4.1 Saving state

ONBU is not only a programming language. It is also an environment. The current state of ONBU — the values and where they are located in the registry — can be saved to disk and re-loaded later on, using the save and load operations.

To save the current registry to a file called state.donbu,

... save "state.donbu" .

To load the registry back in:

... load "state.donbu" .

Optionally, a second argument can be provided to load to load the data into a specific registry, not just the current one.

... load "state.donbu" old-stuff .

Where old-stuff is a registry.

2.4.2 Interacting with the user

One of ONBU's strengths is in rapidly-developing interactive programs. ONBU's syntax is command-like so interaction in such a program can often be done using ONBU's prompt. These types of programs often require input from the user and the ability to interact with other non-ONBU programs on the computer.

The input operation accepts input from the user in the same way as ONBU's usual prompt. It takes a register as an argument and assigns the String value the user enters to that register in the current registry.

... set /get-name 
        (
          print "What is your name?" .
          input /ans .
        ) .
... get-name .
What is your name?
Zach
ans = "Zach"

To call commands from the shell, use the shell operation. The shell operation sets the /ans register to a string containing the output of the command.

... shell "ls" .
ans = "file1.txt
file2.txt
"

The change-dir operation changes the working directory of ONBU and the current-dir operations sets the /ans register to a String giving the current working directory.

... change-dir "/home/zlf" .
... current-dir .
ans = "/home/zlf" 

2.5 Tasks (concurrent programs)

ONBU can be used to write concurrent (multi-threaded) programs using Task objects. ONBU's tasks are a type of data. You can do everything to them that you can do to other data (move it, delete it, etc).

Tasks are created with the task operation. The task operation takes three arguments. The first gives the register to assign the Task to. The second gives the body of the Task, an instruction that the Task will execute when it is run (Tasks do not immediately start running on creation). The third argument is the initial state of the task, a registry defining the environment in which the task will run.

... task /my-task ( commands to run... ) [ registry . ] .

Tasks are started with the run-task operation. run-task takes a single argument, the task to start running. Running a task means that the instruction that forms the body of the task is executed. Only one instance of the task can be running at a given time but the same task executed can be executed multiple times (the state of the task will not revert when it is run again).

The structure of a task is described in the below table:

Element Description
Body The instruction run in a separate thread from the main ONBU thread.
State A Registry that contains the current state of the task. The registry being modified by the Task's body.
Queue A Registry that contains data queued to pass from the main thread to the Task's thread or vice-versa.

A Task's state registry is entirely separate from the current registry. There is no way to directly access data in a task except in the body of the task. No data in the Task's queue can be accessed directly in any thread. Data can be sent between the main thread and the task thread using operations.

Tasks accept data when they take data from their queue and move it into their state registry. They can do this with one of three operations: accept, accept-or, and select.

The accept operation takes a register as an argument and waits for data to be available at that register in the Task's queue. A Task can be provided as an optional second argument to accept to look for the data in another task's queue. Once that data is available, it sets the /ans register in the Task's state registry to the value of the data. accept is blocking so no other code will be executed in the thread running the accept operation until the data is available in the queue. ONBU does not allow running accept in the main task because there is no way to exit the blocking. accept-or can be used instead.

The queue operation moves data from one registry into the queue of another Task. Its arguments are a register to set in the queue registry, the data to send, and the task's queue to access.

... task /t1 ( print [ accept /hello ] . ) .
... run-task t1 .
... queue /hello "Hi!" t1 .
Hi!

Because the accept operation blocks the thread until the data is entered into the queue, it cannot be used when the data accept is looking for is optional.

The accept-or operation checks whether data exists at a certain register in the queue, if it does, it sets the /ans register to the result. If not, it executes its second argument, an instruction, in its state registry. This operation does not block. It checks once if the data is available and otherwise executes the instruction. For example, in a Task body, we might have:

accept-or /hello ( set /hello "Hello." ) .

The main use of accept-or is to provide default values for missing data or to do something else if data is not available.

Alternatively, the select operation executes one of several instructions if data comes in at any number of registers. select takes paired registers and instructions as its arguments. It waits for data to appear in its queue at any of the registers listed. The first register to receive data is "selected". The instruction associated with the register is executed in the register's state registry after setting the /ans register to the value of the data located at the register in the queue.

... task /t1 ( 
                set /continue True .
                while ( answer continue )
                      (
                        select /add-one ( print [ add ans 1 ] ) 
                               /sub-one ( print [ sub ans 1 ] )
                               /continue ( set /continue ans ) .
                      ) .
             ) .
... run-task t1 .
... queue /add-one 3 t1 .
4
... queue /sub-one 3 t1 .
2

3 A Complete Example Program

The following program is one I use daily to keep track of time spent on client work. I use the program to keep track of my time by choosing a client and then using the start and stop instructions. And then the day-time instruction to output the hours for each client by day.

3.1 Source Code

' Initial state

set /current-client "Non-billable" .
set /last-clock -1 .
set /record [ registry ] .
set /running False .
set /current-file Nothing .

' Start a new record of work or load an old one

set /init
    < (
      up set /current-file file .
      please ( load file record )
             (
               up set /record [ registry ] .
               up set /client-list [ registry ] record .
               print "Starting new file." .
             ) .
    ) /file > .

' Save record of work in ONBU format

set /save-hours
    (
      in record ( save current-file ) .
    ) .

' Set current client

set /client
    <
    (
      if [ not running ]
         {
           if [ exist /name ]
              {
                up set /current-client name .
                add-client-to-list name .
              }
              {
                up set /current-client [ choose-client ] .
              } .
           up set /last-clock -1 .
         }
         {
           print "Currently running, stop first." .
         } .
    )
    /name
    > .

' Start clock for current client
set /start
    (
      if [ not running ]
         {
           up set /last-clock [ clock ] .
           up set /running True .
           print [ combine "Starting on " current-client " ..." ] .
         }
         {
           print "Currently running, stop first." .
         } .
    ) .

' Stop clock for current client with a description of task.

set /stop
    (
      if running
         {
           last record /r .
           next ans .
           set ans
               [
                 registry
                   /client current-client
                   /start-time last-clock
                   /stop-time [ clock . ]
                   /description desc
               ] record .
           up set /running False .
           save-hours .
         }
         {
           print "Not currently running." .
         } .
    ) .


' A map to day of week
set /day-of-week-map
    [
      list
        "Sunday"
        "Monday"
        "Tuesday"
        "Wednesday"
        "Thursday"
        "Friday"
        "Saturday"
        "Sunday" 
    ] .


' Convert milliseconds to hours
set /make-hours-day-time
    < (
      do < ( div t 1000.0 60 60 ) /t > structure .
    ) /structure > .


' Return a structure describing a single day of activity
set /day-time
    (
      set /final [ registry ] .
      set /clock1
          [
            make-clock
              [
                registry
                  /day day
                  /month month
                  /year year
                  /hour 0
                  /minute 0
                  /seconds 0
                  /milliseconds 0
                . ] .
          ] .

      set /clock2
          [
            make-clock
              [
                registry
                  /day day
                  /month month
                  /year year
                  /hour 23
                  /minute 59
                  /seconds 59
                  /milliseconds 999
                . ] .
          ] .

      set /i /r1 .
      while ( exist i record )
            (
              if [ and
                     [ gt-eq record:i:/start-time clock1 ]
                     [ lt-eq record:i:/start-time clock2 ] ]
                 {
                   set /client [ to-register record:i:/client ] .
                   set /elapsed [ sub record:i:/stop-time record:i:/start-time ] .

                   if [ exist client final ]
                      {
                        set client [ add final:client elapsed ] final .
                      }
                      {
                        set client elapsed final .
                      } .
                 } .
              set /i [ next i ] .
            ) .
      make-hours-day-time final .
    ) .


' Add client to client list
set /add-client-to-list
    < (
      last record:/client-list /c .
      next ans .
      set ans client record:/client-list .
    ) /client > .


' Select client from menu
set /choose-client
    (
      set /i /c1 .
      while ( exist i record:/client-list )
            (
              print
                [
                  combine
                    [ to-string [ to-number i ] ]
                    ": " 
                    record:/client-list:i .
                ] .
              set /i [ next i . ] .
            ) .
      print "Choose Client: " False .
      input /num .
      set /loc [ to-register [ combine "c" num ] ] .
      answer record:/client-list:loc .
    ) .

' Get current status (client, whether running)
set /status
    (
      print [ combine "Current client: " current-client ] .
      if running
         {
           print "Running a task." .
         }
         {
           print "Not running." .
         } .
    ) .

' Rename a client
set /rename-client
    (
      ' Update client in list
      set /i /c1 .
      set /cont True .
      while ( and [ exist i record:/client-list ]
                  cont )
            (
              if [ string-eq record:/client-list:i client ]
                 {
                   set i new-client record:/client-list .
                   set /cont False .
                 } .

              set /i [ next i ] .
            ) .

      ' Update client in records

      set /i /r1 .
      while ( exist i record )
            (
              if [ string-eq record:i:/client client ]
                 {
                   set /client new-client record:i .
                 } .
              set /i [ next i ] .
            ) .
    ) .


' Rewind X minutes from start

set /start-back
    < (
      if [ not running ]
         {
           print "Currently not running." .
         }
         {
           up set /last-clock [ sub last-clock [ mul x 60 1000 ] ] .
         } .
    ) /x > .

' Rewind X minutes from last entry

set /rewind-last
    < (

      set /end [ last record /r ] .
      set /stop-time [ sub record:end:/stop-time [ mul x 60 1000 ] ]
          record:end .
    ) /x > .

3.2 Building the Code

We can then use a script like the following, supposing we have saved the above code in a file billable.onbu, to build a binary version of the program in a convenient location.

... source "billable.onbu" .
... save "~/.donbus/billable.donbu" .

3.3 Using the code

Then, whenever we would like to load the code, we can simply do:

... use "billable" .

Then all the instructions from the billable program are available to us.

4 Examples

4.1 Quicksort

set /swap
    (
      set /tmp input:i .
      set i input:j input .
      set j tmp input .
    ) .

set /partition
    (
      set /pivot input:end .
      set /i start .
      set /j start .
      set /cont True .
      while ( answer cont )
            (
              if [ compare input:j pivot ]
                 {
                   swap /input input /i i /j j .
                   set /i [ next i ] .
                 } .

              if [ register-eq j end ]
                 {
                   set /cont False .
                 } .

              set /j [ next j ] .
            ) .

      swap /input input /i i /j end .
      answer i .
    ) .

set /-quicksort
    (
      if [ not [ register-eq start end ] ]
         {
           partition /input input /start start /end end /compare compare . 
           move /ans /p .
           if [ not [ register-eq p start ] ]
              {
                -quicksort /input input /start start /end [ previous p . ] .
              } .

           if [ not [ register-eq p end ] ]
              {
                -quicksort /input input /start [ next p . ] /end end .
              } .
         } .
    ) .

set /quicksort
    (
      if [ not [ exist /end ] ]
         {
           set /end [ last input slice . ] .
         } .
      if [ not [ exist /start ] ]
         {
           set /start [ next slice . ] . 
         } .

      if [ not [ exist start input ] ]
         {
           error "Starting register not found in input." .
         } .

      if [ not [ exist end input ] ]
         {
           error "Ending register not found in input." .
         } .

      -quicksort /input input /start start /end end /compare compare .
    ) .

Example of use:

... set /x [ list 3 2 4 1 ] .
... quicksort /input x /slice /t /compare lt .
... print x .
ans = a registry with:
t1 of type Integer, value: 1
t2 of type Integer, value: 2
t3 of type Integer, value: 3
t4 of type Integer, value: 4
... quicksort /input x /slice /t /compare gt .
... print x .
a registry with:
t1 of type Integer, value: 4
t2 of type Integer, value: 3
t3 of type Integer, value: 2
t4 of type Integer, value: 1

4.2 A Line-Oriented Text Editor

This example is a simple interactive program where the user enters commands to edit a buffer.

' Line-oriented text editor
set /onbued [ registry . ] .
in onbued
   (
     set /buffer [ list . ] . 
     set /cur-line /t1 . 
     set /n ( up set /cur-line [ next cur-line . ] . ) .
     set /p ( up set /cur-line [ previous cur-line . ] . ) .
     set /i
         < (
           set /onbued-str s buffer .
           in buffer
              (
                set cur-line onbued-str .
              ) .
         )
         /s > . 
     set /o
         (
           print [ get cur-line buffer . ] .
         ) .
   ) .

Example of use:

... import onbued .
... i "Hello" .
... o .
Hello
... n .
... i "Goodbye" .
... o .
Goodbye
... p .
... o .
Hello

4.3 Numerical Differentiation

' Compute the numerical derivative of an operation that takes an arg
set /deriv
    (
      if [ not [ exist /h ] ]
         {
           set /h 0.001 .
         } .
      div [ sub [ f [ add x h ] ]
                [ f [ sub x h ] ] ]
          [ mul 2 h ] .
    ) .

Example of use:

... set /f < ( power t 2 ) /t > .
... deriv /f f /x 1.0
ans = 2.0

4.4 Compute Factorial

' compute a factorial using while
set /factorial
    < (
      set /i 2 .
      set /prod 1 .
      while ( lt-eq i t )
            (
              set /prod [ mul i prod ] .
              incr i .
            ) .
      answer prod .
    ) /t > .


' compute factorial using recursion

set /-factorial-recurse
    (
      if [ eq t 1 ]
         1
         {
           mul t [ -factorial-recurse /t [ sub t 1 ] ] .
         } .
    ) .

set /factorial-recurse < -factorial-recurse /t > .

Example of use:

... factorial 5 .
ans = 120
... factorial-recurse /t 5 .
ans = 120

4.4.1 Note on Recursion

Operations cannot be directly defined recursively, but Instructions can be. That is why the code above first defines -factorial-recurse as an instruction and then creates the operation.

4.5 Numerical Integration

' takes an operation as an argument and return left hand
' riemmann sum over range

set /integrate 
    ( 
      set /delta [ div [ sub ub lb ] n ] .
      set /x lb .
      set /int 0.0 .
      repeat n
             (
               incr int [ f x ] .
               incr x delta .
             ) .
      mul int delta .
    ) .

Example of use:

... integrate /f < ( power t 2 ) /t > /lb 0.0 /ub 1.0 /n 1000 .
ans = 0.332834

4.6 Basic Linear Algebra

This example demonstrates both application methods like do and collapse as well as when to use operations for programmer code. Operations make more sense when the arguments are all the same kind of thing, the order is the only thing that might matter, like in this example where we are multiplying two matrices. Instructions are preferred when the argument's position is uninformative about the purpose of the argument, as in the previous example with numerical integration.

' Computes v^T w for a vector 
set /dotproduct
    < (
      do mul v w .
      collapse add ans  .
    ) /v /w > .

' matrix-multiply A B computes A^T B
set /matrix-multiply
    < (
      set /i /t1 .
      set /out [ list ] .
      while ( exist i B )
            (
              do < ( dotproduct t1 B:i ) /t1 > A .
              in out ( set i ans ) .
              next i .
              move /ans /i .
            ) .
      answer out .
    ) /A /B > .

' Computes A^T 
set /transpose
    < (
      set /i /t1 .
      set /out [ list ] .
      while ( exist i A:/t1 )
            (
              do < ( answer t1:i ) /t1 > A .
              move /ans /res .
              in out ( set i res ) .
              next i .
              move /ans /i .
            ) .
      answer out .
    ) /A > .

Example of use:

... set /A [ list [ list 1 2 ] [ list 3 4 ] ] .
... set /B [ list [ list 6 2 ] [ list 3 4 ] ] .
... transpose A .
ans = a registry with:
t1 of type Registry, value: a registry with:
t1 of type Integer, value: 1
t2 of type Integer, value: 3
t2 of type Registry, value: a registry with:
t1 of type Integer, value: 2
t2 of type Integer, value: 4
... matrix-multiply A B .
ans = a registry with:
t1 of type Registry, value: a registry with:
t1 of type Integer, value: 10
t2 of type Integer, value: 26
t2 of type Registry, value: a registry with:
t1 of type Integer, value: 11
t2 of type Integer, value: 25

4.7 Split a String

' Tokenize a string /line by splitting on string /sep
set /tokenize
    (
      set /i 1 .
      set /len [ length line . ] .
      set /cur-reg /t1 .
      set /tokened [ list . ] .
      set /init True .
      while ( lt-eq i len . )
            (
              set /d [ substring line i i . ] .
              if [ string-eq d sep . ]
                 {
                   in tokened ( set cur-reg buffer . ) .
                   set /cur-reg [ next cur-reg . ] .
                   set /init True .
                 }
                 {
                   if init
                      { set /buffer d . set /init False . }
                      { set /buffer [ combine buffer d . ] . } .
                 } .
              set /i [ add i 1 . ] .
            ) .
      in tokened ( set cur-reg buffer . ) .
      answer tokened .
    )  .

Examples of use:

... tokenize /line "1,2,3" /sep "," .
ans = a registry with:
t1 of type String, value: 1
t2 of type String, value: 2
t3 of type String, value: 3

4.8 Linking C code

One of the big outstanding tasks with ONBU is to write a more user-friendly (and documented) libonbu for adding extensions, but it is currently possible to write ONBU operations in C, using ONBU's link operation to import the code.

The following is an example of C code.

/* 
   A C Function to compute log(x+1) 
*/

/* Change to wherever briple.h is located or just "briple.h" if it is on the path */
#include "../src/briple.h"
#include <math.h>

void
log_plus_one (arg a, registry* reg)
{

  check_length(&a, 2, "log1", reg->task->task);
  if (is_error(-1, reg->task->task)) return;

  data* arg1 = resolve(a.arg_array[1], reg);

  if (arg1 == NULL)
    {
      do_error("<log1> requires an argument.", reg->task->task);
      return;
    }

  if (arg1->type != Integer && arg1->type != Real)
    {
      do_error("Argument to <log1> must be numeric.", reg->task->task);
      return;
    }

  double ans;
  if (arg1->type == Integer)
    {
      double z = mpz_get_d(*((mpz_t*) arg1->data));
      ans = log(z + 1.0);
    }
  else
    {
      ans = log(*((double*) arg1->data) + 1.0);
    }

  data* d;
  assign_real(&d, ans);
  ret_ans(reg,d);
}

We then compile it into a shared object file, linking against libonbu.

#!/bin/sh

## Assumes you have already make installed briple so that libbriple is on your path and that the
## library is installed in /usr/local/lib.  Modify path if installed elsewhere.

gcc -c -fPIC -o link.o link.c
gcc -shared -Wl,-rpath -Wl,/usr/local/lib -o link.so link.o -lbriple

The code then can be loaded into ONBU with the link operation like so:

' Demonstrates linking C code in
link "./link.so" "log_plus_one" "log1" .

The first argument of link gives the shared-object file name. The second argument gives the name of the function. The third argument gives what the name of the ONBU operation will be.

Now, the operation can be called in the following way:

... log1 1 .
ans = 0.693147

4.9 Concurrent Programming

ONBU uses Tasks to run Instructions in separate processes. The following example demonstrates how queue, accept, and select work to move data between the main Task and other Tasks.

' Demonstrate tasks

task /t1
     (
       while ( answer True . )
             (
               queue /current [ accept /hello . ] .
             ) .
     )
     [ registry . ] .

run-task t1 .

queue /hello "Hi!" t1 .
accept-or /current ( print "Result not ready yet." . ) t1 .

task /t2
     (
       set /continue True .
       while ( answer continue . )
             (
               select 
                      /double
                      (
                        print [ mul ans 2 . ] .
                      )
                      /triple
                      (
                        print [ mul ans 3 . ] .
                      )
                      /end
                      (
                        set /continue False .
                      ) .
             ) .
     )
     [ registry . ] .

run-task t2 .
queue /double 3 t2 .
queue /triple 10 t2 .
queue /double 7 t2 .
queue /end Nothing t2 .

The following task that can be used to run any code in the background.

task /t1
     (
       set /instr [ accept /instruction ] .
       print "HELLO" .
       accept /context .
       import ans .
       instr .
       queue /result ans .
     )
     [ registry ] .

run-task t1 .
queue /instruction ( add x y ) t1 .
queue /context [ registry /x 2 /y 4 ] t1 .
accept-or /result ( print "Result not ready yet." ) t1 .
accept-or /result ( print "Result not ready yet." ) t1 .

5 Reference

This section gives the full vocabulary of the language. The grammar of the language is described in the previous sections. I describe each type of data, and the operations associated with it.

Throughout, I enclose an operation's optional arguments between bars (||).

5.1 Registries

5.1.1 Short description

A registry contains data located at registers. Data can be retrieved from and inserted into registries. Registries also defined the scope of ONBU code because code is executed inside registries. So the values of variables depend on which registry you are executing the code. This allows the user to flexibly manipulate scope.

5.1.2 Implementation details

Registries are hash tables. The keys to the table are called registers. The number of bins in the hash table grows as more elements are added. This keeps lookup times low, but the registry will rehash as it grows. Rehashing can be turned off by using the auto-rehash operation.

5.1.3 Registry operations

  1. Creation
    • registry REGISTER1 VALUE1 REGISTER2 VALUE2 ... — sets the /ans register to a registry with VALUE1 located at REGISTER1 and so on.
    • list VALUE1 VALUE2 ... — set the /ans register to a Registry with VALUE1 at Register /t1, VALUE2 at Register /t2, and so on.
    • range INTEGER1 INTEGER2 |INTEGER3| — set the /ans register to a Registry with INTEGER1 at /t1 and INTEGER1 + INTEGER3 at /t2 and so on so long as the value is less than or equal to INTEGER2.
  2. Insert, move, and remove data to and from registers
    • set REGISTER VALUE |REGISTRY| — sets the value at REGISTER to VALUE in the registry REGISTRY. If the REGISTRY argument is omitted, then it will set the register in the current registry.
    • move REGISTER1 REGISTER2 — move the value located at REGISTER1 to REGISTER2. Does not copy the data so it is an efficient way to move data.
    • delete REGISTER — delete the value at REGISTER in the current registry.
    • free REGISTER — delete the value at REGISTER in the current registry. Does not send the free'd data to the garbage collector but instead releases the memory immediately. The trade-off versus delete is that it forces immediate removal of memory in main execution thread (which takes longer and blocks other instructions) while delete is faster but the memory may not be immediately removed (higher memory use).
  3. Access data in Registry
    • get REGISTER |REGISTRY| — sets the /ans register to the value located at REGISTER in REGISTRY. If the REGISTRY argument is not specified, get from the current registry.
    • exist REGISTER |REGISTRY| — set the /ans register to True if a value exists at the REGISTER in REGISTRY. If the REGISTRY argument is omitted, check in the current registry.
    • import REGISTRY — set the Registers in the current Registry to hold the same values that they hold in REGISTRY.
    • filter REGISTRY OPERATION — set the /ans register to a Registry containing all the elements in REGISTRY such that the OPERATION applied to the element of the Registry sets the /ans register to True.
  4. Apply Operations to elements of a Registry
    • do OPERATION REGISTRY1 REGISTRY2 ... REGISTRYN — execute OPERATION which takes its arguments (1…N) from each element in REGISTRY1, …, REGISTRYN. Sets the /ans register to a Registry which contains whatever the OPERATION evaluates to at the corresponding Registers. For example, do add [ list 1 2 3 . ] [ list 4 5 6 . ] sets the /ans register to a Registry with elements (5,7,9) at registers (/t1,/t2,/t3). do will only return results at Registers that exist in all Registries. So, for example, do add [ list 1 2 3 . ] [ list 4 5 . ] sets the /ans Register to list 5 7.
    • collapse OPERATION REGISTRY |REGISTER| — sets the /ans register to the value of applying OPERATION to the value in the REGISTRY at Register /REGISTER1 and /REGISTER2 and then again to the result of that and to the value of /REGISTER3 and so on. For example, collapse add [ list 1 2 3 . ] /t sets the /ans register to 6. If the REGISTER argument is omitted, it is assumed to be /t.
  5. Execute code in a registry
    • in REGISTRY INSTRUCTION — call INSTRUCTION in REGISTRY.
    • up STATEMENT — execute STATEMENT in the registry above the current one after resolving the values of the arguments of the statement in the current registry. For example, we can create an Instruction that can be used to increment a variable in place, like so:

      ... set /inc ( up set x [ add 1 [ up get x . ] . ] . ) .
      ... set /x 1 . 
      ... inc /x /x . 
      ... print x .
      2
      
  6. Test if a registry
    • is-registry VALUE — sets the /ans Register to True if VALUE is a Registry and to False otherwise.
  7. Technical
    • rehash REGISTRY — manually rehash the registry's underlying hash table.

5.2 Reals and Integers

5.2.1 Short description

Real and Integer are distinct types in ONBU. A Real is a number with a decimal point. An Integer is a number without a decimal point. Sometimes either type can be used for an operation. In this case, I refer to the argument type as a Number in describing the operation for brevity. Number is not a ONBU type.

5.2.2 Implementation details

A Real is a double-precision floating point value (equivalent to C's double).

An Integer is an arbitrarily long integer (a "bignum"). Integers are implemented using GnuMP.

5.2.3 Real and Integer operations

  1. Arithmetic operations
    • add NUMBER1 NUMBER2 ... — adds all the numbers together and sets the /ans register to the result.
    • mul NUMBER1 NUMBER2 ... — multiplies all the numbers together and sets the /ans register to the result.
    • sub NUMBER1 NUMBER2 ... — subtracts the second number from the first and the third number from that and so on and sets the /ans register to the result.
    • div NUMBER1 NUMBER2 ... — divides the first number by the second, the result by the third number, and so on and sets the /ans register to the result.
    • mod NUMBER1 NUMBER2 — return the remainder of dividing the first number by the second.
    • incr INTEGER1 |INTEGER2| — increment INTEGER1 by INTEGER2 or by 1 if INTEGER2 is omitted. The difference between this operation and add is that it modifies INTEGER1 in place. INTEGER2 can be a negative number.
  2. Comparison operations
    • gt NUMBER1 NUMBER2 — set the /ans register to True if NUMBER1 is greater than NUMBER2 and to False otherwise.
    • lt NUMBER1 NUMBER2 — set the /ans register to True if NUMBER1 is less than NUMBER2 and to False otherwise.
    • eq NUMBER1 NUMBER2 — set the /ans register to True if NUMBER1 is equal to NUMBER2 and to False otherwise.
    • lt-eq NUMBER1 NUMBER2 — set the /ans register to True if NUMBER1 is less than or equal to NUMBER2 and to False otherwise.
    • gt-eq NUMBER1 NUMBER2 — set the /ans register to True if NUMBER1 is greater than or equal to NUMBER2 and to False otherwise.
  3. Conversion operations
    • to-number STRING|REGISTER — if the argument is a String, try to convert to a number and set the /ans Register to the result. If the argument is a Register and ends in a number, set the /ans Register to the result.
    • to-real INTEGER — set the /ans Register to a Real representing the INTEGER.
  4. Test if type operations
    • is-integer VALUE — sets the /ans Register to True if the Value is an Integer and to False otherwise.
    • is-real VALUE — sets the /ans Register to True if the Value is a Real and to False otherwise.
  5. Common mathematical operations
    • log NUMBER — set the /ans Register to the natural logarithm of NUMBER.
    • exp NUMBER — set the /ans Register to the NUMBER power of the natural base.
    • power NUMBER1 NUMBER2 — set the /ans Register to NUMBER1 raised to the NUMBER2 power.
    • floor REAL1 — set the /ans Register to the greatest Integer less than REAL1.
    • ceiling REAL1 — set the /ans Register to smallest Integer greater than REAL1.

5.3 Strings

5.3.1 Short description

Strings in ONBU are text. A string literal is enclosed in quotation marks. ONBU supports UTF-8 encodings. Non-ASCII characters are handled correctly: a single character is a single character no matter how many bytes it is. It also implements a collection of escape sequences for whitespace and quotation marks that should be included in the string itself.

The escape sequences are:

Escape Sequence Meaning
\\ \
\' ' (single-quote)
\t tab
\n newline
\r carriage return
' (single-quote) " (double quote)

5.3.2 Implementation details

Strings are internally UTF-32 encoded to enable faster access by index number and because it makes creating substrings faster. ONBU accepts UTF-8 as input because that is more common in terminals and in file-encodings.

5.3.3 String operations

  1. Access, search, and modify string elements
    • substring STRING INTEGER1 INTEGER2 — sets the /ans Register to the subset of STRING where the characters included are determined by INTEGER1 and INTEGER2. Strings are 1-indexed in ONBU so the first character is at location 1. If the INTEGER is less than or equal to 0, determine the location from the end of the String. So if INTEGER1 and INTEGER2 are 0, then it will set the /ans register to the last character in STRING.
    • match STRING1 STRING2 |INTEGER| — find occurences of regular expression STRING1 in String STRING2. Sets the /ans Register to a Registry with all submatches located at the /tN registries. The optional third argument gives the number of matches to return. If 0 is provided, return all matches (default).
    • replace STRING1 STRING2 STRING3 |INTEGER| — set the /ans Register to a String formed by replacing all occurences of regular expression STRING1 with STRING2 in STRING3. The optional fourth argument gives the number of matches to replace. If 0, replace all (default).
  2. String properties
    • length STRING — sets the /ans register to the number of characters in STRING.
  3. String comparison
    • string-eq STRING1 STRING2 — sets the /ans Register to True if the two strings are equal and to False otherwise.
    • string-lt STRING1 STRING2 — sets the /ans Register to True if STRING1 is less than STRING2 in the sense that the ASCII characters have lower values.
    • string-gt STRING1 STRING2 — sets the /ans Register to True if STRING1 is greater than STRING2 in the sense that the ASCII characters have lower values.
    • char-eq STRING1 INTEGER STRING2 — test if character INTEGER of string (using same index numbers as for substring) is equal to STRING2. This is a slight performance improvement over testing whether the substring with only one character is equal to the string.
  4. Combine strings
    • combine STRING1 STRING2 ... STRINGN — sets the /ans register to the concatenation of the two Strings so that the resulting String is "STRING1STRING2".
  5. Convert to string
    • to-string INTEGER|REAL|REGISTER |INTEGER2| — sets the /ans Register to a String representing the INTEGER or REAL or REGISTER provided as the first argument. The second argument is used if a REAL argument is provided. INTEGER2 gives the number of elements after the decimal point to include. If not provided, 6 decimal places are included.
  6. Test if string
    • is-string VALUE — sets the /ans Register to True if the VALUE is a String and to False otherwise.
  7. Strings from User Input
    • input REGISTER — reads a line of text the user enters and sets REGISTER to that value (always a String).

5.4 Registers

5.4.1 Short description

Registers are a type of data in ONBU usually used to refer to locations in registers. More generally, they are symbols. They can be compared with one another and modified. Registers are not "associated" with registries. They are data without any associated context.

5.4.2 Implementation details

Registers are a structure containing the name of the registry and the hashed value of the name so they can be easily inserted into the registry hash table.

5.4.3 Register operations

  1. Operations on "list" registers
    • next REGISTER — if the REGISTER ends in a number, return the Register with that number incremented by 1.
    • previous REGISTER — if the REGISTER ends in a number, return the Register with that number minus 1. If that would cause the number to be less than 1 set the /ans Register to the "first" register with that prefix so that previous /t1 . sets /ans to /t1.
    • last REGISTRY REGISTER — return the last Register in the REGISTRY that starts with REGISTER. So that if you had registers /x0, /x1, and /x2 in the Registry, last REGISTRY /x would return /x2.
  2. Convert to register
    • to-register STRING|INTEGER — sets the /ans register to a Register named STRING or to /tINTEGER.
  3. Register comparison
    • register-eq REGISTER1 REGISTER2 — sets the /ans Register to True if the two Registers are the same and to False otherwise.
  4. Test if a register
    • is-register VALUE — sets the /ans Register to True if the VALUE is a Register and to False otherwise.

5.5 Booleans

5.5.1 Short description

Booleans can take on two values: True or False. Comparison and testing operations usually set the /ans register to a Boolean value.

5.5.2 Implementation details

Booleans are simply a binary value. They use the C99 bool type internally.

5.5.3 Boolean operations

  • is-boolean VALUE — sets the /ans Register to True if the VALUE is a Boolean and to False otherwise.
  • and BOOLEAN1 BOOLEAN2 — sets the /ans Register to True if BOOLEAN1 and BOOLEAN2 are True and to False otherwise.
  • or BOOLEAN1 BOOLEAN2 — sets the /ans Register to True if either BOOLEAN1 or BOOLEAN2 are True and to False otherwise.
  • not BOOLEAN — sets the /ans Register to True if BOOLEAN is False and to False otherwise.

5.6 Instructions

5.6.1 Short description

Instructions are code objects which have not yet been executed. They can be called or executed in different registries.

5.6.2 Implementation details

Instructions are internally "compiled" code. They are then supplied with an environment (a registry) before they are executed. The compiled code object is a linked list of statements each of which is a linked list of elements. Elements are either literal data values or the names of registers to lookup in the environment.

5.6.3 Instruction operations

  • call INSTRUCTION REGISTER1 VALUE1 REGISTER2 VALUE2 ... — executes INSTRUCTION in a Registry with REGISTER1 assigned to VALUE1 and so on.
  • is-instruction VALUE — sets the /ans Register to True if the VALUE is an Instruction and to False otherwise.
  • op INSTRUCTION REGISTER1 ... REGISTERN — set the /ans register to an operation that calls INSTRUCTION after binding REGISTER1 to the first argument, and so on.
  • code INSTRUCTION — sets the /ans register to a String containing the code for the INSTRUCTION.
  • is-operation VALUE — sets the /ans Register to True if the VALUE is an Operation and to False otherwise.

5.7 Files

5.7.1 Short description

Files are stream objects that you can write to and read from.

5.7.2 Implementation details

Files are like the C FILE type.

5.7.3 File operations

  • is-file VALUE — sets the /ans Register to True if the VALUE is a File and to False otherwise.
  • open-file STRING1 |STRING2| — sets the /ans Register to a File object corresponding to the file named STRING1 with mode STRING2 (default: "r+").
  • read FILE — read a byte from a file and set the /ans register to the result (an Integer).
  • read-char FILE — read a single character from a File object and set the /ans Register to that character (a String).
  • read-line FILE — sets the /ans Register to the next line from FILE.
  • write INTEGER FILEINTEGER must be in [0,255]. Directly writes a byte to FILE.
  • write-string STRING FILE — Write a STRING to FILE using UTF-8 encoding.
  • close REGISTER — if a File object is located at REGISTER, close the File and remove its reference from the REGISTER.

5.8 Nothing

5.8.1 Short description

Nothing is a value that is not any other value and is a different type than any other value.

5.8.2 Nothing operations

  • is-nothing VALUE — sets the /ans register to True if the value is Nothing and to False otherwise.

5.9 Task Operations

5.9.1 Short description

Tasks are objects that allow you to execute code in another thread and share data between threads.

5.9.2 Implementation details

Tasks contain three elements:

  1. A Body, an instruction that is executed when the Task is run.
  2. A State, a registry that contains the state of the Task. The Body is run inside the State registry. It is persistent across Task runs.
  3. A Queue, a registry that cannot be reached directly but which facilitates data sharing across tasks without worrying about race conditions or other details.

Tasks use mutexes to ensure data is transferred correctly between threads.

5.9.3 Task operations

  • task REGISTER INSTRUCTION |REGISTRY| — Sets REGISTER to contain a new Task with its Body given by the INSTRUCTION and the initial state being set to REGISTRY. If no REGISTRY argument is given, the initial state is a registry containing only the basic operations (the registry always contains the basic operations even if they are not in the initial registry passed to Task).
  • run-task TASK — executes the Task's Body in a separate thread. The same Task object cannot be running in two threads. The operation will give an Error if it is already running.
  • queue REGISTER DATA |TASK| — Sets the REGISTER in the Queue of TASK to value DATA. If the TASK argument is omitted, it is assumed to be set in the queue of the current task (for sharing Task data with another thread). queue cannot be used for the main ONBU Task.
  • accept REGISTER |TASK| — Waits until REGISTER has a value in TASK's Queue. Once it finds a value, set the /ans register to that value. If TASK is omitted, wait for the current Task's Queue to be set.
  • select REGISTER1 INSTRUCTION1 REGISTER2 INSTRUCTION2 ... REGISTERN INSTRUCTIONN — Waits for the Queue in the current task to contain data at any of the Registers (it will prefer earlier Registers if data is available at both). Once it receives data, it sets the /ans register to the data and executes the Instruction in the current registry.
  • accept-or REGISTER INSTRUCTION |TASK| — Checks if REGISTER is set in TASK's Queue. If it is, the /ans register is set to the result. If it is not, it executes INSTRUCTION. If TASK is omitted, search in the current TASK's Queue.

5.10 Control flow operations

  • if BOOLEAN VALUE1 |VALUE2| — if the first argument is True, sets the /ans register to VALUE1, if it is False, sets the /ans register to VALUE2. If VALUE2 is omitted, do nothing if the first argument is False.
  • while INSTRUCTION1 INSTRUCTION2 — Call INSTRUCTION1 in the current Registry. If it sets its /ans register to True, call INSTRUCTION2. Repeat.
  • repeat INTEGER INSTRUCTION — call INSTRUCTION INTEGER times.
  • please INSTRUCTION1 INSTRUCTION2 — Call INSTRUCTION1. If there is an error, then call INSTRUCTION2.

5.11 Environment operations

5.11.1 Modify the /ans register

  • answer VALUE — set the /ans register to VALUE.
  • sit — do nothing but lookup all arguments (causes Expressions — instructions in curly brackets — to be executed).

5.11.2 Exit ONBU

  • exit — exit ONBU.

5.11.3 Print output

  • print VALUE |BOOLEAN| — prints VALUE to screen. If BOOLEAN is False, then omit the newline (default: add newline after printing value).

5.11.4 Input and output files and state

  • source STRING — executes ONBU code in the file named by STRING.
  • save FILENAME — save the contents of the current registry to file.
  • load FILENAME — load the contents of a saved file into the environment.
  • output-code STRING — outputs the code that has been entered at the prompt so far to the file named STRING.
  • clear-code — clear code that has been entered so far so that the code that output-code outputs will be empty.

5.11.5 Error handling

  • error STRING |INTEGER| — outputs error message String and sets an error code INTEGER (if specified, otherwise, the error number is 1).
  • is-error INSTRUCTION — sets the /ans Register to True if there was an error executing the INSTRUCTION and to False otherwise.
  • ignore-errors INSTRUCTION — ignore any errors while executing the instruction, i.e. go to the next statement if one statement errors.

5.11.6 Version

  • version — sets the /ans Register to a String giving the version of ONBU.

5.12 System operations

5.12.1 Interacting with the shell

  • shell STRING — execute the shell command STRING.
  • change-dir STRING — change directory to the location given in STRING.
  • current-dir — set the /ans Register to a String giving the current directory.

5.12.2 Time

  • clock — set the /ans register to the current time in milliseconds from January 1, 1970.
  • make-time INTEGER — given a clock value in milliseconds (as returned by clock) return a registry with elements second (a number of seconds between 0-59), minute (giving a number between 0-59), hour (a number between 0-23), day (giving a number between 1-31),
  • make-clock REGISTRY — make a clock value in milliseconds from a registry describing the time (like the registry returned by make-time)

5.12.3 Random numbers

  • rand |INTEGER| — set the /ans Register to a uniformly distributed random Real between 0 and 1. If INTEGER is provided, set the seed to the INTEGER before drawing random value.

5.13 Package operations

  • use STRING — load a saved registry in ~/.donbus/ with the extension .donbu into the current registry. The file being loaded is: ~/.donbus/STRING.donbu.
  • build STRING REGISTRY — saves contents of registry to ~/.donbus/STRING.donbu so that it can be loaded as a package.
  • link STRING1 STRING2 STRING3 — loads shared object file STRING1 and its function named STRING2 into ONBU operation named STRING3.

5.14 Interpreter options

5.14.1 Operation

  • interpreter REGISTER VALUE — sets interpreter option REGISTER to VALUE.

5.14.2 List of options

  • /print-ans — if True (default) or any non-Boolean value, then print the value of the /ans Register at the end of commands, if False suppress printing.
  • /auto-rehash — if True (default) or any non-Boolean value, then rehash registries automatically as they grow in size. If False do not rehash automatically.
  • /print-errors — if True (default) or any non-Boolean value, then print error descriptions. if False, do not print error descriptions.

6 Saved Registries

Note: When saving, only "permanent" data is written to disk. For example, operations written in C will not be written to disk because these are linked in at run time. File values will also not be written to disk because the file stream would need to be re-opened after restarting ONBU in any case; it has no permanent information. All other data types are written to disk.

This section will describe the storage format for ONBU saved files. A user or programmer will never need to know the internal structure of the save file, but it will be provided here to have complete documentation and to perhaps be useful to people writing a C extension to ONBU.

7 GNU Free Documentation License

GNU Free Documentation License

Version 1.3, 3 November 2008

Copyright © 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc. <https://fsf.org/>

Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.

0. PREAMBLE

The purpose of this License is to make a manual, textbook, or other functional and useful document "free" in the sense of freedom: to assure everyone the effective freedom to copy and redistribute it, with or without modifying it, either commercially or noncommercially. Secondarily, this License preserves for the author and publisher a way to get credit for their work, while not being considered responsible for modifications made by others.

This License is a kind of "copyleft", which means that derivative works of the document must themselves be free in the same sense. It complements the GNU General Public License, which is a copyleft license designed for free software.

We have designed this License in order to use it for manuals for free software, because free software needs free documentation: a free program should come with manuals providing the same freedoms that the software does. But this License is not limited to software manuals; it can be used for any textual work, regardless of subject matter or whether it is published as a printed book. We recommend this License principally for works whose purpose is instruction or reference.

1. APPLICABILITY AND DEFINITIONS

This License applies to any manual or other work, in any medium, that contains a notice placed by the copyright holder saying it can be distributed under the terms of this License. Such a notice grants a world-wide, royalty-free license, unlimited in duration, to use that work under the conditions stated herein. The "Document", below, refers to any such manual or work. Any member of the public is a licensee, and is addressed as "you". You accept the license if you copy, modify or distribute the work in a way requiring permission under copyright law.

A "Modified Version" of the Document means any work containing the Document or a portion of it, either copied verbatim, or with modifications and/or translated into another language.

A "Secondary Section" is a named appendix or a front-matter section of the Document that deals exclusively with the relationship of the publishers or authors of the Document to the Document's overall subject (or to related matters) and contains nothing that could fall directly within that overall subject. (Thus, if the Document is in part a textbook of mathematics, a Secondary Section may not explain any mathematics.) The relationship could be a matter of historical connection with the subject or with related matters, or of legal, commercial, philosophical, ethical or political position regarding them.

The "Invariant Sections" are certain Secondary Sections whose titles are designated, as being those of Invariant Sections, in the notice that says that the Document is released under this License. If a section does not fit the above definition of Secondary then it is not allowed to be designated as Invariant. The Document may contain zero Invariant Sections. If the Document does not identify any Invariant Sections then there are none.

The "Cover Texts" are certain short passages of text that are listed, as Front-Cover Texts or Back-Cover Texts, in the notice that says that the Document is released under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover Text may be at most 25 words.

A "Transparent" copy of the Document means a machine-readable copy, represented in a format whose specification is available to the general public, that is suitable for revising the document straightforwardly with generic text editors or (for images composed of pixels) generic paint programs or (for drawings) some widely available drawing editor, and that is suitable for input to text formatters or for automatic translation to a variety of formats suitable for input to text formatters. A copy made in an otherwise Transparent file format whose markup, or absence of markup, has been arranged to thwart or discourage subsequent modification by readers is not Transparent. An image format is not Transparent if used for any substantial amount of text. A copy that is not "Transparent" is called "Opaque".

Examples of suitable formats for Transparent copies include plain ASCII without markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly available DTD, and standard-conforming simple HTML, PostScript or PDF designed for human modification. Examples of transparent image formats include PNG, XCF and JPG. Opaque formats include proprietary formats that can be read and edited only by proprietary word processors, SGML or XML for which the DTD and/or processing tools are not generally available, and the machine-generated HTML, PostScript or PDF produced by some word processors for output purposes only.

The "Title Page" means, for a printed book, the title page itself, plus such following pages as are needed to hold, legibly, the material this License requires to appear in the title page. For works in formats which do not have any title page as such, "Title Page" means the text near the most prominent appearance of the work's title, preceding the beginning of the body of the text.

The "publisher" means any person or entity that distributes copies of the Document to the public.

A section "Entitled XYZ" means a named subunit of the Document whose title either is precisely XYZ or contains XYZ in parentheses following text that translates XYZ in another language. (Here XYZ stands for a specific section name mentioned below, such as "Acknowledgements", "Dedications", "Endorsements", or "History".) To "Preserve the Title" of such a section when you modify the Document means that it remains a section "Entitled XYZ" according to this definition.

The Document may include Warranty Disclaimers next to the notice which states that this License applies to the Document. These Warranty Disclaimers are considered to be included by reference in this License, but only as regards disclaiming warranties: any other implication that these Warranty Disclaimers may have is void and has no effect on the meaning of this License.

2. VERBATIM COPYING

You may copy and distribute the Document in any medium, either commercially or noncommercially, provided that this License, the copyright notices, and the license notice saying this License applies to the Document are reproduced in all copies, and that you add no other conditions whatsoever to those of this License. You may not use technical measures to obstruct or control the reading or further copying of the copies you make or distribute. However, you may accept compensation in exchange for copies. If you distribute a large enough number of copies you must also follow the conditions in section 3.

You may also lend copies, under the same conditions stated above, and you may publicly display copies.

3. COPYING IN QUANTITY

If you publish printed copies (or copies in media that commonly have printed covers) of the Document, numbering more than 100, and the Document's license notice requires Cover Texts, you must enclose the copies in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on the back cover. Both covers must also clearly and legibly identify you as the publisher of these copies. The front cover must present the full title with all words of the title equally prominent and visible. You may add other material on the covers in addition. Copying with changes limited to the covers, as long as they preserve the title of the Document and satisfy these conditions, can be treated as verbatim copying in other respects.

If the required texts for either cover are too voluminous to fit legibly, you should put the first ones listed (as many as fit reasonably) on the actual cover, and continue the rest onto adjacent pages.

If you publish or distribute Opaque copies of the Document numbering more than 100, you must either include a machine-readable Transparent copy along with each Opaque copy, or state in or with each Opaque copy a computer-network location from which the general network-using public has access to download using public-standard network protocols a complete Transparent copy of the Document, free of added material. If you use the latter option, you must take reasonably prudent steps, when you begin distribution of Opaque copies in quantity, to ensure that this Transparent copy will remain thus accessible at the stated location until at least one year after the last time you distribute an Opaque copy (directly or through your agents or retailers) of that edition to the public.

It is requested, but not required, that you contact the authors of the Document well before redistributing any large number of copies, to give them a chance to provide you with an updated version of the Document.

4. MODIFICATIONS

You may copy and distribute a Modified Version of the Document under the conditions of sections 2 and 3 above, provided that you release the Modified Version under precisely this License, with the Modified Version filling the role of the Document, thus licensing distribution and modification of the Modified Version to whoever possesses a copy of it. In addition, you must do these things in the Modified Version:

  • A. Use in the Title Page (and on the covers, if any) a title distinct from that of the Document, and from those of previous versions (which should, if there were any, be listed in the History section of the Document). You may use the same title as a previous version if the original publisher of that version gives permission.
  • B. List on the Title Page, as authors, one or more persons or entities responsible for authorship of the modifications in the Modified Version, together with at least five of the principal authors of the Document (all of its principal authors, if it has fewer than five), unless they release you from this requirement.
  • C. State on the Title page the name of the publisher of the Modified Version, as the publisher.
  • D. Preserve all the copyright notices of the Document.
  • E. Add an appropriate copyright notice for your modifications adjacent to the other copyright notices.
  • F. Include, immediately after the copyright notices, a license notice giving the public permission to use the Modified Version under the terms of this License, in the form shown in the Addendum below.
  • G. Preserve in that license notice the full lists of Invariant Sections and required Cover Texts given in the Document's license notice.
  • H. Include an unaltered copy of this License.
  • I. Preserve the section Entitled "History", Preserve its Title, and add to it an item stating at least the title, year, new authors, and publisher of the Modified Version as given on the Title Page. If there is no section Entitled "History" in the Document, create one stating the title, year, authors, and publisher of the Document as given on its Title Page, then add an item describing the Modified Version as stated in the previous sentence.
  • J. Preserve the network location, if any, given in the Document for public access to a Transparent copy of the Document, and likewise the network locations given in the Document for previous versions it was based on. These may be placed in the "History" section. You may omit a network location for a work that was published at least four years before the Document itself, or if the original publisher of the version it refers to gives permission.
  • K. For any section Entitled "Acknowledgements" or "Dedications", Preserve the Title of the section, and preserve in the section all the substance and tone of each of the contributor acknowledgements and/or dedications given therein.
  • L. Preserve all the Invariant Sections of the Document, unaltered in their text and in their titles. Section numbers or the equivalent are not considered part of the section titles.
  • M. Delete any section Entitled "Endorsements". Such a section may not be included in the Modified Version.
  • N. Do not retitle any existing section to be Entitled "Endorsements" or to conflict in title with any Invariant Section.
  • O. Preserve any Warranty Disclaimers.

If the Modified Version includes new front-matter sections or appendices that qualify as Secondary Sections and contain no material copied from the Document, you may at your option designate some or all of these sections as invariant. To do this, add their titles to the list of Invariant Sections in the Modified Version's license notice. These titles must be distinct from any other section titles.

You may add a section Entitled "Endorsements", provided it contains nothing but endorsements of your Modified Version by various parties—for example, statements of peer review or that the text has been approved by an organization as the authoritative definition of a standard.

You may add a passage of up to five words as a Front-Cover Text, and a passage of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover Text may be added by (or through arrangements made by) any one entity. If the Document already includes a cover text for the same cover, previously added by you or by arrangement made by the same entity you are acting on behalf of, you may not add another; but you may replace the old one, on explicit permission from the previous publisher that added the old one.

The author(s) and publisher(s) of the Document do not by this License give permission to use their names for publicity for or to assert or imply endorsement of any Modified Version.

5. COMBINING DOCUMENTS

You may combine the Document with other documents released under this License, under the terms defined in section 4 above for modified versions, provided that you include in the combination all of the Invariant Sections of all of the original documents, unmodified, and list them all as Invariant Sections of your combined work in its license notice, and that you preserve all their Warranty Disclaimers.

The combined work need only contain one copy of this License, and multiple identical Invariant Sections may be replaced with a single copy. If there are multiple Invariant Sections with the same name but different contents, make the title of each such section unique by adding at the end of it, in parentheses, the name of the original author or publisher of that section if known, or else a unique number. Make the same adjustment to the section titles in the list of Invariant Sections in the license notice of the combined work.

In the combination, you must combine any sections Entitled "History" in the various original documents, forming one section Entitled "History"; likewise combine any sections Entitled "Acknowledgements", and any sections Entitled "Dedications". You must delete all sections Entitled "Endorsements".

6. COLLECTIONS OF DOCUMENTS

You may make a collection consisting of the Document and other documents released under this License, and replace the individual copies of this License in the various documents with a single copy that is included in the collection, provided that you follow the rules of this License for verbatim copying of each of the documents in all other respects.

You may extract a single document from such a collection, and distribute it individually under this License, provided you insert a copy of this License into the extracted document, and follow this License in all other respects regarding verbatim copying of that document.

7. AGGREGATION WITH INDEPENDENT WORKS

A compilation of the Document or its derivatives with other separate and independent documents or works, in or on a volume of a storage or distribution medium, is called an "aggregate" if the copyright resulting from the compilation is not used to limit the legal rights of the compilation's users beyond what the individual works permit. When the Document is included in an aggregate, this License does not apply to the other works in the aggregate which are not themselves derivative works of the Document.

If the Cover Text requirement of section 3 is applicable to these copies of the Document, then if the Document is less than one half of the entire aggregate, the Document's Cover Texts may be placed on covers that bracket the Document within the aggregate, or the electronic equivalent of covers if the Document is in electronic form. Otherwise they must appear on printed covers that bracket the whole aggregate.

8. TRANSLATION

Translation is considered a kind of modification, so you may distribute translations of the Document under the terms of section 4. Replacing Invariant Sections with translations requires special permission from their copyright holders, but you may include translations of some or all Invariant Sections in addition to the original versions of these Invariant Sections. You may include a translation of this License, and all the license notices in the Document, and any Warranty Disclaimers, provided that you also include the original English version of this License and the original versions of those notices and disclaimers. In case of a disagreement between the translation and the original version of this License or a notice or disclaimer, the original version will prevail.

If a section in the Document is Entitled "Acknowledgements", "Dedications", or "History", the requirement (section 4) to Preserve its Title (section 1) will typically require changing the actual title.

9. TERMINATION

You may not copy, modify, sublicense, or distribute the Document except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, or distribute it is void, and will automatically terminate your rights under this License.

However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.

Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.

Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, receipt of a copy of some or all of the same material does not give you any rights to use it.

10. FUTURE REVISIONS OF THIS LICENSE

The Free Software Foundation may publish new, revised versions of the GNU Free Documentation License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. See https://www.gnu.org/licenses/.

Each version of the License is given a distinguishing version number. If the Document specifies that a particular numbered version of this License "or any later version" applies to it, you have the option of following the terms and conditions either of that specified version or of any later version that has been published (not as a draft) by the Free Software Foundation. If the Document does not specify a version number of this License, you may choose any version ever published (not as a draft) by the Free Software Foundation. If the Document specifies that a proxy can decide which future versions of this License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Document.

11. RELICENSING

"Massive Multiauthor Collaboration Site" (or "MMC Site") means any World Wide Web server that publishes copyrightable works and also provides prominent facilities for anybody to edit those works. A public wiki that anybody can edit is an example of such a server. A "Massive Multiauthor Collaboration" (or "MMC") contained in the site means any set of copyrightable works thus published on the MMC site.

"CC-BY-SA" means the Creative Commons Attribution-Share Alike 3.0 license published by Creative Commons Corporation, a not-for-profit corporation with a principal place of business in San Francisco, California, as well as future copyleft versions of that license published by that same organization.

"Incorporate" means to publish or republish a Document, in whole or in part, as part of another Document.

An MMC is "eligible for relicensing" if it is licensed under this License, and if all works that were first published under this License somewhere other than this MMC, and subsequently incorporated in whole or in part into the MMC, (1) had no cover texts or invariant sections, and (2) were thus incorporated prior to November 1, 2008.

The operator of an MMC Site may republish an MMC contained in the site under CC-BY-SA on the same site at any time before August 1, 2009, provided the MMC is eligible for relicensing.

ADDENDUM: How to use this License for your documents

To use this License in a document you have written, include a copy of the License in the document and put the following copyright and license notices just after the title page:

    Copyright (C)  YEAR  YOUR NAME.
    Permission is granted to copy, distribute and/or modify this document
    under the terms of the GNU Free Documentation License, Version 1.3
    or any later version published by the Free Software Foundation;
    with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
    A copy of the license is included in the section entitled "GNU
    Free Documentation License".

If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace the "with … Texts." line with this:

    with the Invariant Sections being LIST THEIR TITLES, with the
    Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST.

If you have Invariant Sections without Cover Texts, or some other combination of the three, merge those two alternatives to suit the situation.

If your document contains nontrivial examples of program code, we recommend releasing these examples in parallel under your choice of free software license, such as the GNU General Public License, to permit their use in free software.

Author: Zach Flynn

Created: 2021-11-18 Thu 08:42

Validate