bldc/doc/lbmref.md

28 KiB

LispBM language reference

Arithmetic

+

Adds up an aribtrary number of values. The form of a + expression is (+ expr1 ... exprN)

Example adding up two numbers. The result is 3.

(+ 1 2)

When adding up values of different types values are converted.

(+ 1i28 3.14)

The example above evaluates to float value 4.14.
You can add up multiple values.

(+ 1 2 3 4 5 6 7 8 9 10)

The example above results in the value 55.


-

Subtract an arbitrary number of values from a value. The form of a - expression is (- expr1 ... exprN)

Example subtracting 3 from 5.

(- 5 3)

*

Multiplying an arbitrary number of values. The form of a * expression is (* expr1 ... exprN)

Example 2pi.

(* 2 3.14)

/

Division. The form of a / expression is (/ expr1 ... exprN).

Divide 128 by 2

(/ 128 2)

The following example evaluates to 1.

(/ 128 2 2 2 2 2 2 2)

mod

Modulo operation. The form of a mod expression is (mod expr1 ... exprN).

Compute 5 % 3, evaluates to 2.

(mod 5 3)

Comparisons

eq

Compare expressions for equality. The eq implements structural equality. The form of an eq expression is (eq expr1 ... exprN)

Compare the result of (+ 1 2) with 3. The result of this comparison is t.

(eq (+ 1 2) 3)

Multiple expressions can be checked at once. The examples below evaluates to t

(eq 1 1 1 1)

(eq (+ 3 4) (+ 2 5) (+ 1 6))

The following examples evaluate to nil representing false.

(eq 1 1 1 1 2)

(eq (+ 1 2) (+ 0 2) (+ -1 2))

The = comparison can be used on tree shaped data. The following expression evaluates to t.

(eq '(1 (1 2)) '(1 (1 2)))

=

The = operation can only be used on numerical arguments. If you know you are comparing numbers, it will be more efficient to use =.

An important difference between eq and = is that equals compare the numerical values of the arguments. A 3 is a 3 independent of them being different types. eq on the other hand compares the representations of the arguments exactly and they must match in structure, type and value to be considered equal.

Example of = comparison.

(= (+ 2 3) (+ 1 4))

>

Greater than comparison. A greater than comparison has the form (> expr1 ... exprN) and evaluates to t if expr1 is greater than all of expr2 ... exprN.

Example

(> 5 2)

<

Less than comparison. A less than comparison has the form (> expr1 ... exprN) and evaluates to t if expr1 is less than all of expr2 ... exprN.

Example

(< 5 2)

Boolean operators

and

Boolean and operation between n arguments. The form of an and expression is (and expr1 ... exprN). This operation treats all non-nil values as true. Boolean and is "shirt-circuiting" and only evaluates until a false is encountered.

The example below evaluates to t

(and t t)

The folowing example evaluates to 3

(and t t (+ 1 2))

And lastly an example that evaluates to nil (for false).

(and t (< 5 3))

or

Boolean or operation between n arguments. The form of an or expression is (or expr1 ... exprN). This operation treats all non-nil values as true. Boolean or is "short-circuiting" and only evaluates until a true is encountered.

The example below evaluates to t.

(or t nil)

not

Boolean not takes one argument. The form of a not expression is (not expr). All non-nil values are considered true.

The following example evaluates to t

(not nil)

Bit level operations

shl

The shift left operation takes two arguments. The first argument is a value to shift and the second argument is the number of bit positions to shift the value.

The example below evaluates to 4.

(shl 1 2)

shr

The shift right operation takes two arguments. The first argument is a value to shift and the second argument in the number of bit positions to shift the value.

The example below evaluates to 1.

(shr 4 2)

bitwise-and

Performs the bitwise and operation between two values. The type of the result is the same type as the first of the arguments.


bitwise-or

Performs the bitwise or operation between two values. The type of the result is the same type as the first of the arguments.


bitwise-xor

Performs the bitwise xor operation between two values. The type of the result is the same type as the first of the arguments.


bitwise-not

Performs the bitwise not operations on a value. The result is of same type as the argument.

Low level operations

encode-i32

The encode-i32 function converts a list of four (byte sized) values into an i32 value.

Example that evaluates to the i32 value 1024.

(encode-i32 (list 0 0 4 0))

encode-u32

The encode-u32 function converts a list of four (byte sized) values into an u32 value.

Example that evaluates to the u32 value 1024.

(encode-u32 (list 0 0 4 0))

encode-float

The encode-float function converts a list four (byte sized) values into a float value.

Example that evaluates to 3.14.

(encode-float (list 64 72 245 195))

decode

The decode function decodes a value into a list of four (byte sized) values.

Example that decodes float 3.14 into the list (64 72 245 195).

(decode 3.14)

nil and t

nil

Represents the empty list. The nil value is also considered to be false by conditionals

The example below creates a one element list by allocating a cons cell and putting a value (1) in the car field and nil in the cdr field.

(cons 1 nil)

t

All non nil values are considered true in conditionals. t should be used in cases where an explicit true makes sense.


Quotes and Quasiquotation

Code and data share the same representation, it is only a matter of how you look at it. The tools for changing how your view are the quotation and quasiquotation operations.


quote

Usages of the ' quote symbol in input code is replaced with the symbol quote by the reader. Evaluating a quoted expression, (quote a), results in a unevaluated.

The program string '(+ 1 2) gets read into the heap as the list (quote (+ 1 2)). Evaluating the expression (quote (+ 1 2)) results in the value (+ 1 2).


`

The backwards tick ` is called the quasiquote. It is similar to the ' but allows splicing in results of computations using the , and the ,@ operators.

The result of '(+ 1 2) and `(+ 1 2) are similar in effect. Both result in the result value of (+ 1 2), that is a list containing +, 1 and 2. When `(+ 1 2) is read into the heap it is expanded into the expression (append (quote (+)) (append (quote (1)) (append (quote (2)) (quote nil)))) which evaluates to the list (+ 1 2).


,

The comma is used to splice the result of a computation into a quasiquotation.

The expression `(+ 1 ,(+ 1 1)) is expanded by the reader into (append (quote (+)) (append (quote (1)) (append (list (+ 1 1)) (quote nil)))). Evaluating the expression above results in the list (+ 1 2).


,@

The comma-at operation is used to splice in the result of a computation (that returns a list) into a list.

Example:

(define mylist (list 1 2 3 4 5)
`(9 6 5 ,@mylist)

Evaluates to the list (9 6 5 1 2 3 4 5).

Built-in operations

eval

Evaluate data as an expression. The data must represent a valid expression.

Example that evaluates to 3.

(eval (list + 1 2))

eval-program

Evaluate a list of data where each element represents an expression.

This function is quite awkward as it replaces the program in the running context with the program provided in the list. Avoid using this function if possible.


type-of

The type-of function returns a symbol that indicates what type the argument is. The form of a type-of expression is (type-of expr).

Example that evaluates to type-float.

(type-of 3.14)

sym-to-str

The sym-to-str function converts a symbol to its string representation. The resulting string is a copy of the original so you cannot destroy built in symbols using this function.

Example that returns the string "lambda".

(sym-to-str 'lambda)

str-to-sym

The str-to-sym function converts a string to a symbol.

Example that returns the symbol hello.

(str-to-sym "hello")

sym-to-u

The sym-to-u function returns the numerical value used by the runtime system for a symbol.

Example that evaluates to 4.

(sym-to-u 'lambda)

u-to-sym

The u-to-sym function returns the symbol associated with the numerical value provided. This symbol may be undefined in which case you get as result a unnamed symbol.


is-fundamental

The is-funamental function returns true for built-in functions.

Example that returns true.

(is-fundamental '+)

Special forms

if

Conditionals are written as (if cond-expr then-expr else-expr). If the cond-expr evaluates to nil the else-expr will be evaluated. for any other value of cond-expr the then-expr will be evalated.

The example below evaluates to 0 if a is less than or equal to 4. Otherwise it evaluates to a + 10.

(if (> a 4) (+ a 10) 0)

lambda

You create an anonymous function with lambda. The function can be given a name by binding the lambda expression using define or let. A lambda expression has the form (lambda param-list body-expr).

The example shows an anonymous function that adds one.

(lambda (x) (+ x 1))

A lambda can be immediately applied to an argument.

((lambda (x) (+ x 1)) 10)

The application above results in the value 11. Using define you can give a name to the function.

(define inc (lambda (x) (+ x 1)))

Now the expression (inc 10) computes the result 11.


closure

A lambda expression evaluates into a closure which is very similar to a lambda but extended with a captured environment for any names unbound in the param-list appearing in the body-expr. The form of a closure is (closure param-list body-exp environment).

Evaluation of the expression

(lambda (x) (+ x 1))

results in the value

(closure (x) (+ x 1) nil)

Below is an example of how a value is captured into the closure.

(let ((a 1)) (lambda (x) (+ x a)))

The expression above evaluates to the following. Note that (a . 1) appears in the closure.

(closure (x) (+ x a) ((a . 1)))

let

Local environments are created using let. The let binding in lispbm allows for mutually recursive bindings. The form of a let is (let list-of-bindings body-expr) and evaluating this expression means that body-expr is evaluted in an environment extended with the list-of-bindings.

Example that evaluates to 3.

(let ((a 1)
      (b 2))
  (+ a b))

Below is a more advanced example of two mutually recursive functions created in a let binding.

(let ((f (lambda (x) (if (= x 0) 0 (g (- x 1)))))
      (g (lambda (x) (if (= x 0) 1 (f (- x 1))))))
  (f 11))

The mutually recursive program above evaluates to 1.


define

You can give names to values in a global scope by using define. The form of define is (define name expr). The expr is evaluated and it is the result of the evaluated expr that is stored in the environment. In lispbm you can redefine already defined values.

Example

(define apa 10)

progn

The progn special form allows you to sequence a number of expressions. The form of a progn expression is:

(progn expr1
       expr2
       ...
       exprN)

The evaluation result of a progn sequence is the value that the last exprN evaluated to. This is useful for sequencing of side-effecting operations.

Simple example that evaluates to 3:

(progn 1
       2
       3)

An example where side effects are sequenced:

(progn (define a 10)
        (define b 20)
        (+ a b))

This program evaluates 30 but also extends the global environment with the 2 bindings (a 10) and (b 20) created using define.


read

Parses a string resulting in either an expression or the read_error in case the string can not be parsed into an expression. The form of a read expression is (read string).

The example below evaluates to the value 1:

(read "1")

You can also read code:

(read "(lambda (x) (+ x 1))")

That lambda you just read in from a string can be directly applied to an argument.

((read "(lambda (x) (+ x 1))") 10)

The code above evaluates to 11.


read-program

Parses a string containing multiple sequenced expressed. The resulting list of expressions can be evaluated as a program using eval-program. The form of a read-program expression is (read-program string).

Evaluate a program you just read from a string with eval-program.

(eval-program (read-program "(define apa 1) (+ 2 apa)"))

The expression above evaluates to 3 with the side effect that the global environment has been extended with the binding (apa 1).


Lists and cons cells

Lists are build using cons cells. A cons cell is represented by the \ref lbm_cons_t struct in the implementation and consists of two fields named the car and the cdr. There is no special meaning associated with the car and the cdr each can hold a \ref lbm_value. See cons and list for two ways to create structures of cons cells on the heap.

car

Use car to access the car field of a cons cell. A car expression has the form (car expr).

Taking the car of a number of symbol type is in general a type_error. The following program results in type_error.

(car 1)

The next example evaluates to 1.

(car (cons 1 2))

The car operation accesses the head element of a list. The following program evaluates to 9.

(car (list 9 8 7))

cdr

Use cdr to access the cdr field of a cons cell. A cdr expression has the form (cdr expr).

The example below evaluates to 2.

(cdr (cons 1 2))

The cdr operation gives you the rest of a list. The example below evaluates to the list (8 7).

(cdr (list 9 8 7))

cons

The cons operation allocates a cons cell from the heap and populates the car and the cdr fields of this cell with its two arguments. The form of a cons expression is (cons expr1 expr2).

Build the list (1 2 3) using cons. nil terminates a proper list.

(cons 1 (cons 2 (cons 3 nil)))

Construct the pair (+ . 1) using cons.

(cons + 1)

.

The dot, ., operation creates a pair. The form of a dot expression is (expr1 . expr2). By default the evaluator will attempt to evaluate the result of (expr1 . expr2) unless it is prefixed with '.

Example that creates the pair (1 . 2)

'(1 . 2)

list

The list function is used to create proper lists. The function takes n arguments and is of the form (list expr1 ... exprN).

Example that creates the list (1 2 3 4).

(list 1 2 3 4)

append

The append function combines two lists into a longer list. An append expression is of the form (append expr1 expr2).

Example that combines to lists.

(append (list 1 2 3) (list 4 5 6))

ix

Index into a list using the ix. the form of an ix expression is (ix list-expr index-expr). Indexing starts from 0 and if you index out of bounds the result is nil.

Example that evaluates to 2.

(ix (list 1 2 3) 1)

set-car

The set-car is a destructive update of the car field of a cons-cell.

Define apa to be the pair (1 . 2)

(define apa '(1 . 2))

Now change the value in the car field of apa to 42.

(set-car apa 42)

The apa pair is now (42 . 2).


set-cdr

The set-cdr is a destructive update of the cdr field of a cons-cell.

Define apa to be the pair (1 . 2)

(define apa '(1 . 2))

Now change the value in the cdr field of apa to 42.

(set-cdr apa 42)

The apa pair is now (1 . 42).

Arrays

array-read

Read one or many elements from an array. The form of an array-read expression is either (array-read array-expr index-expr) of (array-read array-expr start-index-expr end-index-expr) for reading a range of values into a list.

Example that evaluates to the character l.

(array-read "hello" 3)

The next example reads a range values

(array-read "hello" 1 3)

and results in the list (\#e \#l \#l).


array-write

The array-write function performs a destructive update of an array.

Example that turns array "hello" into "heflo"

(array-write "hello" 2 \#f)

Pattern-matching

match

Pattern-matching is expressed using match. The form of a match expression is (match expr (pat1 expr1) ... (patN exprN)). Pattern-matching compares the shape of an expression to each of the pat1 ... patN and evaluates the expression exprM of the pattern that matches. In a pattern you can use a number of match-binders or wildcards: _, ?, ?i28,?u28,?float.

For example the match expression below evaluates to 2.

(match 'orange
       (green 1)
       (orange 2)
       (blue 3))

_

The underscore pattern matches anything.

An example that evaluates to i-dont-know

(match 'fish
       (horse 'its-a-horse)
       (pig 'its-a-pig)
       (_ 'i-dont-know))

?

The ? pattern matches anything and binds that anything to variable. Using the ? pattern is done as (? var) and the part of the expression that matches is bound to var.

An example that evaluates to 19.

(match '(orange 17)
       ((green (? n)) (+ n 1))
       ((orange (? n)) (+ n 2))
       ((blue (? n)) (+ n 3)))

?i28

The ?i28 pattern matches any i28 and binds that value to a variable. Using the ?i28 pattern is done as (?i28 var) and the part of the expression that matches is bound to the var.

The following example evaluates to not-an-i28.

(match 3.14
       ( (i28 n) (+ n 1))
       ( _ 'not-an-i28))

The example below evaluates to 5.

(match 4
       ( (i28 n) (+ n 1))
       ( _ 'not-an-i28))

?u28

The ?u28 pattern matches any u28 and binds that value to a variable. Using the ?u28 pattern is done as (?u28 var) and the part of the expression that matches is bound to the var.


?float

The ?float pattern matches any float and binds that value to a variable. Using the ?float pattern is done as (?float var) and the part of the expression that matches is bound to the var.


Concurrency

spawn

Use spawn to spawn a concurrent task. The concurrency implemented by LispBM is called cooperative concurrency and it means that processes must sleep using yield or they will starve out other processes. The form of a spawn expression is (spawn closure arg1 ... argN) The return value is a process ID.


wait

Use wait to wait for a spawned process to finish. The argument to wait should be a process id. The wait blocks until the process with the given process id finishes.

Be careful to only wait for processes that actually exist and do finish. Otherwise you will wait forever.


yield

To put a process to sleep, call yield. The argument to yield is number indicating at least how many microseconds the process should sleep.


Message-passing

send

Messages can be sent to a process by using send. The form of a send expression is (send pid msg). The message, msg, can be any LispBM value.


recv

To receive a message use the recv command. A process will block on a recv until there is a matching message in the mailbox. The recv syntax is very similar to match.

Example where a process waits for an i28

(recv ( (?i28 n) (+ n 1) ))

Macros

lispBM macros are created using the macro keyword. A macro is quite similar to lambda in lispBM except that arguments are passed in unevaluated. Together with the code-splicing capabilities given by quasiquotation, this provides a powerful code-generation tool.

A macro application is run through the interpreter two times. Once to evaluate the body of the macro on the unevaluated arguments. The result of this first application should be a program. The resulting program then goes through the interpreter again to compute final values.

Given this repeated evaluation, macros are not a performance boost in lispbm. Macros are really a feature that should be used to invent new programming abstractions in cases where it is ok to pay a little for the overhead for benefits in expressivity.

macro

The form of a macro expression is: (macro args body)

Some lisps provide a defun operation for defining functions with a bit less typing. The example below defines a defun macro.

(define defun (macro (name args body)
                     `(define ,name (lambda ,args ,body))))

With this macro the function inc that adds 1 to its argument can be defined as:

(defun inc (x) (+ x 1))

Call With Current Continuation

"Call with current continuation" is called call-cc in LBM. Call with current continuation saves the "current continuation", which encodes what the evaluator will do next, into an object in the language. This encoded continuation object behaves as a function taking one argument.

The call-cc should be given a function, f, as the single argument. This function, f, should also take a single argument, the continuation. At any point in the body of f the continuation can be applied to a value, in essense replacing the entire call-cc with that value. All side-effecting operations operations up until the application of the continuation will take effect.

From within a call-cc application it is possible to bind the continuation to a global variable which will allow some pretty arbitrary control flow.

The example below creates a macro for a progn facility that allows returning at an arbitrary point.

(define do (macro (body)
                  `(call-cc (lambda (return) (progn ,@body)))))

The example using do below makes use of print which is not a built-in feature of lispBM. There are just to many different ways a programmer may want to implement print on an microcontroller. Use the lispBM extensions framework to implement your own version of print

(do ((print 10)
     (return 't)
     (print 20)))

In the example above only "10" will be printed. Below is an example that conditionally returns.

(define f (lambda (x)
            (do ((print "hello world" \#newline)
                 (if (= x 1)
                     (return 't)
                     nil)
                 (print "Gizmo!" \#newline)))))

Unparsable symbols

Unparsable symbols cannot be written into a program. The unparsable symbols signals different kinds of error conditions that may point at something being wrong in the code (or that it is exhausting all resources).

no_match

The no_match symbol is returned from pattern matching if no case matches the expression.


read_error

The read_error symbol is returned if the reader cannot parse the input code.


type_error

The type_error symbol is returned byt built-in functions if the values passed in are of incompatible types.


eval_error

The eval_error symbol is returned if evaluation could not proceed to evaluate the expression. This could be because the expression is malformed.


out_of_memory

The out_of_memory symbol is returned if the heap is full and running the garbage collector was not able to free any memory up. The program uses more memory than the size of the heap. Make the heap larger.


fatal_error

The fatal_error symbol is returned in cases where the LispBM runtime system cannot proceed. Something is corrupt and it is not safe to continue.


out_of_stack

The out_of_stack symbol is returned if the evaluator runs out of continuation stack (this is its runtime-stack). You are most likely writing a non-tail-recursive function that is exhausting all the resources.


division_by_zero

The division_by_zero symbol is returned when dividing by zero.


variable_not_bound

The variable_not_bound symbol is returned when evaluating a variable (symbol) that is neighter bound nor special (built-in function).

Types

type-list


type-i28


type-u28


type-float


type-i32


type-u32


type-array


type-symbol


type-char


type-ref


type-stream