SLOBIL
Zach Flynn
2022-11-19 Sat 12:52

SLOBIL

Table of Contents

1. Introduction

2. The Language

2.1. Lexical Elements

Slots are represented by a forward-slash (/) followed by any non-whitespace characters. Slot names are case-sensitive. /a and /A refer to different slots. I use lower case names for slots with multiple words separated by hyphens. Like this: /a-name-for-a-slot.

A literal number is a sequence of digits ([0-9]) that includes at most one decimal point represented by a period. It may also be preceded by a hyphen to indicate a negative number. A literal number contains no spaces.

A literal string is a sequence of characters where the first and last characters are double quotation marks. A literal string can contain the following escape sequences preceded by a backwards slash \.

  • \ outputs \.
  • t outputs tab
  • n outputs newline
  • r outputs carriage return
  • ' outputs a single quote character

A literal string outputs a double quotation mark when a single quotation mark is used '. A literal string can contain whitespace, including newlines.

A literal boolean value is a string of characters that is either True or False.

A literal nothing value is a string of characters that is Nothing.

A single apostrophe (') is a token that starts a comment which is ended by a new line.

The following characters are special tokens: [, (, {, ., }, ), ].

If a sequence of characters without any whitespace is (1) not a literal string, (2) not a literal number, (3) not a special token, (4) not a comment, (5) not a literal boolean, (6) not a literal nothing, and (7) does not begin with a forward slash, then it is considered a data reference. Some names that are valid for slots are not valid for data references. Data references refer to the data currently inserted in the slot with the same name.

SLOBIL programs are whitespaced-separated lists of the above tokens.

2.2. Data and Types

Programmers do not define types. The following are the data types: Integer, Real, String, Slot, Object, Instruction, Expression, Operation, File, Boolean, Nothing, and Task.

Integer's are numbers without decimal points. They are "big" and can be arbitrarily large in magnitude.

Real's are numbers with decimal points. They are double precision floating-points.

String's are UTF-8 encoded character strings. Internally, they are represented as UTF-32 strings for speed, but they output to UTF-8.

Slot's indicate the location of data and also can act as symbols. They are written by giving a name that begins with a forward-slash. They are represented as a hash of their name internally.

Object's can contain any number of slots and store data in these locations. Code is executed inside of objects and can refer to data located in the object's slots. Objects are represented internally as a hash table.

Instruction's are SLOBIL code objects that are executed only when the programmer explicitly requests their execution. They are the primary way programmers write functions, procedures, and methods. Instructions are created by enclosing code between the tokens ( and ).

Expression's are SLOBIL code objects that are executed whenever the programmer requests their value. Expressions are created by enclosing code between the tokens { and }.

Operation's are code objects that take a series of positional arguments. The built-in operations are written in C, but users can also define operations.

File's are stream objects used for writing to external files.

Boolean's are either True or False.

Nothing is the type for the literal nothing value Nothing. Nothing else has this type.

Task's are processes running on the computer. When the program starts, it is in the main tasks. Additional tasks can be created for multi-threaded programs.

File, Task, and Object data are passed by reference. All others are by value.

2.3. Statements and Objects

A statement is a list of whitespace-separated tokens ended by a .. Naturally, the tokens included in the list cannot include ..

... TOKEN TOKEN ... TOKEN .

Statements happen inside objects. The object a statement is executed in determines what data is returned by a data reference. Data references look for a value in the relevant slot in the current object, if none is found, then they look for an object in the slot in the object containing the current object and so on.

Statements are executed effectively inside temporary objects. Arguments in a statement generally determine the values located at slots in the temporary object.

Statements begin with an operation. There are a few exceptions to this rule where the operation is automatically-selected based on the data type used for the first argument. This is for convenience. The entire language can be accessed with statements that begin with an operation.

The obligatory first statement in SLOBIL:

... print "Hello, world!" .

2.3.1. Creating Objects

Objects are created by using the object operation, followed by an alternating sequence of slots and data elements.

... object /number 10 /string "Hello!" /real 3.2 .
ans = an Object with:
number of type Integer, value: 10
string of type String, value: "Hello!"
real of type Real, value: 3.200000

The object operation sets the /ans slot to the relevant object.

2.3.2. The /ans Slot

Each Object has a special slot called the /ans slot. Many built-in operations put data in the /ans slot. It is the standard place to return values from an instruction, operation, or expression. For example,

... add 4 5 .
ans = 9
... print ans .
9

2.3.3. Substatements

Substatements are statements included as an element of another statement. They are surrounded by square brackets ([]) and executed in the order they appear in the statement prior to the statement itself being evaluated. When the outer statement is evaluated the substatements are replaced by the value each substatement set the /ans slot to after it was executed.

For example,

... object /number 10 /string "Hello!" /real 3.2
/object [ object /x 3 /y 2 ] .
ans = an Object with:
number of type Integer, value: 10
string of type String, value: "Hello!"
real of type Real, value: 3.200000
object of type Object, value: an Object with:
x of type Integer, value: 3
y of type Integer, value: 2
... add 5 [ add 4 3 ] .
ans = 12

Substatements do not need to include a . to end the last statement in the substatement. They can, but they do not have to. Substatements can include multiple statements.

2.3.4. Inserting Data Into Slots

Slots determine the location of data. The set operation inserts data into a slot, like so:

... set /number 10 .
... print number .
10

The set operation takes the slot to insert to and the data to insert as arguments.

The second line in the above example, print number ., demonstrates how to access the data located at a slot. Simply omit the forward slash to get the data.

Because this is a common procedure in SLOBIL, if the first element of a statement is a slot, then SLOBIL assumes you want to call set. For example, the above could have been written as:

... /number 10 .

set has an optional third argument: the object whose slot we should insert the data into. If the third argument is omitted, then the value is inserted into the current object's slot.

... /rectangle [ object /length 10 /width 5 ] .
... print rectangle .
an Object with:
length of type Integer, value: 10
width of type Integer, value: 5
... /width 4 rectangle .
... print rectangle .
an Object with:
length of type Integer, value: 10
width of type Integer, value: 4

The move operation moves data between different slots without copying the data.

... /x "Hello." .
... print x .
Hello.
... move /x /y .
... print y .
Hello.
... print x .
Error: Value at slot /x not found.

The delete operation removes data from the slot.

... /x "Hello." .
... print x .
Hello
... delete /x .
... print x .
Error: Value at slot /x not found.

2.3.5. Instructions

Instructions are a data type that contains code to execute. They are created by enclosing a series of statements in parenthesis. The last statement in the parenthesis does not have to end with a period.

For example:

... /add-one ( add t 1 ) .
... print add-one .
(  add t 1  )

The call operation calls an instruction in a temporary object determined by alternating slots and data values similar to the call to an object. In effect, instructions are called with named arguments.

... call add-one /t 4 .
ans = 5

When instructions execute, whatever data they set the /ans slot to in the temporary object in which they execute is sent back to the object from which they are called. No other slots they assign are modified in the current object.

... /circle-area
(
  /pi 3.1459 .
  /r2 [ power r 2 ] .
  mul r2 pi .
) .
... call circle-area /r 2
ans = 12.583600
... print ans.
12.583600
... print pi .
Error: Value at slot /pi not found.
-> print pi .

Calling instructions is a common thing to do. So, if the first element of a statement is an instruction, SLOBIL assumes the programmer wants to call it.

... circle-area /r 2 .
ans = 12.583600

Instructions are just code, they do not carry an environment around with them. When the programmer call's an instruction, the programmer determines the environment the code is executed in. Instructions do not have a fixed set of arguments but allow the caller of the code to manipulate the environment in which they are called arbitrarily. For example:

... /add-one ( add t 1 ) .
... add-one /t 5 .
ans = 6
... add-one /t 5 /add sub .
ans = 4

Any non-literal in an instruction can be changed by adjusting the environment in which the code is executed.

2.3.6. Getting data

The get operation sets the /ans slot to the data located at a certain slot.

... /x "Hello." .
... /y [ object /test "Goodbye." ] .
... get /x .
ans = "Hello."
... get /test .
... get /test y .
ans = "Goodbye."

The second argument to get is the object to get the data from. If it is omitted, the data is grabbed from the current object. There is also a shorthand notation for getting data from an object. The notation object:slot returns the data located at the slot within the object. The notation can also be nested like object:slot1:slot2 where another object is located at object:slot1.

... print y:/test .
Goodbye

One limitation to the colon notation is that it cannot be used with substatements.

... ' Will give an error
... print y:[ answer /test ] .

The exist operation tests whether data exists at a given slot in an object. Like get, it has an optional second argument to give the object. If the argument is omitted, it searches the current object. It sets the /ans slot to True if data exists at the slot and False if the data does not.

... exist /new-slot .
ans = False.
... /new-slot 0 .
... exist /new-slot .
ans = True.

2.3.7. Control flow operations

The if operation sets the /ans slot to its second argument if its first argument is True and to its third argument if its first argument is False.

... if True "Hello." "Goodbye." .
ans = "Hello."
... if False "Hello." "Goodbye." .
ans = "Goodbye."

The if operation can be used for control flow by using Expressions. Expressions are created by enclosing statements in {}. The code in an expression is executed when its value is accessed.

... /x 10 .
... if [ gt x 3 ] { print "x is greater than 3." } { print "x is less than three." } .
x is greater than 3.
... /x 1 .
... if [ gt x 3 ] { print "x is greater than 3." } { print "x is less than three." } .
x is less than three.

Expressions are like substatements in that they evaluate to what they set the /ans slot to. Where they differ is that substatements are executed before the main statement is evaluated. Expressions are executed only when they are needed. Expressions are necessary for the if operation to function as a control flow operation. If we wrote the above statements using substatements, all the statements would be evaluated regardless of the truth of the first argument.

... if [ gt x 3 ] [ print "x is greater than 3." . answer 0 . ] [ print "x is less than three." . answer 0 . ] .
x is greater than 3.
x is less than three.
ans = 0

The answer operation sets the /ans slot to a value. To demonstrate the above, I had to add it because otherwise the second and third argument to if would appear to be missing to the code because substatements evaluate to what they set the /ans slot to before the statement processes.

The while operation is a looping operation. It takes two instructions for its argument. It calls the first instruction without any arguments. If the instruction sets the /ans slot to True, it then calls the second instruction without any arguments, and so on. while calls both instructions in the current object so code in the instructions can modify the slots in the current object.

... /i 1 .
... while ( lt-eq i 10 )
(
  print i .
  incr i .
) .
1
2
3
4
5
6
7
8
9
10
ans = False.
... print i .
10

The repeat operation executes an instruction a fixed number of times in the current object. For example, the same loop could have been written as:

... /i 1
... repeat 10 ( print i . incr i . ) .
1
2
3
4
5
6
7
8
9
10

The repeat loop is faster than the while loop when it is applicable because it does not need to check a condition.

2.3.8. Prototyping

SLOBIL is a prototyped-based object language. New objects can inherit from other objects using the new operation. When changes are made to the parent object, it propogates to all the children. The new operation also adds to (or replaces) the slots included from their parent object in an analogous way to the object operation.

... /+rectangle+ [ object /area ( mul l w ) ] .
... /rect1 [ new +rectangle+ /l 5 /w 4 ] .
... print rect1:/area .
(  mul l w  )
... set /d 2 +rectangle+ .
... set /area ( mul l w d ) +rectangle+ .
... print rect1:/area
(  mul l w d )

The in operation executes code inside a given object. It can be used to do what other languages call "method calls".

... in rect1 rect1:/area .
ans = 40

Without using in, calling rect1:/area will not reference the values at the /l and /w slot in the object at /rect1.

... rect1:/area .
Error: Value at slot /l not found.

Calling instructions within an object is a common thing to do. So SLOBIL provides a convenience syntax. If the first element of a statement is an object, it calls the instruction at the slot provided in the first argument from within the object.

... rect1 /area .
ans = 40

The link between objects created by new is a link between the objects themselves, not the slots at which the objects are inserted. If the parent object is moved, deleted, or the slot is overwritten with a new object, it will not change the children because the object is simply removed from the slot it was located in originally, the object itself has not changed.

... /+rectangle+ [ object /area ( mul w l ) ] .
... /rect1 [ new +rectangle+ /l 5 /w 4 ] .
... rect1 /area .
ans = 20
... move /+rectangle+ /x .
... rect1 /area .
ans = 20
... /+rectangle+ [ object /area ( add l w ) ] .
... rect1 /area .
ans = 20
... delete /x .
... rect1 /area .
ans = 20

2.3.9. Applying Instructions To Data In An Object

The do operation applies an operation to each slot in a set of objects. For example:

... do add [ object /x 2 /y 3 /z 4 ] [ object /x 5 /y 2 ] .
ans = an Object with:
x of type Integer, value: 7
y of type Integer, value: 5

For each slot that has data in all the objects passed to do as arguments, do calls the operation in its first argument using the data from the given slot in each object as positional arguments, in the order the objects are given to do.

do takes operations as arguments, but programmers write instructions, not operations. Instructions can be converted to operations by equipping their code with a fixed set of positional arguments using the op operation.

... do [ op ( div [ sub y x ] x ) /x /y ]
[ object /x 2.0 /y 3.0 /z 4.0 ]
[ object /x 5.0 /y 2.0 ] .
ans = an Object with:
x of type Real, value: 1.500000
y of type Real, value: -0.333333

The op operation converts the instruction given into an operation by setting the slots in the temporary environment in which an instruction is called to the given positional arguments, e.g. in the above, the first argument will be inserted into the slot /x and the second argument to the slot /y.

2.3.10. Objects As Arrays

There is no array type in SLOBIL. Objects are used as array as well. Several operations make use of a slot-naming convention to treat objects like arrays. Slots that match the pattern /name1, /name2, …, /nameN can be treated like an array by operations.

The next operation sets the /ans slot to the next slot given the current slot.

... next /c1 .
ans = /c2

The previous operation sets the /ans slot to the previous slot given the current slot.

... previous /c10 .
ans = /c9

The last operation sets the /ans slot to the last slot in an object that begins with the given prefix.

... /array [ list 1 2 3 ] .
... print array .
an Object with:
t1 of type Integer, value: 1
t2 of type Integer, value: 2
t3 of type Integer, value: 3
... last array /t .
ans = /t3

This example demonstrates the list operation as well which creates a list-like object with data located at slots /t1, /t2, …, /tN.

The collapse operation can be used to summarize a list.

... /array [ list 3 -1 4 ] .
... collapse add array /t .
ans = 6

collapse applies an operation that takes two arguments to each element of the list consecutively. So, in the above example, first add 3 -1 is computed. Then, add ans 4 is computed, and so on.

collapse and last both operate on the /t prefix by default so the last argument can be omitted if that is the prefix desired,

2.3.11. Saving and Loading Objects

Objects can be saved with the save operation. save saves the current object to the given filepath.

... /test [ object /x 1 /y "hello" /z True ] .
... in test ( save "test.dslob" ) .

The build operation saves objects in a standard location, ~/.dslobs.

... build "test" test .

The above statement saves the object test to ~/.dslobs/test.dslob.

Similarly, the load operation loads a given filepath into the current object, while the use operation imports an object from the standard location for save files, ~/.dslobs.

... load "test.dslob" .
... use "test" .

2.3.12. REPL Settings

The REPL itself has a few settings that can be configured using the interpreter operation. The below example demonstrates how to change the various values from their defaults.

... interpreter /print-ans True .
... interpreter /auto-rehash False .
... interpreter /print-errors False .

The print-ans setting indicates whether to print what the /ans slot is after running each command. The default is not to do so.

The auto-rehash setting indicates whether objects — which, internally, are hash tables — should be automatically rehashed as they grow in size or whether the rehash operation should be used explicitly. The default is to rehash automatically.

The print-errors setting indicates whether to print the trace of the error out when an error is encountered. The default is to do so.

2.4. Multi-threaded Programs

Tasks model processes. They enable multi-threaded programming.

The task operation creates a task. The task operation takes three arguments. The first gives the slot 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 object defining the environment in which the task will run.

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

The run-task operation starts a task. It 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 can be executed multiple times. The state of the task will not revert to its original state 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 SLOBIL thread.
State A Object that contains the current state of the task. The object being modified by the Task's body.
Queue A Object that contains data queued to pass from the main thread to the Task's thread or vice-versa.

A Task's state object is entirely separate from the current object. 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 object. They can do this with one of three operations: accept, accept-or, and select.

The accept operation takes a slot as an argument and waits for data to be available at that slot 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 slot in the Task's state object 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. SLOBIL 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 object into the queue of another Task. Its arguments are a slot to set in the queue object, 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 slot in the queue, if it does, it sets the /ans slot to the result. If not, it executes its second argument, an instruction, in its state object. 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 slots. select takes paired slots and instructions as its arguments. It waits for data to appear in its queue at any of the slots listed. The first slot to receive data is "selected". The instruction associated with the slot is executed in the slot's state object after setting the /ans slot to the value of the data located at the slot 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. Example Programs

3.1. Accounting System

SLOBIL programs can use SLOBIL itself as their means of interacting with the user because the language is "command-like". This section describes a simple double-entry accounting program written in SLOBIL.

Interactive programs are usually created as objects so they can be easily loaded into SLOBIL with both state variables and instructions. So we start by defining the program object which will contain a list of accounts.

/accounting
  [ object
      /account-list [ object ]
  ] .

Next, we define a generic account object and add it to accounting. I use +name+ as a naming convention for base objects that will be used as prototypes for subsequent objects.

/+account+
[ object
    /add-entry
    (
      last entries /entry .
      next ans .
      set ans entry entries .

      incr balance entry:/amount .
    )
    /summary
    (
      print [ combine name ": " balance ] .
    )
    /print-entry
    (
      print [ combine
                entry:/desc ": "
                [
                  to-string entry:/amount
                ]
            ] .
    )
    /print-entries
    (
      do [ op print-entry /entry ]
         entries ...
    )
]
accounting .

To suppress printing out the /ans slot, you can end a statement with three periods ....

Now, we need an instruction for creating a new account. We will have the user specify the account as a slot.

/new-account
(
  set name
      [
        new +account+ /name [ to-string name ] /balance balance
            /entries [ object ] 
      ] account-list .
)
accounting .

This instruction might not seem to do much beyond what the new operation does. One advantage of defining this instruction is that it will give an intelligible error if the user does not specify a name for the account and an opening balance. The new operation doesn't expect any particular slots to be set, but the new-account instruction does.

We next need an instruction to transfer money between two accounts. Let's define one.

/transfer
  (
    /to-entry [ object /desc desc /amount amount ] .
    /from-entry [ object /desc desc /amount [ mul -1 amount ] ] .

    if [ exist from-account account-list ]
       {
         account-list:from-account /add-entry /entry from-entry .
       }
       {
         if [ exist to-account account-list ]
            {
              account-list:to-account /add-entry /entry to-entry .
            } .
       } .
  )
  accounting .

Lastly, let's add some instructions to summarize our accounts and print out entries in a given account.

/summary
  (
    do [ op ( account /summary ) /account  ]
       account-list ...
  )
  accounting .

/entries
  (
    account-list:account /print-entries .
  ) 
  accounting .

Let's see the program in action.

  ... accounting /new-account /name /Checking /balance 1000.50 .
  ... accounting /new-account /name /Savings /balance 20000.00 .
  ... accounting /transfer
  /desc "Saving Money"
  /amount 100
  /to-account /Savings
  /from-account /Checking .
...  accounting /summary .
Savings: 20100.000000
Checking: 900.500000
... accounting /entries /account /Checking .
Saving Money: -100

The user shouldn't have to type accounting every time. Temporarily, in the session, we can import it, but to package it for users, it is better to use the build operation to save the object to disk.

... build "accounting" accounting .

Now, whenever we start a SLOBIL session, we can simply type:

... use "accounting" .
... new-account /name /Checking /balance 1000.50 .
... summary .

3.2. A Time-Keeping Program

When I worked for a company that billed clients by the hour, I had to keep track of my time. I used this program to do it until I left the job. With two and a half years of use, it is certainly the most used SLOBIL program.

' Initial state

/current-thing "Nothing" .
/last-clock -1 .
/record [ object ] .
/running False .
/current-file Nothing .

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

/init
  [ op
      (
        up set /current-file file .
        please ( load file record )
               (
                 up set /record [ object ] .
                 up set /thing-list [ object ] record .
                 print "Starting new file." .
               ) .
      ) /file ] .


' Save record of work in SLOBIL format
/save-hours
  (
    in record ( save current-file ) .
  ) .

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

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

' Stop clock for current thing with a description of task.
/stop
  (
    if running
       {
         last record /r .
         next ans .
         set ans
             [
               object
                 /thing current-thing
                 /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
/day-of-week-map
  [
    list
      "Sunday"
      "Monday"
      "Tuesday"
      "Wednesday"
      "Thursday"
      "Friday"
      "Saturday"
      "Sunday" 
  ] .

' Convert to hours
/make-hours-day-time
  [ op
      (
        do [ op ( div t 1000.0 60 60 ) /t ] structure .
      )
      /structure ] .

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

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

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

                     if [ exist thing final ]
                        {
                          set thing [ add final:thing elapsed ]
                              final .
                        }
                        {
                          set thing elapsed final .
                        } .
                   } .
                /i [ next i ] .
              ) .

        make-hours-day-time final .
      ) /year /month /day ] .

' Add thing to thing list
/add-thing-to-list
  [ op (
        if [ not [ exist /thing-list record ] ]
           {
             set /thing-list [ object ] record .
           } .
        last record:/thing-list /c .
        next ans .
        set ans thing record:/thing-list .
      )
       /thing ] .


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

' Print status
/status
  (
    print [ combine "Current thing: " current-thing ] .
    if running
       {
         print "Running a task." .
       }
       {
         print "Not running." .
       } .
  ) .

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

            /i [ next i ] .
          ) .

    ' Update thing in records

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

' Rewind X minutes from start
/start-back
  [ op
      (
        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
/rewind-last
  [ op (
        if running
           {
             print "Currently running." .
           }
           {
             /end [ last record /r ] .
             /stop-time
               [
                 sub record:end:/stop-time [ mul x 60 1000 ]
               ]
               record:end .
           } .
      )
       /x ] .

4. Example Instructions

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 [ slot-eq j end ]
                 {
                   set /cont False .
                 } .

              set /j [ next j ] .
            ) .

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

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

           if [ not [ slot-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 slot not found in input." .
         } .

      if [ not [ exist end input ] ]
         {
           error "Ending slot 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 object 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 object 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
/slobed [ object . ] .
in slobed
   (
     /buffer [ list . ] . 
     /cur-line /t1 . 
     /n ( up set /cur-line [ next cur-line . ] . ) .
     /p ( up set /cur-line [ previous cur-line . ] . ) .
     /i
       [ op (
         /arbed-str s buffer .
         in buffer
            (
              set cur-line arbed-str .
            ) .
       )
       /s ] . 
     /o
       (
         print [ get cur-line buffer . ] .
       ) .
   ) .

Example of use:

... import slobed .
... 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 [ op ( power t 2 ) /t ] .
... deriv /f f /x 1.0
ans = 2.0

4.4. Compute Factorial

' compute a factorial using while
set /factorial
    [ op (
      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 [ op -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 [ op ( 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
    [ op (
      do mul v w .
      collapse add ans  .
    ) /v /w ] .

' matrix-multiply A B computes A^T B
set /matrix-multiply
    [ op (
      set /i /t1 .
      set /out [ list ] .
      while ( exist i B )
            (
              do [ op ( 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
    [ op (
      set /i /t1 .
      set /out [ list ] .
      while ( exist i A:/t1 )
            (
              do [ op ( 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 object with:
t1 of type Object, value: a object with:
t1 of type Integer, value: 1
t2 of type Integer, value: 3
t2 of type Object, value: a object with:
t1 of type Integer, value: 2
t2 of type Integer, value: 4
... matrix-multiply A B .
ans = a object with:
t1 of type Object, value: a object with:
t1 of type Integer, value: 10
t2 of type Integer, value: 26
t2 of type Object, value: a object 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 object 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 SLOBIL is to write a more user-friendly (and documented) libslobil for adding extensions, but it is currently possible to write SLOBIL operations in C, using SLOBIL'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 slobil.h is located or just "slobil.h" if it is on the path */
#include "../src/slobil.h"
#include <math.h>

void
log_plus_one (arg a, object* 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 libslobil.

#!/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 -lslobil

The code then can be loaded into SLOBIL 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 SLOBIL operation will be.

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

... log1 1 .
ans = 0.693147

4.9. Concurrent Programming

SLOBIL 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 . ] .
             ) .
     )
     [ object . ] .

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 .
                 ) .
             ) .
     )
     [ object . ] .

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 .
     )
     [ object ] .

run-task t1 .
queue /instruction ( add x y ) t1 .
queue /context [ object /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. Objects

5.1.1. Short description

A object contains data located at slots. Data can be retrieved from and inserted into objects. Objects also defined the scope of SLOBIL code because code is executed inside objects. So the values of variables depend on which object you are executing the code. This allows the user to flexibly manipulate scope.

5.1.2. Implementation details

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

5.1.3. Object operations

  1. Creation
    • object SLOT1 VALUE1 SLOT2 VALUE2 ... — sets the /ans slot to a object with VALUE1 located at SLOT1 and so on.
    • list VALUE1 VALUE2 ... — set the /ans slot to a Object with VALUE1 at Slot /t1, VALUE2 at Slot /t2, and so on.
    • range INTEGER1 INTEGER2 |INTEGER3| — set the /ans slot to a Object 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 slots
    • set SLOT VALUE |OBJECT| — sets the value at SLOT to VALUE in the object OBJECT. If the OBJECT argument is omitted, then it will set the slot in the current object.
    • move SLOT1 SLOT2 — move the value located at SLOT1 to SLOT2. Does not copy the data so it is an efficient way to move data.
    • delete SLOT — delete the value at SLOT in the current object.
    • free SLOT — delete the value at SLOT in the current object. 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 Object
    • get SLOT |OBJECT| — sets the /ans slot to the value located at SLOT in OBJECT. If the OBJECT argument is not specified, get from the current object.
    • exist SLOT |OBJECT| — set the /ans slot to True if a value exists at the SLOT in OBJECT. If the OBJECT argument is omitted, check in the current object.
    • import OBJECT — set the Slots in the current Object to hold the same values that they hold in OBJECT.
    • filter OBJECT OPERATION — set the /ans slot to a Object containing all the elements in OBJECT such that the OPERATION applied to the element of the Object sets the /ans slot to True.
  4. Apply Operations to elements of a Object
    • do OPERATION OBJECT1 OBJECT2 ... OBJECTN — execute OPERATION which takes its arguments (1…N) from each element in OBJECT1, …, OBJECTN. Sets the /ans slot to a Object which contains whatever the OPERATION evaluates to at the corresponding Slots. For example, do add [ list 1 2 3 . ] [ list 4 5 6 . ] sets the /ans slot to a Object with elements (5,7,9) at slots (/t1,/t2,/t3). do will only return results at Slots that exist in all Objects. So, for example, do add [ list 1 2 3 . ] [ list 4 5 . ] sets the /ans Slot to list 5 7.
    • collapse OPERATION OBJECT |SLOT| — sets the /ans slot to the value of applying OPERATION to the value in the OBJECT at Slot /SLOT1 and /SLOT2 and then again to the result of that and to the value of /SLOT3 and so on. For example, collapse add [ list 1 2 3 . ] /t sets the /ans slot to 6. If the SLOT argument is omitted, it is assumed to be /t.
  5. Execute code in a object
    • in OBJECT INSTRUCTION — call INSTRUCTION in OBJECT.
    • up STATEMENT — execute STATEMENT in the object above the current one after resolving the values of the arguments of the statement in the current object. 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 object
    • is-object VALUE — sets the /ans Slot to True if VALUE is a Object and to False otherwise.
  7. Technical
    • rehash OBJECT — manually rehash the object's underlying hash table.

5.2. Reals and Integers

5.2.1. Short description

Real and Integer are distinct types in SLOBIL. 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 SLOBIL 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 slot to the result.
    • mul NUMBER1 NUMBER2 ... — multiplies all the numbers together and sets the /ans slot 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 slot 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 slot 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 slot to True if NUMBER1 is greater than NUMBER2 and to False otherwise.
    • lt NUMBER1 NUMBER2 — set the /ans slot to True if NUMBER1 is less than NUMBER2 and to False otherwise.
    • eq NUMBER1 NUMBER2 — set the /ans slot to True if NUMBER1 is equal to NUMBER2 and to False otherwise.
    • lt-eq NUMBER1 NUMBER2 — set the /ans slot to True if NUMBER1 is less than or equal to NUMBER2 and to False otherwise.
    • gt-eq NUMBER1 NUMBER2 — set the /ans slot to True if NUMBER1 is greater than or equal to NUMBER2 and to False otherwise.
  3. Conversion operations
    • to-number STRING|SLOT — if the argument is a String, try to convert to a number and set the /ans Slot to the result. If the argument is a Slot and ends in a number, set the /ans Slot to the result.
    • to-real INTEGER — set the /ans Slot to a Real representing the INTEGER.
  4. Test if type operations
    • is-integer VALUE — sets the /ans Slot to True if the Value is an Integer and to False otherwise.
    • is-real VALUE — sets the /ans Slot to True if the Value is a Real and to False otherwise.
  5. Common mathematical operations
    • log NUMBER — set the /ans Slot to the natural logarithm of NUMBER.
    • exp NUMBER — set the /ans Slot to the NUMBER power of the natural base.
    • power NUMBER1 NUMBER2 — set the /ans Slot to NUMBER1 raised to the NUMBER2 power.
    • floor REAL1 — set the /ans Slot to the greatest Integer less than REAL1.
    • ceiling REAL1 — set the /ans Slot to smallest Integer greater than REAL1.

5.3. Strings

5.3.1. Short description

Strings in SLOBIL are text. A string literal is enclosed in quotation marks. SLOBIL 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. SLOBIL 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 Slot to the subset of STRING where the characters included are determined by INTEGER1 and INTEGER2. Strings are 1-indexed in SLOBIL 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 slot to the last character in STRING.
    • match STRING1 STRING2 |INTEGER| — find occurences of regular expression STRING1 in String STRING2. Sets the /ans Slot to a Object with all submatches located at the /tN objects. 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 Slot 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 slot to the number of characters in STRING.
  3. String comparison
    • string-eq STRING1 STRING2 — sets the /ans Slot to True if the two strings are equal and to False otherwise.
    • string-lt STRING1 STRING2 — sets the /ans Slot 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 Slot 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 slot to the concatenation of the two Strings so that the resulting String is "STRING1STRING2".
  5. Convert to string
    • to-string INTEGER|REAL|SLOT |INTEGER2| — sets the /ans Slot to a String representing the INTEGER or REAL or SLOT 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 Slot to True if the VALUE is a String and to False otherwise.
  7. Strings from User Input
    • input SLOT — reads a line of text the user enters and sets SLOT to that value (always a String).

5.4. Slots

5.4.1. Short description

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

5.4.2. Implementation details

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

5.4.3. Slot operations

  1. Operations on "list" slots
    • next SLOT — if the SLOT ends in a number, return the Slot with that number incremented by 1.
    • previous SLOT — if the SLOT ends in a number, return the Slot with that number minus 1. If that would cause the number to be less than 1 set the /ans Slot to the "first" slot with that prefix so that previous /t1 . sets /ans to /t1.
    • last OBJECT SLOT — return the last Slot in the OBJECT that starts with SLOT. So that if you had slots /x0, /x1, and /x2 in the Object, last OBJECT /x would return /x2.
  2. Convert to slot
    • to-slot STRING|INTEGER — sets the /ans slot to a Slot named STRING or to /tINTEGER.
  3. Slot comparison
    • slot-eq SLOT1 SLOT2 — sets the /ans Slot to True if the two Slots are the same and to False otherwise.
  4. Test if a slot
    • is-slot VALUE — sets the /ans Slot to True if the VALUE is a Slot 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 slot 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 Slot to True if the VALUE is a Boolean and to False otherwise.
  • and BOOLEAN1 BOOLEAN2 — sets the /ans Slot to True if BOOLEAN1 and BOOLEAN2 are True and to False otherwise.
  • or BOOLEAN1 BOOLEAN2 — sets the /ans Slot to True if either BOOLEAN1 or BOOLEAN2 are True and to False otherwise.
  • not BOOLEAN — sets the /ans Slot 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 objects.

5.6.2. Implementation details

Instructions are internally "compiled" code. They are then supplied with an environment (a object) 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 slots to lookup in the environment.

5.6.3. Instruction operations

  • call INSTRUCTION SLOT1 VALUE1 SLOT2 VALUE2 ... — executes INSTRUCTION in a Object with SLOT1 assigned to VALUE1 and so on.
  • is-instruction VALUE — sets the /ans Slot to True if the VALUE is an Instruction and to False otherwise.
  • op INSTRUCTION SLOT1 ... SLOTN — set the /ans slot to an operation that calls INSTRUCTION after binding SLOT1 to the first argument, and so on.
  • code INSTRUCTION — sets the /ans slot to a String containing the code for the INSTRUCTION.
  • is-operation VALUE — sets the /ans Slot to True if the VALUE is an Operation and to False otherwise.
  • set-help INSTRUCTION STRING — sets the help for INSTRUCTION to STRING.
  • help INSTRUCTION — Print the help string.

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 Slot to True if the VALUE is a File and to False otherwise.
  • open-file STRING1 |STRING2| — sets the /ans Slot 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 slot to the result (an Integer).
  • read-char FILE — read a single character from a File object and set the /ans Slot to that character (a String).
  • read-line FILE — sets the /ans Slot 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 SLOT — if a File object is located at SLOT, close the File and remove its reference from the SLOT.

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 slot 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 object that contains the state of the Task. The Body is run inside the State object. It is persistent across Task runs.
  3. A Queue, a object 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 SLOT INSTRUCTION |OBJECT| — Sets SLOT to contain a new Task with its Body given by the INSTRUCTION and the initial state being set to OBJECT. If no OBJECT argument is given, the initial state is a object containing only the basic operations (the object always contains the basic operations even if they are not in the initial object 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 SLOT DATA |TASK| — Sets the SLOT 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 SLOBIL Task.
  • accept SLOT |TASK| — Waits until SLOT has a value in TASK's Queue. Once it finds a value, set the /ans slot to that value. If TASK is omitted, wait for the current Task's Queue to be set.
  • select SLOT1 INSTRUCTION1 SLOT2 INSTRUCTION2 ... SLOTN INSTRUCTIONN — Waits for the Queue in the current task to contain data at any of the Slots (it will prefer earlier Slots if data is available at both). Once it receives data, it sets the /ans slot to the data and executes the Instruction in the current object.
  • accept-or SLOT INSTRUCTION |TASK| — Checks if SLOT is set in TASK's Queue. If it is, the /ans slot 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 slot to VALUE1, if it is False, sets the /ans slot to VALUE2. If VALUE2 is omitted, do nothing if the first argument is False.
  • while INSTRUCTION1 INSTRUCTION2 — Call INSTRUCTION1 in the current Object. If it sets its /ans slot 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 slot

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

5.11.2. Exit SLOBIL

  • exit — exit SLOBIL.

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 SLOBIL code in the file named by STRING.
  • save FILENAME — save the contents of the current object 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 Slot 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 Slot to a String giving the version of SLOBIL.

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 Slot to a String giving the current directory.

5.12.2. Time

  • clock — set the /ans slot 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 object 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 OBJECT — make a clock value in milliseconds from a object describing the time (like the object returned by make-time)

5.12.3. Random numbers

  • rand |INTEGER| — set the /ans Slot 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 object in ~/.dslobs/ with the extension .dslob into the current object. The file being loaded is: ~/.dslobs/STRING.dslob.
  • build STRING OBJECT — saves contents of object to ~/.dslobs/STRING.dslob so that it can be loaded as a package.
  • link STRING1 STRING2 STRING3 — loads shared object file STRING1 and its function named STRING2 into SLOBIL operation named STRING3.

5.14. Interpreter options

5.14.1. Operation

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

5.14.2. List of options

  • /print-ans — if True, then print the value of the /ans Slot at the end of commands, if False (default) or any other non-Boolean value, suppress printing.
  • /auto-rehash — if True (default) or any non-Boolean value, then rehash objects 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 Objects

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 SLOBIL in any case; it has no permanent information. All other data types are written to disk.

This section will describe the storage format for SLOBIL 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 SLOBIL.

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: 2022-11-19 Sat 12:52

Validate