New features for 2026


#1

Some of the new microcontrollers coming out are real beasts, with PSRAM and multiple cores that run at speeds that put my first computers to shame. In the past we’ve talked about new features to take advantage of these capabilities, I’d like to bring up the idea again to see if anyone is interesting in participating in making them a reality.

Multicore support

A REPL that runs on each core, and can channel messages between cores seems imminent. FreeRTOS already has most of the components needed to achieve this. You can pin Tasks to cores and create a message queue for passing messages between cores. I’d imagine we would want to create separate memory arena for each core which with independent Garbage Collectors. This is actually a bonus, because it allows uLisp to utilize more of the PSRAM without the overhead of large mark and sweep cycles.

Package support

I’d love to see more thought going into storing uLisp on SD cards, loading the code into namespaces, and then removing it when needed. I’ve worked out most of the details, but just need to wrap it all up into something that has it’s own namespace.

Structs support

I’d love to see the ability to utilize memory more efficiently for functional programming styles that don’t need to mutate variables in place. I think we can use smart pointers with a word or byte count that works well with our existing GC.

Macros

This functionality is mostly done, I’m curious why there isn’t an #ifdef for including macros in uLisp on more performant devices.

Interrupt handling

Interrupt support seems like a big missing component for microcontrollers. Could we use streams for this? Flags? Or is this just something that always needs to be done in C, and we should create some tutorials on how to integrate them with lisp functions?

If all of these are too advanced for uLisp, should we create a separate repo for community driven advanced branches?


#2

Hello,

Just a quick observation regarding multicore. The Zephyr RTOS has already started adding support for the Arduino core. So perhaps at some point, uLisp will be able to natively use some features from it.

More details here:

Best regards,


#3

I would very much welcome packages and structs. Namespaces would be interesting as well. I’d be interested in seeing how we can handle structs effectively compared to something like the lambda enclosed case statements I’ve been researching.

Macros I’m not sold on in that there’s no compile time phase in uLisp right now, so I’m not sure how that would be so different than running eval on some runtime constructed functions.

Multicore I would almost oppose if it complicates the implementation, if we need speed there should be much more straightforward optimizations we can consider before that.

Interrupts believe should be done in C, I don’t see how uLisp could be fast enough and/or interrupt safe for that to work unless there’s assembly compilation. They’re almost always really specific, I’d just look for C tutorials on how to write ISRs.

Features I’d like to see

would be some kind of optimizations for function lookups, like perhaps an in-flash hash table or sort + binary search? If I understand correctly right now symbol look up is just a linear scan through the symbol table, so it should be straightforward to do it faster? I could be wrong but I feel there could be some easy wins that would speed things up significantly without complicating the implementation too much. Doesn’t do that much for performance, see post below.

Another thing I’ve been thinking about is support for framebuffers with graphics. I don’t know if this needs to be in an official release since we write the functions ourselves, but drawing to a frame buffer and then writing it to the screen tends to be less flickery than writing to the screen piece by piece. IIRC the adafruit graphics library already has support for framebuffers, and maybe the other graphics libraries do as well. We don’t need to support them for every device, just ones with big enough screens and memory capacity.

I’d also like string indexing that isn’t just linearly scanning the string from the beginning every time, but that seems complicated because of how uLisp stores strings, and they’re often aren’t so long as to need that sort of optimization. It just bothers me from a conceptual standpoint.


#4

That linear search only happens once (during read-in) so it’s still fairly fast, but built-in symbols are still stored only as an index into the table so there’s still a few operations being done beyond a single pointer lookup.

Also, binary search would require that the table be sorted by the symbol name, which is just weird to require, especially that we have separate extension files and tables now.


#5

So after I suggested the idea I figured I might as well try it, and I did https://gist.github.com/hasn0life/168f7a771d830ff3ee092803978c8ea2
(RP2040 ARM is the only platform I got it working on, modifying the code from David’s test version)

It’s faster on the benchmarks but not by too much, around 10-20% in my attempts

The drawbacks are that you have to sort the built in functions and you have to give the appropriate indexes to the builtins_t enum. This is complicated by the fact that the different platforms have different builtin functions that occur in different positions, which change the indexes of the builtins_t enum.

So it does something but not as much as I thought it would. Could still be worthwhile if we find a way to keep everything sorted and indexed without too much overhead, but not a huge priority


#6

A macro is a good way to write aesthetically pleasing but efficient code. Macros would be efficient if uLisp allows for calls to macros to be replaced with their macro expansion after they are initially evaluated.

A very simple example would be replacing confusing car and cdr code when accessing data:

(defmacro get-state-field (location) 
      (list `cadr location))

(get-state-field '("Chico" "CA" 95926))

In fact many commonly used Lisp functions like defun, prog, cond, first, second and even defmacro are commonly done with macros. The destructuring done with the new function bind could have been implemented by anyone without touching the firmware by using macros.

Macros clean up the function look up table.

Clojure uses a hash lookup table. This makes lookup O(1), and allows equality checks to be fast. It also allows you to use keywords. A Trie (prefix tree) would be another option as it allows code completion and spellchecking to be implemented with the same structure. Not sure if either option would really improve lookup performance performance or implementation complexity though. Here is an implementation of Trie datastructure in C.

I believe adding structs would allow implementing more performant strings and framebuffers a possibility, as you could set aside a contiguous memory space for a matrix array (say 320x480 array) for a framebuffer, or a vector of exactly the right size to store your immutable string of characters. This allows you to use one pointer to the start of the structure, and pointer arithmetic to traverse the structure in place from the one index.

Each vector would have a integer value that gives the count of how many cells (words or bytes?) the vector takes up. These would be removed in one pass from the Free-list. We would have to add the type “Struc” and possibly “Vec” to the enumerated types, but there are something like 128 values possible, after removing the one garbage collection bit in this byte.

It definitely adds a bit of extra complexity to the garbage collection and cell allocation, but it would improve storage usage and speeds. Honestly, I’m still unsure if its entirely useful for uLisp, as most of the needs that I have are things like framebuffers, where they should be statically allocated in C by the graphics library, and then called to with Lisp API’s.

We had these sort of worked out back in 2017 (http://www.ulisp.com/show?1MWO), and now with the addition of defcode and user defined streams I can see these being more capable. With defcode the interrupt callback function could be replaced with some more performant assembly, and with streams one could just append data to a stream, such as key presses, or timestamps of when the interrupt was fired.

It would likely be very little change at all, only adding a queue used to send messages to the new REPL on the second core, and setting an Environment and REPL pinned to the second core that would read those messages. Inter core queues and Tasks pinned to cores are both built into FreeRTOS, which ESP32’s and RP2040’s are already running behind the scenes.

The nice thing about this would be you could run a webserver or other long running task on your second core, and leave the primary loop for interacting with the user. Without using tasks, the second core is left unused (except for WIFI/BLE transmissions behind the scene).


#7

Something non-invasive that might be still very useful, and indeed, has been so for me in other systems: parallel mapping / parallel let . Semantics exactly the same as for mapping & for let, race conditions to be left as an exercise to the user (i.e. if anybody talks to pins etc, that’s THEIR issue), just gain parallel code execution.


#8

Macros

Ok I agree with the concept of macros, but how would that work in terms of implementation?

Macros would be efficient if uLisp allows for calls to macros to be replaced with their macro expansion after they are initially evaluated.

I guess I’m just wondering how that would work with no compile time phase? Would they be expanded when they’re entered? Would they show up on the device as macros or their expanded versions?

Structs

I think there’s two different concepts here: one is holding a bunch of data in contiguous memory as opposed to a linked list, the other is a data structure with known fields and named field access. The two can be related depending on implementation, and I wouldn’t mind support for either. But I am specifically talking about the latter when I talk about structs, even if they’re not necessarily implemented in contiguous memory. Technically the macro example you gave (get-state-field '("Chico" "CA" 95926)) can allow for named field access, so maybe just having macros would satisfy that requirement. That said I do prefer the syntax of the “lambda enclosed case statements” I mentioned earlier where you could write something like (location 'state). If we can have support for efficient construction and usage of those types of structs I would like that, though I suspect that macros can help there as well.

Interrupts

I see that works but I’d be suspicious of using uLisp based ISRs for anything more complicated than the examples given because they would be fundamentally non deterministic in terms of real time requirements, which would be important for basic things like reading characters from a serial or I2C port for example (which is already done in C right now). But I suppose there can be other valid use cases.

Multicore

If it can be done with minimal complexity in the implementation I guess I would support it, and I do like your examples.