28 KiB
LispBM language reference
- 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).