Skip to content
Jonas S Karlsson edited this page Sep 17, 2017 · 46 revisions

esp-lisp documentation

Esp-lisp is mostly try to be scheme-ish, at least for functions defined. It is, however, limited and small, not trying to contain all functions.

functions

  • basic: t nil null? atom? symbol?
  • cells: cons car cdr nth nthcdr set-car! set-cdr! cons?
  • *lists: list assoc length member map mapcar nth nthcdr
  • functions: de define set! quote lambda nlambda progn func? fundef funame funenv
  • strings: length string? concat split char (strings)
  • < = eq equal cmp (compare, all types including integer/string!/atom!/nil/list/mixtype)
  • test: if case cond and or not
  • integers: * - + / % number? integer? random < <= = => > eq (iota 5) (iota NUM start step)
  • readwrite: read princ prin1 print terpri printf pp load
  • files: load dir cat
  • lisp: eval evallist apply
  • internal: gc test syms fundef funenv funame
  • system: clock ticks time load baud
  • hardware: in out interrupt dht adc
  • web: web wget
  • timers: at stop

functions loaded by "env.lsp"

  • debugging: trace untrace
  • list: reverse append2 append (merge a b cmp)
  • make atom from string: intern

functions (load "scheme.lsp")

  • list: (map list '((a b c) (1 2 3) (4 5))) - essentially ZIP!
  • list: caar cadr cdar cddr caaar caadr cadar caddr cdaar cdadr cddar cdddr
aliases
  • string: string<, string>, string= string-append string-concatenate
  • string->symbol symbol->string symbol<
  • (take L n) (drop L n)
  • else
  • eq? eqv? equal? pair?
  • list-ref

types

  • integer
  • string (immutable, some scheme allows them to be modified)
  • cons (mutable)
  • prim
  • symbol
  • func (i.e. a closure/user defined function)
  • thunk/immediate (temporary internal closures)

commands

  • help = give some help: list some symbols, and commands
  • trace on / trace off = turns eval tracing on or off
  • gc on / gc off = turn gc tracing on or off
  • bye / quit / exit / ^D = leave esp-lisp
  • (load "fil.lsp") = load and execute a file, line by line

define function

This lisp have two(/three) types of ways to define a function, this one is defining a variable and giving it a function value...

lisp> (define plus (lambda (a b) (+ a b)))
#plus
lisp> plus
#plus
lisp> (plus 3 4)
7
lisp> (define a (+ 3 4))
7
lisp> a
7

To change a value:

lisp> a
7
lisp> (set! a 99)
99
lisp> a
99

You can alternative write the shorter variants:

lisp> (define (plus a b) (+ a b))
lisp> (de plus (a b) (+ a b))

Now you can also pretty-print the function, if you forget what it does:

lisp> (pp fibo)
(de fibo (n)
   (if (< n 2)
         1
      (+ (fibo (- n 1)) (fibo (- n 2)))))

vararg function

lisp> (define (bar . L) (list 'all L L L))
lisp> (bar 1 2 3)
(all (1 2 3) (1 2 3) (1 2 3)

lexical

lisp> (define a 9)
9
lisp> (define topa (lambda () a))
#topa
lisp> (topa)
9
lisp> (set! a 111)
111
lisp> a
111
lisp> (topa)
111
lisp> ((lambda (a) (+ a (topa))) 777)
888

real closures

lisp> (define mkints (lambda (b) (lambda ()(set! b (+ b 1))))))
#mkints
lisp> (define next-int (mkints 5))
#next-int
lisp> (next-int)
6
lisp> (next-int)
7

eval with "custom" environment

The form is (eval expr env), if env is given it's used, you may construct it yourself as dotted assoc list, as shown below. If no env is given, then then global env is used. lisp> (define a 3) 3 lisp> (eval (quote a) (cons (cons (quote a) 5))) 5 lisp> (env) ((a . 3) (fibo . #func[]) (nil) ...

This can be used to do an eval in the global environment:

lisp> (define (foo a) (eval (list 'define 'b a)))
#foo
lisp> (foo 77)
77

You an also extract the environment from within a closure:

lisp> (define (close a b c) (lambda (x) (+ a (* b c))))
#close
lisp> (define d (close 1 2 3))
#d
lisp> (d)
7
lisp> (funenv d)
((c . 3) (b . 2) (a . 1) ...
lisp> (eval (list 'list 'c 'b 'a) (funenv d))
(3 2 1)

NLAMBDA instead of macros

Question on reddit prompted me to add the syntax/function nlambda:

The NLAMBDA is like LAMBDA only that it quotes it's arguments. Interlisp had this, but it was ugly because you had to eval and then your local parameter names could shadow yours.

Instead of implementing macros, that potentially lead to large code-expansions, which are undesirable on platforms with little memory, esp-lisp implements NLAMBDA. The NLAMBDA gets a first argument which is the local environment where it's evaluated. This caputures the variable bindings outside the function. Then you just call (eval x env).

lisp> (define my-if (nlambda (env x th el)
            (if (eval x env) (eval th env) (eval el env))))
#my-if
lisp> (my-if 1 2 3)
2
lisp> (my-if nil 2 3)
3
lisp> (my-if 1 (print 2) (print 3))
2 2
lisp> (my-if nil (print 2) (print 3))
3 3

The same mechanism is used for C FFI functions in C that implement many "special" forms.

Here is a simple example that captures the lexical environment.

lisp> (define e (nlambda (e) e))
#e
lisp> (let ((x 11)(y 12)) (e))
((y . 12) (x . 11) (nil))

string functions

The names of these string functions are "non-standard" (not scheme). I prefer the shorter names (<=6 chars), instead of string-append, string-concatenate, integer->char char->integer... There will be a scheme.lsp file you can load with "long name aliases".

lisp> (length "foo")
3

Note that concat can take a list of integers, strings, symbols and make a string out of it. This replaces symbol->string. Read can be used to get a symbol of a string, replaces string->symbol.

lisp> (concat "foo" "bar")
"foobar"
lisp> (concat 1 "foo" '- "bar" (funame funame))
"1foo-barfuname"
lisp> (read (concat 'foo "bar"))
foobar

lisp> (split "1a2a3a4a5" "a")
("1" "2" "3" "4" "5")
lisp> (split "1a2a3a4a5" "a" 3)
("1" "2" "3")

Please note there is no "char" type, just use integer, and use the multi-way char function for conversions. UTF-8? Haha, come again? For now, just plain stupid "C".

lisp> (list (char "A") (char 66) (char "CA"))
(65 "B" 67)

printing functions

lisp> (define a '(foo (bar 42) "fie" fum))
(foo (bar 42) "fie" fum)
lisp> (princ a)
(foo (bar 42) fie fum)(foo (bar 42) "fie" fum)     - one print and one return value!
lisp> (prin1 a)
(foo (bar 42) "fie" fum)(foo (bar 42) "fie" fum)
lisp> (print a)

(foo (bar 42) "fie" fum) (foo (bar 42) "fie" fum)
lisp> (terpri)

nil
lisp> (pp a)
(foo (bar 42) "fie" fum)
lisp> (printf "Foo%5sFie%03d%5sFish" "Bar" 42 "Fum")
FooBarFie042FumFishnil

time related functions

Ticks increase in idle state each time the idle function is called. Also, calling the ticks function increase it. Thus, the difference will at be one. It's monotonically increasing, but not regularly.

lisp> (ticks)
69589278
lisp> (- (ticks) (ticks))
1

Clock returns time since process start in ms.

lisp> (clock)
163119

Time the difference in clock time ms for producing the result. Result it (clock-time . result).

lisp> (time (fibo 22))
(135 . 28657)
lisp> (time (fibo 22))
(136 . 28657)

Delay for N ms. Returns actual time spent in ms.

lisp> (delay 10000)
1000
lisp> (delay 0)
0

This function may in the future be allowed to run idle functions or interrupts. Currently, that is not supported (blocked). Also note that (clock) does not increase while this function is called as no CPU processing is taking place!

On ESP-8266 it does call vTaskDelay, and on unix it calls usleep.

Set the speed of the serial port to 9600

lisp> (baud 9600)
nil

random

Randomization Three forms:

  • (random) => -seed, initilize with a system randomly generated seed

  • (random -seed) => -seed, initalize with given seed

  • (random N) => give a random integer [0, N[

  • (random S E) => give a random integer [S, E]

    lisp> (random 10) 7 lisp quit unix> ./run lisp> (random 10) 7

The result is the same every time unless you randomize it:

lisp> (list (random) (random 10) (random 10) (random 10))
(-14927580 1 1 8)
lisp> (list (random) (random 10) (random 10) (random 10))
(-11862655 9 7 7)
lisp> (list (random) (random 10) (random 10) (random 10))
(-12267469 6 1 7)

lisp> (list (random -4711) (random 10) (random 10) (random 10))
(-4711 1 5 3)
lisp> (list (random -4711) (random 10) (random 10) (random 10))
(-4711 1 5 3)
lisp> 

SPIFFS file support

Esp-lisp now has added SPIFFS support!

Initial disk size 128K. Files are stored in the SPIFFS directory. The Makefile will build the directory snapshot each time you do make flash. The file "init.lsp" loads automatically on start, it loads "env.lsp" with some debugging functions. There is a scheme.lsp compatibility module that defines long scheme names.

There is a function (cat "lisp.hlp") to display files. More file functions can be added, scheme/common lisp style...

loading a file from the filesystem

To read a file and execute each line

lisp> (load "fil.lsp")
0
lisp> (load "file.lsp" verbosity)

where verbosity can be:

0 = only results of actions like print (it doesn't suppress prints)
1 = 0 + print file header, echo result of each expression                                                                 
2 = 1 + print expression, arrow, result                                                                                   
3 = 2 + print header with line number before each chunk. Good for debugging 

Web related functions

To get a web page:

lisp> (define whandler (lambda (a b c) (princ a) (if b (princ b)) (if c (princ c))))
lisp> (wget "yesco.org" "http://yesco.org/index.html" whandler)

To serve web pages:

lisp> (define w (lambda (what method path) (if what nil "string to return"))))
#w
lisp> (web 8080 w)

ESP8266 I/O

Read value from GPIO-0

lisp> (in 0)
1

Write value to GPIO-5

lisp> (out 5 1)
1
lisp> (out 5 0)
0

Read value from DHT temperature / humidity sensor

lisp> (dht 3)
(370 . 850) -- temperature in celsius * 10 . relative humidity * 10

Read value (0..1024) from analog-to-digital pin (A0 / TOUT)

lisp> (adc)
(777)

ESP8266 Interrupts/Buttons

To read data, like from a button, from input pins reliably, the following interrupt based functions are supported. The code supports 16 pins (0-15), it's not tested if all these can be used on the ESP8266.

Please note that PIN 0 on the NodeMCU is the button on the right from the USB port. This makes testing quite easy!

To control interrupts

// (interrupt PIN 0)  : disable
// (interrupt PIN 1)  : EDGE_POS
// (interrupt PIN 2)  : EDGE_NEG
// (interrupt PIN 3)  : EDGE_ANY
// (interrupt PIN 4)  : LOW
// (interrupt PIN 5)  : HIGH

Note: TODO: this has 200 ms, "ignore" if happen within 200ms, maybe add as parameter?

Callback API

If any interrupt is enabled it'll call intXX where XX=pin if the symbol exists. This is called from the IDLE loop, no clicks counts will be lost, clicks is the new clicks since last invokation/clear. It will only be invoked once if any was missed, and only the last time in ms is retained.

Call int00 function each time the default button is released:

(interrupt 0 1)

(define (int00 pin clicks count ms)
  (printf " [button %d new clicks=%d total=%d last at %d ms] " pin clicks count ms))

If esp-lisp is running a long program and is busy, the clicks will be updated alongside with count ms is set at each click. When the your program returns back to the idle loop/prompt. It will be called with clicks being the amount of clicks since last time it was callled.

Polling API

// (interrupt PIN)    : get count
// (interrupt PIN -1) : get +count if new, or -count if no new
// (interrupt PIN -2) : get +count if new, or 0 otherwise
// (interrupt PIN -3) : get ms of last click

For example, using the default button:

lisp> (interrupt 0 1)
0
lisp> (interrupt 0)
0
=== press button once ===
lisp> (interrupt 0)
1
=== press button three times ===
lisp> (interrupt 0)
4
lisp> (interrupt 0 -1)
4
lisp> (interrupt 0 -1)
-4
=== press button once ===
lisp> (interrupt 0 -1)
5
lisp> (interrupt 0 -1)
-5
=== press button three times ====
lisp> (interrupt 0 -1)
8
lisp> (intterupt 0 -2)
8
lisp> (interrupt 0 -2)
0
=== press button one time ====
lisp> (interrupt 0 -2)
1
lisp> (interrupt 0 -2)
0
=== press button three times ====
lisp> (interrupt 0 -2)
3
lisp> (interrupt 0 -2)
0
lisp> (interrupt 0 -3)
57300
lisp> (clock)
58500

schedule idle tasks ("setTimeOut")

The idle function has been augmented with a simple background task manager, it'll run a task 1000 ms in the future.

lisp> (at 1000 (lambda () (princ "ONE TIME")))
(4711 1000 . #func)
lisp> and while we areONE TIME typing

repeating tasks ("setInterval")

A task can be made repeating by using a negative number, it is then rescheduled after being run, in this case, at 1000 ms in the future, repeadetly, until explicitly cancelled.

lisp> (at -1000 (lambda () (princ ".")))
(61000 -1000 . #func)
lisp> (at -3000 (lambda () (princ "THREE")))
(63000 -3000 . #func)
lisp> ...THREE...THREE...THREE...THREE

cancel a task

lisp> (define never (at 1000 (lambda () (princ "NEVER"))))
(72000 1000 . #func)
lisp> (stop never)
*stop*
lisp>

Debugging

esp-lisp doesn't yet have a debugger. However, there are tools which help with debugging.

trace on, trace off

Commands that give complete trace of all evaluations and function calls and environments when running code.

print, princ, prin1

All these functions return their argument, thus can be inserted to "trace" values/variables.

mem XXX

Mem command prefix to expression XXX will print memory usage changes that is caused by evaluating XXX.

gc on, gc off

See gc traces.

(load "file.lsp" 5)

Displays command before executed, as well as result for that file.

(trace fibo) (untrace fibo)

Does a normal dynamic trace on the function fibo, printing outline of calls. Works on normal functions. The interface functions are defined in "env.lsp" file and loaded on startup.

lisp> (trace fibo)
lisp> (fibo 5)
---> (fibo n=5)
  ---> (fibo n=4)
    ---> (fibo n=3)
      ---> (fibo n=2)
        ---> (fibo n=1)
        <--- fibo ==> 1
        ---> (fibo n=0)
        <--- fibo ==> 1
      <--- fibo ==> 2
      ---> (fibo n=1)
      <--- fibo ==> 1
    <--- fibo ==> 3
    ---> (fibo n=2)
      ---> (fibo n=1)
      <--- fibo ==> 1
      ---> (fibo n=0)
      <--- fibo ==> 1
    <--- fibo ==> 2
  <--- fibo ==> 5
  ---> (fibo n=3)
    ---> (fibo n=2)
      ---> (fibo n=1)
      <--- fibo ==> 1
      ---> (fibo n=0)
      <--- fibo ==> 1
    <--- fibo ==> 2
    ---> (fibo n=1)
    <--- fibo ==> 1
  <--- fibo ==> 3
<--- fibo ==> 8
8

it's different from the "trace on" command, that traces all.