This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
LispBM is an embeddable Lisp/Scheme-like programming language designed for microcontrollers and embedded systems. It features:
- Support for both 32-bit and 64-bit platforms
- Concurrent programming with message passing and process monitoring (inspired by Erlang)
- Multiple runtime environments: ChibiOS, FreeRTOS, ZephyrOS, bare-metal, and Linux
- Extensions system for integrating C functions
- Garbage collection with multiple strategies
The project uses multiple Makefiles in different directories for various build targets:
repl/Makefile- Builds the REPL application and interactive toolstests/Makefile- Builds test binaries for the main test suitetests/c_unit/Makefile- Builds C unit test executables
- 32-bit REPL:
makeormake all - 64-bit REPL:
make FEATURES="64" - With SDL support:
make FEATURES="sdl"ormake FEATURES="sdl 64" - Coverage build:
make FEATURES="coverage"ormake FEATURES="64 coverage" - Debug build:
make debug - Install:
make install(installs to ~/.local/bin/lbm)
Note: Multiple features can be combined, e.g., make FEATURES="64 sdl coverage"
Dependencies: libreadline, libhistory, libpng, optionally SDL2
Located in tests/ directory:
- All tests:
./run_tests.sh(comprehensive 32-bit test suite) - 64-bit tests:
./run_tests64.sh - Coverage tests:
./run_tests_cov.sh - GC tests:
./run_tests_gc.sh - Reverse GC tests:
./run_tests_gc_rev.sh - Time quota tests:
./run_tests_time.shand./run_tests64_time.sh - Image tests:
./run_image_tests.sh - REPL tests:
./run_repl_tests.sh - SDL tests:
./run_sdl_tests.sh
Built in tests/ via Makefile:
test_lisp_code_cps- Main test runnertest_lisp_code_cps_64- 64-bit versiontest_lisp_code_cps_gc- With aggressive GCtest_lisp_code_cps_revgc- With reverse GCtest_lisp_code_cps_cov- Coverage instrumented
tests/tests/- Unit test files (.lisp)tests/repl_tests/- REPL-specific teststests/image_tests/- Heap image serialization tests- Test scripts automatically build required binaries before running
The REPL test suite includes comprehensive edge case tests for fundamental operations:
- test_arithmetic_edge_cases.lisp - Tests +, -, *, /, mod with wrong types and edge cases
- test_comparison_edge_cases.lisp - Tests =, !=, <, >, <=, >= with invalid arguments
- test_list_operations_edge_cases.lisp - Tests cons, car, cdr, list, append, length, ix edge cases
- test_type_operations_edge_cases.lisp - Tests type-of, list?, number?, conversions with edge cases
- test_integer_division_float_args.lisp - Tests integer division // with floating-point arguments
- test_random_extensions_edge_cases.lisp - Tests seed and random functions with invalid inputs
- test_runtime_extensions_edge_cases.lisp - Tests runtime introspection functions with edge cases
These tests ensure robust error handling and predictable behavior for embedded applications.
All test suites should print "SUCCESS" when all tests pass:
- C unit tests (
tests/c_unit/) - Each test executable should print "SUCCESS" on completion - REPL tests (
tests/repl_tests/) - Each.lisptest file should print "SUCCESS" when passing - Image tests (
tests/image_tests/) - Should print "SUCCESS" when serialization/deserialization works - SDL tests (
tests/sdl_tests/) - Should print "SUCCESS" when graphics operations complete
When creating C unit tests in tests/c_unit/, use the standard initialization pattern:
#define _GNU_SOURCE // MAP_ANON
#define _POSIX_C_SOURCE 200809L // nanosleep?
#include <stdlib.h>
#include <stdio.h>
// ... other standard includes
#include "lispbm.h"
#include "init/start_lispbm.c"
static int test_init(void) {
return start_lispbm_for_tests();
}The start_lispbm_for_tests() function:
- Handles complete LispBM system initialization
- Sets up heap, memory management, and all subsystems
- Provides a clean environment for testing LispBM C API functions
- Should be called at the beginning of each test function that requires LispBM
- Automatically manages cleanup and reinitialization between tests
Important: Use kill_eval_after_tests() at the end of C unit tests that start threads (such as evaluation threads) which are not explicitly killed or waited for during the test. This function ensures proper cleanup by:
- Killing the evaluator thread before test function scope ends
- Preventing use-after-free errors when evaluator threads access test function local variables
- Ensuring clean test isolation and preventing state leakage between tests
Example usage:
int test_something_with_threads() {
if (!test_init()) return 0;
// Test code that may start evaluator threads
// ...
kill_eval_after_tests(); // Clean up before function returns
return 1;
}- Evaluator (
src/eval_cps.c): Continuation-passing style evaluator - Heap (
src/heap.c): Memory management and garbage collection - Symbols (
src/symrepr.c): Symbol representation and interning - Parser (
src/tokpar.c): Tokenizer and parser - Environment (
src/env.c): Variable binding management - Extensions (
src/extensions.c): C function integration system
Located in src/extensions/:
- Array operations, display functions, math, string manipulation
- Runtime system functions, TTF font rendering
- Custom extensions can be added via
lbm_add_extension()
platform/directory contains platform-specific codeplatform/linux/for POSIX systemsplatform/chibios/,platform/freertos/,platform/zephyr/for RTOSes
- Fixed-size heap of cons cells
- Separate memory pools for different object types
- Multiple GC strategies: copying GC, reverse pointer GC
- Configurable heap sizes and memory pools
DLBM64: Enable 64-bit supportDLBM_ALWAYS_GC: Force GC on every allocation (testing)DLBM_USE_GC_PTR_REV: Use reverse pointer GCDFULL_RTS_LIB: Enable full runtime libraryDVISUALIZE_HEAP: Enable heap visualization
include/lispbm.h: Main API headerlispbm.mk: Build system include filedoc/lbmref.md: Language reference documentationrepl/examples/: Example LispBM programs
trapfunction: Returns a list with error handling results- Success:
'(exit-ok value)wherevalueis the result - Error:
'(exit-error error-symbol)whereerror-symbolis the specific error type - Common error symbols:
eval_error,type_error,division_by_zero,out_of_memory, etc. - Example:
(trap (sin "string"))returns'(exit-error eval_error) - Important:
trapalways returns either'(exit-ok something)or'(exit-error something) - Some functions return error symbols directly (like
'type-symbol) without needingtrap
- Success:
progn: Traditional syntax for evaluating multiple expressions in sequence(progn expr1 expr2 expr3)evaluates all expressions and returns the last value- Allows context switches between expressions (concurrent processes can interleave)
- Brace syntax
{}: Alternative syntax forprogn{ expr1 expr2 expr3 }is equivalent to(progn expr1 expr2 expr3)- More concise and readable than traditional
progn
atomic: Likeprognbut prevents context switches(atomic expr1 expr2 expr3)evaluates expressions atomically- Disallows context switches between expressions (critical sections)
- Only 3 constructs in LispBM allow expression sequences:
progn,{}, andatomic
- Arrays: Two types available
- Byte arrays:
[1 2 3]syntax - Lisp arrays:
[| 1 2 3 |]syntax
- Byte arrays:
- Variable Assignment: Use
setqinstead ofset! - Lists: Standard Lisp lists with
'(...)syntax - Type names: Use correct LispBM type names in tests
'type-ifor integers,'type-ufor unsigned integers'type-arrayfor strings,'type-listfor lists'type-symbolfor symbols
- Single argument arithmetic:
(+ 5)returns5,(- 5)returns-5,(* 7)returns7 - Single argument comparisons:
(= 5)and(<= 5)returnt, others returnnil - Random functions:
randomreturns unsigned integers (type-u), ignores all arguments - Integer division:
//properly converts floating-point arguments to integer results - Type conversions:
to-iandto-floatreturn0/0.0f32for invalid types (no errors) - Runtime functions: Most ignore arguments;
lbm-heap-statereturnsnilfor invalid symbols
=(numerical equality): Use for comparing numbers- Example:
(= 42 42)returnst - Example:
(= 3.14 3.14)returnst - Preferred for all numerical comparisons in tests and code
- Example:
eq(identity equality): Use for comparing symbols, booleans, and object identity- Example:
(eq 'symbol 'symbol)returnst - Example:
(eq t t)returnst - Example:
(eq nil nil)returnst
- Example:
- Rule: Always use
=for numbers andeqfor non-numerical values
LispBM provides an advanced color system for use with disp-render:
-
img-color: Creates color objects for advanced rendering'regular: Simple 1-1 color mapping(img-color 'regular 0xRRGGBB)'gradient_x/'gradient_y: Dynamic gradients calculated at render time'gradient_x_pre/'gradient_y_pre: Pre-calculated gradient buffers (max width 512)- Gradients support
'repeator'mirroredrepeat modes
-
img-color-setpre: Modify colors in pre-calculated gradient buffers(img-color-setpre color pos new-color)- Position is relative to gradient start (ignores offset)
- Only works with
gradient_x_pre/gradient_y_precolor objects
-
img-color-getpre: Read colors from pre-calculated gradient buffers(img-color-getpre color pos)- Useful for testing and validating gradient color mappings
Important: Advanced color objects can only be used with disp-render, not with regular drawing functions.
This comprehensive index contains 106 distinct functions and operations organized by category for quick reference:
+- Addition of arbitrary number of values -(+ expr1 ... exprN)-- Subtraction from a value -(- expr1 ... exprN)*- Multiplication of arbitrary number of values -(* expr1 ... exprN)/- Division -(/ expr1 ... exprN)mod- Modulo operation -(mod expr1 expr2)//- Integer division (floors floating point results) -(// expr1 ... exprN)
eq- Structural equality comparison -(eq expr1 ... exprN)not-eq- Structural inequality comparison -(not-eq expr1 ... exprN)=- Numerical equality comparison -(= expr1 ... exprN)>- Greater than comparison -(> expr1 ... exprN)<- Less than comparison -(< expr1 ... exprN)>=- Greater than or equal comparison -(>= expr1 ... exprN)<=- Less than or equal comparison -(<= expr1 ... exprN)
and- Logical AND with short-circuit evaluation -(and expr1 ... exprN)or- Logical OR with short-circuit evaluation -(or expr1 ... exprN)not- Logical negation -(not expr)
list?- Test if value is a list -(list? expr)number?- Test if value is a number -(number? expr)
shl- Shift left operation -(shl value positions)shr- Shift right operation -(shr value positions)bitwise-and- Bitwise AND -(bitwise-and val1 val2)bitwise-or- Bitwise OR -(bitwise-or val1 val2)bitwise-xor- Bitwise XOR -(bitwise-xor val1 val2)bitwise-not- Bitwise negation -(bitwise-not value)
nil- Empty list and false valuet- True valuetrue- Alias fortfalse- Alias fornil
quote- Prevent evaluation of expression -(quote expr)or'expr`- Quasiquotation for templating -`expr,- Unquote within quasiquotation -,expr,@- Unquote-splicing within quasiquotation -,@expr
identity- Returns argument unchanged -(identity expr)rest-args- Access extra arguments in functions -(rest-args)or(rest-args index)set- Destructively update a binding -(set symbol-expr value-expr)setvar- Set variable value -(setvar symbol-expr value-expr)undefine- Remove global binding -(undefine symbol)eval- Evaluate expression -(eval expr)eval-program- Evaluate program with multiple expressions -(eval-program expr-list)apply- Apply function to argument list -(apply func arg-list)read- Parse string into expression -(read string)read-program- Parse string into program -(read-program string)read-eval-program- Parse and evaluate string -(read-eval-program string)type-of- Get type of value -(type-of expr)sym2str- Convert symbol to string -(sym2str symbol)str2sym- Convert string to symbol -(str2sym string)sym2u- Convert symbol to unsigned integer -(sym2u symbol)u2sym- Convert unsigned integer to symbol -(u2sym uint)gc- Trigger garbage collection -(gc)
if- Conditional expression -(if condition then-expr else-expr)cond- Multi-branch conditional -(cond (test1 expr1) ... (testN exprN))lambda- Create anonymous function -(lambda params body)closure- Function with captured environment -(closure params body env)let- Local variable binding -(let bindings body)loop- Iterative loop construct -(loop bindings condition body)define- Global variable definition -(define symbol expr)setq- Set variable (symbol not evaluated) -(setq symbol expr)progn- Sequential evaluation -(progn expr1 ... exprN){- Shorthand forprogn-{ expr1 ... exprN }}- Closing brace for{var- Local binding in progn -(var symbol expr)trap- Error handling -(trap expr)atomic- Uninterruptible execution -(atomic expr1 ... exprN)
car- Get first element of list/pair -(car list)first- Alias forcar-(first list)cdr- Get rest of list/pair -(cdr list)rest- Alias forcdr-(rest list)cons- Create cons cell -(cons car cdr).- Dot notation for pairs -(expr1 . expr2)list- Create proper list -(list expr1 ... exprN)length- Get list length -(length list)range- Generate range of integers -(range start end)append- Concatenate lists -(append list1 list2)ix- Index into list -(ix list index)setix- Destructively update list element -(setix list index value)member- Test membership in list -(member value list)setcar- Destructively update car -(setcar cons value)setcdr- Destructively update cdr -(setcdr cons value)take- Take first n elements -(take list n)drop- Drop first n elements -(drop list n)reverse- Reverse list -(reverse list)rotate- Rotate list elements -(rotate list distance)merge- Merge sorted lists -(merge comparator list1 list2)sort- Sort list -(sort comparator list)
acons- Add key-value pair to alist -(acons key value alist)assoc- Look up value by key -(assoc alist key)cossa- Look up key by value -(cossa alist value)setassoc- Destructively update alist -(setassoc alist key value)
bufcreate- Create byte buffer -(bufcreate size)buflen- Get buffer length -(buflen buffer)bufget-[X]- Get value from buffer (various types) -(bufget-u8 buffer index)bufset-[X]- Set value in buffer (various types) -(bufset-u8 buffer index value)bufclear- Clear buffer -(bufclear buffer value start end)
array- Create array from list -(array type list)mkarray- Create array with initial value -(mkarray type size init-val)
dm-create- Create defragmentable memory -(dm-create size)dm-alloc- Allocate from defragmentable memory -(dm-alloc dm size)
match- Pattern matching -(match expr (pattern1 result1) ... (patternN resultN))no_match- Default match case_- Wildcard pattern?- Variable binding pattern -(? var)
spawn- Create concurrent process -(spawn closure arg1 ... argN)spawn-trap- Spawn with exit trapping -(spawn-trap closure arg1 ... argN)self- Get current thread ID -(self)wait- Wait for process to finish -(wait pid)yield- Yield execution for microseconds -(yield microseconds)sleep- Sleep for seconds -(sleep seconds)exit-ok- Exit thread successfully -(exit-ok value)exit-error- Exit thread with error -(exit-error error)kill- Terminate another thread -(kill thread-id value)
send- Send message to process -(send pid message)recv- Receive message (blocking) -(recv (pattern1 result1) ... (patternN resultN))recv-to- Receive with timeout -(recv-to timeout (pattern1 result1) ... (patternN resultN))set-mailbox-size- Change mailbox size -(set-mailbox-size size)
flatten- Serialize value to byte array -(flatten value)unflatten- Deserialize from byte array -(unflatten flat-value)
macro- Define macro -(macro params body)
call-cc- Call with current continuation -(call-cc func)call-cc-unsafe- Unsafe call with current continuation -(call-cc-unsafe func)
read_error- Reading/parsing errortype_error- Type mismatch erroreval_error- Evaluation errorout_of_memory- Memory exhaustion errorfatal_error- Fatal system errorout_of_stack- Stack overflow errordivision_by_zero- Division by zero errorvariable_not_bound- Unbound variable error
@const-start- Start of constants section@const-end- End of constants sectionmove-to-flash- Move value to flash memory -(move-to-flash symbol ...)
Note: Special forms (like if, cond, lambda, let, etc.) are evaluated differently than regular functions. Many functions support variable arguments. Destructive operations modify data in-place.
When writing or modifying C code in this project, use the following indentation style (consistent with eval_cps.c):
- 2-space indentation for all code blocks (no tabs)
- Consistent spacing throughout all nesting levels
- Compact style suitable for deeply nested continuation-passing style code
Examples:
static void function_name(params) {
if (condition) {
while (loop_condition) {
if (nested_condition) {
statement;
}
}
}
}
switch (value) {
case CONSTANT:
statement;
break;
case OTHER:
statement;
break;
default:
statement;
}- Modify core code in
src/ - Build library:
make clean && make - Test changes:
cd tests && ./run_tests.sh - For REPL testing:
cd repl && make && ./repl - For coverage:
cd tests && ./run_tests_cov.sh