S-expression programming language for PC and Arduino.

Requirements...

Arduino or LinuxArduino: IDEArduino: FAT SD card≥2K RAM≥32K ROMa web browser or NodeJS (๐Ÿ”œ decomissioning)

Features...

functionaldynamic typesimmutable memorypure functionsheap-free memory≤16K program ROMround-robin schedulerinter-program messagingexplicit recursionimplicit tail-call optimisationGPL 3.0๐Ÿ”œ self-hosted compiler๐Ÿ”œ self-hosted REPL

Does not include...

homoiconicityclosuresnative compiler/REPLmacroslazinessmulti-arityfloating pointmulti-corecode import

Currently supports...

LinuxArduino MKRZeroAdafruit Feather M0

Implementation source, examples, documentation, issue tracking, and more: Github repository

Community: Programming Languages Design

Targets both Arduino as firmware or Linux as an executable. It facilitates high-level round-robin multi-tasking, loading programs from either an SD card or Linux filesystem. With a unique stack memory model, Lisp-inspired syntax, and MQTT-style internal messaging.

Similar projects

Rationale

decouplingflash-once

The vision...

Both Dylan Eddies and I are keen on developing our own pocket PC's.
Its capabilities should enable a user to, unless there's a major update, never need to reflash their device with new firmware. Instead, be able to write, compile, and run programs on the device.
It doesn't have to be performant, rather agile and comfortable to develop on. Inter-task comms should be available to help compose complex, uncoupled systems.
It should be able to fit onto something as small as a watch to anything larger, be headless, or support multiple peripherals.
It should be able to interact with peripherals through its native programs, rather than compiled support with the OS itself.

Why not real-time scheduling?

Real-time scheduling has been done to death, providing compile-time kernels.
The vision calls for the ability to write, compile, and execute programs without the need of a second device. A simple to compile, run-time-managed language achieves this.

Why use MQTT-style messaging?

MQTT is predominant in the IoT world for directing and monitoring devices, and I find programs on a computer to be no less important. This will hopefully facilitate drop-in, many-to-many relationships of logic and data, being easier to monitor, secure, and compose.

Why no native REPL, drivers, compiler, &c?

I've chosen to have the REPL, any peripheral drivers, and a compiler written in Chika, rather than a feature of the VM.
Composition of programs at runtime is a strength, and I much prefer it to baked-in drivers (e.g. "this OS works with X display and only that!").
A really nice example is SSD1306 support in uLisp.
Essentially, other than the SD card, serial, and I/O pins, there will be no other firmware-level facilitates.

Why not heap or linked-list memory?

With the 1-dimensional mind I have I realised Chika's memory can be implemented as a stack. Cheaper so leaning on C's stack, using recursion when Chika enters a function.
Using this method, global variables are not practical to implement, with immutability and purity guaranteed.
Futhermore, heap memory on the Arduino platform is cautioned against, due to code size and memory fragmentation.
However, the lack of garbage collection and dynamic memory management means items must be duplicated on the stack before processing.
uLisp uses a linked-list for managing memory, following the traditional implementation of Lisp cells. This approach was not used to avoid re-inventing the wheel, and the vision of a simpler, stack memory model.

Why separate program memories?

uLisp, again for an example saves "images", composing together at start-up, sharing memory. I like it, but I like the opportunity of sandboxed areas of memory, with inter-process communication, more.
Decoupling logic through 0-to-many relationships with other programs has its benefits, especially in avoiding recompilation to accommodate new logic. Rather, programs can just assume there will be a program implementing the logic, by emitting messages.

Why dynamic types?

I want to keep the compilation stage simple so it can eventually self-host. It's not too much overhead (3 bytes per item), and most native functions expect only certain types.

Symbolistic lore