Hi all,
Does anyone know of any tiny embeddable Lisp implementation in Nim or C? Planning to embed ot in a Nim aplication.
I found make-a-lisp: https://github.com/kanaka/mal/tree/master/impls%2Fnim
Are there any others? I'm interested in the smallest size, performance is not a priority, something that could also possibly run on a microcontroller.
Thanks
The obvious one: https://github.com/Robert-van-Engelen/tinylisp#lisp-in-99-lines-of-c-and-how-to-write-one-yourself
Then i.i.r.c in one of the early(est) papers on lisp there was a lisp interpreter in lisp of ~60 lines.
You could try embedding janet using nim:
{.compile: "janet.c".}
# Import Janet types and functions
type
JanetTable* {.importc: "JanetTable", header: "janet.h".} = object
# Janet VM initialization/cleanup
proc janet_init*() {.importc: "janet_init", header: "janet.h".}
proc janet_deinit*() {.importc: "janet_deinit", header: "janet.h".}
# Core environment
proc janet_core_env*(replacements: ptr JanetTable): ptr JanetTable {.
importc: "janet_core_env", header: "janet.h".}
# Execute Janet code
proc janet_dostring*(env: ptr JanetTable, str: cstring, sourcePath: cstring,
outResult: pointer): cint {.
importc: "janet_dostring", header: "janet.h".}
const code = """
(defn sum3
"Solve the 3SUM problem in O(n^2) time."
[s]
(def tab @{})
(def solutions @{})
(def len (length s))
(for k 0 len
(put tab (s k) k))
(for i 0 len
(for j 0 len
(def k (get tab (- 0 (s i) (s j))))
(when (and k (not= k i) (not= k j) (not= i j))
(put solutions {i true j true k true} true))))
(map keys (keys solutions)))
(let [arr @[2 4 1 3 8 7 -3 -1 12 -5 -8]]
(printf "3sum of %j: " arr)
(printf "%j" (sum3 arr)))
"""
proc exec_janet(code: string) =
janet_init()
defer: janet_deinit()
let env = janet_core_env(nil)
discard janet_dostring(env,code , "main", nil)
when isMainModule:
exec_janet(code)
Expand on https://gitlab.com/pmetras/nim0.git via Vibe coding. :-)
Then you can have your own VM and scripting language you yourself barely understand! Maybe your project will even compile if you get lucky! I'm hoping that the software systems for the next airplane I fly on were vibe coded, along with the software powering my next bank transaction!
Then you can have your own VM and scripting language you yourself barely understand.
So do it via traditional coding then. The benefits are that you get a language with a nice syntax and a static typing system. Which is simply great for scripting.
There's always the stalwart µlisp (ulisp) for small 8bit microcontrollers: http://www.ulisp.com/ as well as Pico TCL https://wiki.tcl-lang.org/page/Picol
However I'd highly recommend trying WASM + Nim. There's https://github.com/beef331/wasm3 based on https://github.com/wasm3/wasm3 which should be sufficient.
Then you get a typed language and can still load code at runtime! The wasm3 interpreter will easily fit on a microcontroller like an esp32 or pico2350. Many modern STM or NRF boards should work too.
A WASM interpreter can be pretty small, though larger than a LISP/TCL since it needs to interpret int8/int16/int32 instructions whereas a LISP or TCL interpreters just need to handle a single bytecode object type. However you get lots of tooling, documentation, flexibility with WASM. You can upgrade to a JIT later for speed, etc.
Forth is kinda fun to play with but it quickly becomes a PITA for maintenance. I used it for a couple of years on a microcontroller before finding Nim. ;)
Nowadays I mostly rely on the OTA firmware updates and RPC on esp32's using Nim. Updating takes 1.5 minutes or so but is decent enough. Especially after adding utility RPC's for things like "exec-spi-cmd" makes it just as interactive for tinkering with a new peripheral but with far less fuss.
Note that the lightweight, fast, and easy RPC route is sorta unique to Nim due to typed macros. Normal RPC systems in C++, Rust, Go, etc end up being behemoths like grpc or cap'n'proto.
If you're curious free to DM or ping me on Nim embedded discord!