LispBM
|
Symbols are very important and fundamental to LispBM and also perhaps a bit different from identifiers/names used in languages such as C, so a short intro could be good here.
A symbol can be thought of as a name and can be used to give names to functions or values (variables). A symbol can also be treated and used as a value in and of itself a value (or data). So it can be used to name data and functions and is itself also data.
NOTE
Symbols are expressed as strings in your program such as a
, let
, define
, +
or orange
. The "reader", the part of LBM that parses code, translates each symbol into a 28bit value. The string orange
for example is only of interest if you print a symbol and then the runtime system will look up what string corresponds to the 28bit identifier you want to print. So the runtime system is never wasting time comparing strings to see if a symbol is this or that symbol, it's all integer comparisons.
You associate values with symbols using, define, let and you can change the value bound to a "variable" using setvar
Not all symbols are treated the same in LBM. Some symbols are treated as special because of their very fundamental nature. Among these special symbols you find define
, let
and lambda
for example. These are things that you should not be able to redefine and trying to redefine them leads to an error. There are two classes of symbols that are special by naming convention and these either start with a #
, for fast-lookup variables, and ext-
for extensions that will be bound at runtime.
Examples of symbols used as data are nil
and t
. nil
is used the represent nothing, the empty list or other similar things and t
represents true. But any symbol can be used as data by quoting it ‘’`, see Quotes and Quasiquotation .
Adds up an aribtrary number of values. The form of a +
expression is (+ expr1 ... exprN)
Example adding up two numbers. The result is 3.
When adding up values of different types values are converted.
The example above evaluates to float value 4.14.
You can add up multiple values.
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.
Multiplying an arbitrary number of values. The form of a * expression is (* expr1 ... exprN)
Example 2pi.
Division. The form of a / expression is (/ expr1 ... exprN)
.
Divide 128 by 2
The following example evaluates to 1.
Modulo operation. The form of a mod expression is (mod expr1 ... exprN)
.
Compute 5 % 3, evaluates to 2.
Compare expressions for equality. The eq
operation 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
.
Multiple expressions can be checked at once. The examples below evaluates to t
The following examples evaluate to nil
representing false.
The eq
comparison can be used on tree shaped data. The following expression evaluates to t
.
not-eq
implements the negation of eq. In other words, (not-eq a b c)
evaluates to the same result as (not (eq a b c))
.
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 =
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.
The !=
operation implements the negation of =
. So, (!= a b)
evaluates to the same result as (not (= a b))
.
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
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
Greater than or equal comparison. A greater than comparison has the form (>= expr1 ... exprN)
and evaluates to t
if expr1 is greater than or equal to all of expr2 ... exprN.
Example
Less than or equal comparison. A less than or equal comparison has the form (<= expr1 ... exprN)
and evaluates to t
if expr1 is less than or equal to all of expr2 ... exprN.
Example
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
The following example evaluates to 3
And lastly an example that evaluates to nil (for false).
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
.
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
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.
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.
Performs the bitwise and operation between two values. The type of the result is the same type as the first of the arguments.
Performs the bitwise or operation between two values. The type of the result is the same type as the first of the arguments.
Performs the bitwise xor operation between two values. The type of the result is the same type as the first of the arguments.
Performs the bitwise not operations on a value. The result is of same type as the argument.
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.
All non nil values are considered true in conditionals. t
should be used in cases where an explicit true makes sense.
true
is an alias for t
.
false
is an alias for nil
.
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.
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)
.
(9 6 5 1 2 3 4 5)
. Built-in operationsEvaluate data as an expression. The data must represent a valid expression.
Example that evaluates to 3.
Evaluate a list of data where each element represents an expression.
Example that results in the value 15:
Example that prints the strings "apa", "bepa" and "cepa":
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
.
The sym2str
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"
.
The str2sym
function converts a string to a symbol.
Example that returns the symbol hello
.
The sym2u
function returns the numerical value used by the runtime system for a symbol.
Example that evaluates to 4.
The u2sym
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.
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 evaluated.
The example below evaluates to 0 if a is less than or equal to 4. Otherwise it evaluates to a + 10.
cond
is a generalization of if
to discern between n different cases based on boolean expressions. The form of a cond
expression is: (cond ( cond-expr1 expr1) (cond-expr2 expr2) ... (cond-exprN exprN))
. The conditions are checked from first to last and for the first cond-exprN
that evaluates to true, the corresponding exprN
is evaluated.
If no cond-exprN
evaluates to true, the result of the entire conditional is nil
.
Example that prints "Hello world":
Example that evaluates to nil
as none of the conditions evaluate to true.
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.
A lambda can be immediately applied to an argument.
The application above results in the value 11. Using define you can give a name to the function.
Now the expression (inc 10)
computes the result 11.
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
results in the value
Below is an example of how a value is captured into the closure.
The expression above evaluates to the following. Note that (a . 1)
appears in the closure.
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.
Below is a more advanced example of two mutually recursive functions created in a let binding.
The mutually recursive program above evaluates to 1.
Let supports deconstructive bindings. These are bindings that decompose a complex value into constituents.
Example:
In the example, the bindings a = 1 and b = 2 are created for use in the let body.
loop allows to repeatedly evaluate an expression for as long as a condition holds. The form of a loop is (loop list-of-local-bindings condition-exp body-exp)
.
The list-of-local-bindings
are very similar to how let
works, just that here the body-exp
is repeated.
Example that prints hello world 5 times:
When the cond-exp
evaluates to false, the loop exits and the entire loop expression returns the value nil
.
Here is an example of a "for each element in a list do something":
The example above loops for as long as there are elements in a. Each element is printed and a is updated in the body.
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
A definition in the global can be removed using undefine. The form of an undefine expression is (undefine name-expr)
where name-expr should evaluate to a symbol (for example 'apa
).
Example
It is also possible to undefine several bindings at the same time by providing a list of names.
Example
The setvar
form is used to change the value of some variable in an environment. You can use setvar
to change the value of a global definition, a local definition or a variable defintion (#var
). An application of the setvar
form looks like (setvar var-expr val-expr)
where var-expr
should evaluate to a symbol. The val-expr
is evaluated before rebinding the variable. setvar
returns the value that val-expr
evaluates to.
Examples:
The variable a
is now 10
in the global environment.
Now, the value of a
will be 20. Note that a
is quoted in the setvar
form application while it is not in the define
form. This is because define
requires the first argument to be a symbol while the setvar
form requires the first argument to evaluate into a symbol.
You can also set the value of a let bound variable.
And you can change the value of a #var
.
#a
is now 20.
The set
form is used to change the value of a variable in an environment. It behaves identical to setvar
.
Example:
The variable a
is now 10 in the global environment.
And now a
is associated with 20 in the global environment.
set
works in local environments too such as in the body of a let
or in a progn
-local variable created using var
.
Example:
The expression above evaluates to 20.
The setq
special-form is similar to set
and to setvar
but expects the first argument to be a symbol. The first argument to setq
is NOT evaluated.
Example:
The variable a
is now 10 in the global environment.
And now a
has been associated with the value 20 in the global env.
Just like set
and setvar
, setq
can be used on variables that are bound locally such as in the body of a let
or a progn
-local variable created using var
.
The progn special form allows you to sequence a number of expressions. The form of a progn expression is:
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:
An example where side effects are sequenced:
This program evaluates 30 but also extends the global environment with the 2 bindings (a 10)
and (b 20)
created using define.
The curlybrace {
syntax is a short-form (syntactic sugar) for (progn
. The parser replaces occurrences of {
with (progn
. The {
should be closed with an }
.
These two programs are thus equivalent:
And
The closing curlybrace }
should be used to close an opening {
but purely for esthetical reasons. The }
is treated identically to a regular closing parenthesis )
.
The opening {
and closing }
curlybraces are used as a short-form for progn
-blocks of sequences expressions.
The var special form allows local bindings in a progn expression. A var expression is of the form (var symbol expr) and the symbol symbol
is bound to the value that expr
evaluates to withing the rest of the progn expression.
Example:
and:
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:
You can also read code:
That lambda you just read in from a string can be directly applied to an argument if using an application of eval to evaluate the read lambda into a closure.
The code above evaluates to 11.
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.
The expression above evaluates to 3 with the side effect that the global environment has been extended with the binding (apa 1)
.
Parses and evaluates a program incrementally. read-eval-program
reads a top-level expression then evaluates it before reading the next.
Example that evaluates to 20:
read-eval-program
supports the @const-start
, @const-end
which moved all global definitions created in the program to constant memory (flash).
Example that evaluates to 20:
Lists are built using cons cells. A cons cell is represented by the 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 lbm_value. See cons and list for two ways to create structures of cons cells on the heap.
A cons cell can be used to store a pair of values. You create a pair by sticking a value in both the car and cdr field of a cons cell using either ‘’(1 . 2)or
(cons 1 2)`.
A list is a number of cons cells linked together where the car fields hold values and the cdr fields hold pointers (the last cdr field is nil). The list below can be created either as ‘’(1 2 3)or as
(list 1 2 3)`.
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
.
The next example evaluates to 1.
The car
operation accesses the head element of a list. The following program evaluates to 9.
first
is an alternative (and one that makes some sense) name for the car
operation.
Use first
to access the first element of a list or pair. A first
expression has the form (first expr)
.
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.
The cdr
operation gives you the rest of a list. The example below evaluates to the list (8 7).
rest
is an alternative name for the cdr
operation.
Use rest
to access all elements except the first one of a list, or to access the second element in a pair. A rest
expression has the form (rest expr)
.
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.
Construct the pair (+ . 1)
using cons.
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)
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).
Computes the length of a list. The length
function takes one argument and is of the form (length expr)
.
Example that evaluates to 4
The range
function computes a list with integer values from a range specified by its endpoints. The form of a range expression is (range start-expr end-expr)
. The end point in the range is excluded.
Example that generates the list (4 5 6 7).
A range specified with the end-point being smaller than the starting point is in descending order.
Example that generates the list (7 6 5 4).
Negative number can be used to specify a range
Example that generates the list (-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9)
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.
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.
Destructively update an element in a list. The form of a setix
expression is (setix list-expr index-extr value-expr)
. Indexing starts from 0 and if you index out of bounds the result is nil.
The setcar
is a destructive update of the car field of a cons-cell.
Define apa
to be the pair (1 . 2)
Now change the value in the car field of apa to 42.
The apa
pair is now (42 . 2)
.
The setcdr
is a destructive update of the cdr field of a cons-cell.
Define apa
to be the pair (1 . 2)
Now change the value in the cdr field of apa to 42.
The apa
pair is now (1 . 42)
.
take
creates a list containing the n
first elements of another list. The form of a take
expression is (take list-exp n-exp)
.
Example that takes 5 elements from a list:
In the example above, the result of (take ls 5)
is (1 2 3 4 5)
.
drop
creates a list from another list by dropping the n
first elements of that list. The form of a drop
expression is (drop list-exp n-exp)
.
Example that drops 5 elements from a list:
Here (drop ls 5)
evaluates to the list (6 7 8 9 10)
.
merge
merges two lists that are ordered according to a comparator into a single ordered list. The form of a merge
expression is (merge comparator-exp list-exp1 list-exp2)
.
Example:
Here (merge < a b)
evaluates to the list (1 2 3 4 5 6 8 10 12)
.
sort
orders a list of values according to a comparator. The sorting algorithm used is an in-place merge-sort. A copy of the input list is created at the beginning of the sort to provide a functional interface from the user's point of view. The form of a sort expression is (sort comparator-exp list-exp)
Example:
Here (sort < ls)
evaluates to the list (1 1 2 3 5 8 9)
.
Association lists (alists) are, just like regular lists, built out of cons-cells. The difference is that an alist is a list of pairs where the first element in each par can be thought of as a key and the second element can be thought of as the value. So alists implement a key-value lookup structure.
‘(list ’(1 . horse) '(2 . donkey) '(3 . shark))` is an example of an alist with integer keys and symbol values.
The acons
form is similar to cons
, it attaches one more element onto an alist. The element that is added consists of a key and a value so acons
takes one more argument than cons
. The form of an acons
expression is (acons key-expr val-expr alist-expr)
. The alist-expr
should evaluate to an alist but there are no checks to ensure this.
Example that adds the key 4
and associated value lemur
to an existing alist.
The assoc
function looks up the first value in an alist matching a given a key. The form of an assoc
expression is (assoc alist-expr key-expr)
Example that looks up the value of key 2
in an alist.
The cossa
function looks up the first key in an alist that matches a given value. The form of an cossa
expression is (cossa alist-expr value-expr)
Example that looks up the key for the value donkey
in an alist.
The setassoc
function destructively updates a key-value mapping in an alist. The form of a setassoc
expression is (setassoc alist-expr key-expr value-expr)
.
Create an array of bytes. The form of an bufcreate
expression is (bufcreate size-expr)
Example that creates a 10 element buffer caled data:
Use the buffer extensions to operate data
. See the VESC lbm documentation for details on operations on buffers.
Returns the size of a buffer in number of bytes. The form of an buflen
expression is (buflen buf-expr)
where buf-expr has to evaluate into a buffer.
Read a value from a buffer. The contents of a buffer can be read as a sized integer or unsigned value using as many bytes from the buffer as the X portion of the function name implies. The form of a bufget expression is (bufget-[X] buf-expr ix-expr)
where ix-expr
evaluates to a number indicating the byte position to start reading from.
bufget-i8
bufget-i16
bufget-i24
bufget-i32
bufget-u8
bufget-u16
bufget-u24
bufget-u32
Example that reads an u8 from a buffer at position 1:
The bufset
functions performs a destructive updates to a buffer. The form of a bufset
expression is (bufset-[X] buf-expr ix-expr val-expr)
where ix-expr
evaluates to a number indicating where in the buffer to start writing and val-expr
is the value to write.
bufset-i8
bufset-i16
bufset-i24
bufset-i32
bufset-u8
bufset-u16
bufset-u24
bufset-u32
Example that updates position 1 in a buffer:
To clear a byte array the function bufclear can be used:
Where arr is the byte array to clear, optByte is the optional argument of what to clear with (default 0), optStart is the optional argument of which position to start clearing (default 0) and optLen is the optional argument of how many bytes to clear after start (default the entire array). Example:
Byte-array (buffer) literals can be created using the [
and ]
syntax to enclose values to initialize the array with. The [
and ]
syntax is complete resolved in the parser and thus cannot contain arbitrary lisp terms. the values listed between the [
and the ]
must be literals!
The form of the [
and ]
syntax is [ val1 ... valN ]
.
Example that creates a byte array
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: _
, ?
, ?i
,?u
,?float
.
For example the match expression below evaluates to 2.
The no_match
symbol is returned from pattern matching if no case matches the expression.
- Add a catch-all case to your pattern-matching. `_`.
The underscore pattern matches anything.
An example that evaluates to 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.
Patterns used in a match expressions can be augmented with a boolean guard to further discern between cases. A pattern with a guard is of the form (pattern-expr guard-expr expr)
. A pattern with a guard, matches only if the pattern structurally matches and if the guard-expr evaluates to true in the match environment.
Example:
The concurrency support in LispBM is provided by the set of functions, spawn
, wait
, yeild
and atomic
described below. Concurrency in LispBM is scheduled by a round-robin scheduler that splits the runtime system evaluator fairly (with caveats, below) between all running processes.
When a process is scheduled to run, made active, it is given a quota of evaluator "steps" to use up. The process then runs until that quota is exhausted or the process itself has signaled it wants to sleep by yielding or blocking (for example by waiting for a message using the message passing system).
A process can also request to not be "pre-empted" while executing a certain expression by invoking atomic
. One should take care to make blocks of atomic code as small as possible as it disrupts the fairness of the scheduler. While executing inside of an atomic block the process has sole ownership of the shared global environment and can perform atomic read-modify-write sequences to global data.
Use spawn
to launch a concurrent process. Spawn takes a closure and arguments to pass to that closure as its arguments. The form of a spawn expression is (spawn opt-name opt-stack-size closure arg1 ... argN)
.
Each process has a runtime-stack which is used for the evaluation of expressions within that process. The stack size needed by a process depends on
Having a stack that is too small will result in a out_of_stack
error.
The default stack size is 256 words (1K Bytes) and should be more than enough for reasonable programs. Many processes will work perfectly fine with a lot less stack. You can find a good size by trial and error.
Use spawn-trap
to spawn a child process and enable trapping of exit conditions for that child. The form of a spawn-trap
expression is (spawn-trap opt-name opt-stack-size closure arg1 .. argN)
. If the child process is terminated because of an error, a message is sent to the parent process of the form (exit-error tid err-val)
. If the child process terminates successfully a message of the form (exit-ok tid value)
is sent to the parent.
Example:
Use self
to obtain the thread-id of the thread in which self
is evaluated. The form of a self
expression is (self)
. The thread id is of an integer type.
Example:
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. When the process with with the given id finishes, the wait function returns True.
Be careful to only wait for processes that actually exist and do finish. Otherwise you will wait forever.
To put a process to sleep, call yield
. The argument to yield
is number indicating at least how many microseconds the process should sleep.
atomic
can be used to execute a LispBM one or more expression without allowing the runtime system to switch process during that time.
An example that atomically perfoms operations a,b and c.
The exit-ok
function terminates the thread in a "successful" way and returnes a result specified by the programmer. The form of an exit-ok
expression is (exit-ok value)
. If the process that calls exit-ok
was created using spawn-trap
a message of the form (exit-ok tid value)
is be sent to the parent of this process.
The exit-error
function terminates the thread with an error specified by the programmer. The form of an exit-error
expression is (exit-error err_val)
. If the process that calls exit-error
was created using spawn-trap
a message of the form (exit-error tid err_val)
is sent to the parent of this process.
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.
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 integer ?i
.
Like recv, recv-to
is used to receive messages but recv-to
takes an extra timeout argument.
The form of an recv-to
expression is
If no message is received before the timout, the message timeout
is delivered to the waiting process. This timeout
message can be handled in one of the receive patterns.
Example
Change the size of the mailbox in the current process. Standard mailbox size is 10 elements.
Example that changes mailbox size to 100 elements.
Lisp values can be "flattened" into an array representation. The flat representation of a value contains all information needed so that the value can be recreated, "unflattened", in another instance of the runtime system (for example running on another microcontroller).
Not all values can be flattened, custom types for example cannot.
The flatten
function takes a value as single argument and returns the flat representation if successful. A flatten expression has the form (flatten expr)
. Note that expr
is evaluated before the flattening.
Example:
The example above creates a byte-array representating the the value 3
.
Now the byte-array contains a representation of the list (+ 1 2)
unflatten
converts a flat value back into a lisp value. Te form of an unflatten
expression is (unflatten flat-value)
Example:
and:
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.
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.
With this macro the function inc
that adds 1 to its argument can be defined as:
"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.
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
In the example above only "10" will be printed. Below is an example that conditionally returns.
If an error occurs while evaluating a program, the process that runs that program is killed. The result of the killed process is set to an error symbol indicating what went wrong.
If the process was created using spawn
(or equivalently, started by a issuing a command in the repl), the process dies and an error message is presented over the registered printing callback (dependent on how LispBM is integrated into your system). The ctx_done_callback
is also called and performs other integration dependent tasks related to the shutting down of a process.
If the process was created using spawn-trap
, in addition to the above, a message is sent to the parent process (the process that executed the spawn-trap) containing information about the process that struck an error. See spawn-trap. The parent process can now choose to restart the process that crashed or to take some other action.
The read_error
symbol is returned if the reader cannot parse the input code.
Read errors are most likely caused by syntactically incorrect input programs.
- Check that all opening parenthesis are properly closed.
The type_error
symbol is returned by built-in functions or extensions if the values passed in are of incompatible types.
The eval_error
symbol is returned if evaluation could not proceed to evaluate the expression. This could be because the expression is malformed.
Evaluation error happens on programs that may be syntactically correct (LispBM has a very low bar for what is considered syntactically correct), but semantically nonsensical.
- Check the program for mistakes. - Are your parenthesis enclosing the correct subterms? - Check that you haven't written, for example, (1 + 2) where it should be (+ 1 2).
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 you have written requires more memory.
- Increase the heap size. - Rewrite the application to use less memory.
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.
- If this happens please send the program and the full error message to blog.joel.svensson@gmail.com. It will be much appreciated.
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.
- Check your program for recursive functions that are not tail-recursive Rewrite these in tail-recursive form. - If you spawned this process in a small stack. For example (spawn 10 prg), try to spawn it with a larger stack.
The division_by_zero
symbol is returned when dividing by zero.
- Check your math. - Add 0-checks into your code at a strategic position.
The variable_not_bound
symbol is returned when evaluating a variable (symbol) that is neighter bound nor special (built-in function).
Flash memory can be used to store data and functions that are constant. Things can be moved to flash explicitly using the move-to-flash
function or as part of the reading procedure. To move things automatically to flash during reading, there are @
directives.
if @const-symbol-strings
directive is placed in a file, symbols will be created in flash memory instead of the arrays memory.
@const-start
opens a block of code where each global definition is moved to constant memory (flash) automatically. This can be used only together with the incremental reader (such as read-eval-program
).
A @const-start
opened block should be closed with a @const-end
. Constant blocks cannot be nested.
Example:
@const-end
closes an block opened by @const-start
.
A value can be moved to flash storage to save space on the normal evaluation heap or lbm memory. A move-to-flash
expression is of the form (move-to-flash sym opt-sym1 ... opt-symN)
. The symbols sym
, opt-sym1 ... opt-symN
should be globally bound to the values you want moved to flash. After the value has been moved, the environment binding is updated to point into flash memory. CAUTION This function should be used carefully. Ideally a value should be moved to flash immediately after it is created so there is no chance that other references to original value exists.
Example that moves an array to flash storage:
Example that moves a list to flash storage:
Functions can be moved to flash storage as well:
A value with type type-i
occupy 28bits on the 32 bit version of LBM and 56bits on the 64bit version.
A value with type type-u
occupy 28bits on the 32 bit version of LBM and 56bits on the 64bit version.
Convert any numerical value to a byte. If the input is not a number the output of this function will be 0.
Convert a value of any numerical type to an integer. The resulting integer is a 28bit value on 32bit platforms and 56 bits on 64 bit platforms. If the input is not a number the output of this function will be 0.
Convert a value of any numerical type to an unsigned integer. The resulting integer is a 28bit value on 32bit platforms and 56 bits on 64 bit platforms. If the input is not a number the output of this function will be 0.
Convert any numerical value to a 32bit int. If the input is not a number the output of this function will be 0.
Convert any numerical value to a 32bit unsigned int.
Convert any numerical value to a single precision floating point value. If the input is not a number the output of this function will be 0.
Convert any numerical value to a 64bit int. If the input is not a number the output of this function will be 0.
Convert any numerical value to a 64bit unsigned int. If the input is not a number the output of this function will be 0.
Convert any numerical value to a double precision floating point value. If the input is not a number the output of this function will be 0.