LambLisp 01 Red Fox Alpha
From AI to Actuator
Loading...
Searching...
No Matches
Lamb Class Reference

Public Types

typedef Sexpr_t(* Mop3st_t) (Lamb &lamb, Sexpr_t sexpr, Sexpr_t env_exec)
typedef Sexpr_t(* Mop3st_argv_t) (Lamb &lamb, Sexpr_t *argv, int nargs, Sexpr_t env_exec)

Public Member Functions

Sexpr_t cdar (Sexpr_t l)
Sexpr_t cddr (Sexpr_t l)
Sexpr_t caddr (Sexpr_t l)
Sexpr_t cdddr (Sexpr_t l)
Sexpr_t cadddr (Sexpr_t l)
Sexpr_t cddddr (Sexpr_t l)
Sexpr_t load (Charst_t name, Sexpr_t env_exec, LL_int32 verbosity=0)
Sexpr_t mk_input_string_port (Charst_t inp, Sexpr_t env_exec)
Sexpr_t mk_output_string_port (Sexpr_t env_exec)
Sexpr_t mk_input_bytevector_port (ByteVec_t src, LL_int32 n, Sexpr_t env_exec)
Sexpr_t mk_output_bytevector_port (Sexpr_t env_exec)
void diag_memory_report ()
Arduino-compatible loop-based control interface.
Sexpr_t setup (void)
Sexpr_t loop (void)
void end (void)
 ~Lamb ()
A few foundational functions for embedded debugging.
void log (const char *fmt,...) CHECKPRINTF_pos2
void printf (const char *fmt,...) CHECKPRINTF_pos2
bool debug (void)
void debug (bool onoff)
Information about the current build.
bool build_isDebug ()
unsigned long build_version ()
unsigned long build_UTC ()
unsigned long build_pushUTC ()
const char * build_buildRelease ()
const char * build_buildDate ()
const char * build_pushDate ()
Cell constructors

Garbage collection may happen at any time during the execution of the Cell constructors. The cell constructors that accept S-expression arguments will protect those arguments from GC during the operation, using the gc root push/pop functions.

void expand ()
Sexpr_t tcons (LL_int32 typ, Word_t a, Word_t b, Sexpr_t env_exec)
Sexpr_t tcons (LL_int32 typ, Sexpr_t a, Sexpr_t b, Sexpr_t env_exec)
Sexpr_t cons (Sexpr_t a, Sexpr_t b, Sexpr_t env_exec)
void gc_root_push (Sexpr_t p)
Sexpr_t materialized (Sexpr_t box)
void gc_root_pop (LL_int32 n=1)
LL_int32 gc_root_depth ()
LL_int32 gc_root_depth_max ()
void gc_root_depth_max_reset ()
void gc_root_setdepth (LL_int32 n)
void gc_idle_task_us (LL_int32 us, Sexpr_t env_exec)
void gc_collect (Sexpr_t env_exec)
uint32_t eval_progress () const
LL_int32 gc_Qm () const
void gc_pass_n (LL_int32 nsteps)
LL_int32 mem_cell_block_size ()
LL_int32 mem_n_cell_blocks ()
LL_int32 mem_total_cells ()
LL_int32 mem_ngc ()
LL_int32 mem_markstack_grows ()
LL_int32 mem_urgent_calls ()
LL_int32 mem_urgent_max_us ()
void mem_diag_quiet (Bool_t q)
LL_int32 mem_startmark_max_us ()
LL_int32 mem_startmark_nroots ()
LL_int32 mem_extraroots_max_us ()
LL_int32 mem_extraroots_pushes ()
LL_int32 mem_nfree ()
LL_int32 mem_extension_block_size ()
void mem_set_extension_block_size (LL_int32 sz)
void mem_expand_to_n_blocks (LL_int32 n, Sexpr_t env_exec)
LL_int32 mem_max_cell_blocks ()
void mem_set_max_cell_blocks (LL_int32 n)
LL_int32 mem_gc_budget_ns ()
LL_int32 mem_dbg_urgent_max_us ()
LL_int32 mem_dbg_urgent_calls ()
LL_int32 mem_dbg_urgent_bulk ()
unsigned long mem_dbg_t_gc_total_us ()
LL_int32 mem_dbg_nfree ()
LL_int32 mem_dbg_total_cells ()
LL_int32 mem_dbg_amax ()
LL_int32 mem_dbg_live_marked ()
unsigned long mem_dbg_n_idle ()
unsigned long mem_dbg_n_work ()
LL_int32 mem_dbg_t_mark_ns ()
LL_int32 mem_dbg_t_sweep_ns ()
LL_int32 mem_dbg_mark_q ()
LL_int32 mem_dbg_sweep_q ()
LL_int32 mem_dbg_yuasa_M ()
LL_int32 mem_dbg_yuasa_N ()
void mem_set_gc_budget_ns (LL_int32 ns)
LL_int32 mem_gcload_target_pct ()
void mem_set_gcload_target_pct (LL_int32 pct)
LL_int32 verbosity ()
void set_verbosity (LL_int32 v)
void set_repl_quiet (bool q)
bool repl_quiet (void) const
LL_int32 ncg_frame_pool_n ()
void ncg_frame_pool_init (LL_int32 n)
void ncg_frame_pool_free ()
A subset of the car/cdr accessors for list cells. This is the subset used within LambLisp.
Sexpr_t car (Sexpr_t l)
Sexpr_t cdr (Sexpr_t l)
Sexpr_t prechecked_anypair_get_car (Sexpr_t c)
Sexpr_t prechecked_anypair_get_cdr (Sexpr_t c)
Sexpr_t caar (Sexpr_t l)
Sexpr_t cadr (Sexpr_t l)
Non-allocating slot readers

Read a C value straight out of a pair's slot. Use these instead of car(x)->mustbe_int32(): when the slot holds an embedded int or float, car/cdr must BOX it into a fresh cell, which these skip entirely. Behaviour is otherwise identical – same type checks, same errors – so they are a drop-in replacement wherever the result is wanted as a C value rather than a cell.

WHICH TYPES NEED A READER IS FIXED BY THE FLAGS, NOT BY A LIST OF NAMES. A slot embeds just two things – F_CAR_IS_INT and F_CAR_IS_FLOAT, all that car_is_embedded() tests – so mustbe_codepoint, mustbe_any_str_t and mustbe_Char_t cannot box, have no reader, and would gain nothing from one. Read the flags rather than this sentence: a type added later is covered by the first and not by the second. For a composition, apply the car_ form to the inner expression – cadr(x)->mustbe_int32() is car_mustbe_int32(cdr(x)).

THE mustbe_/coerce_ CONVERSION WAS COMPLETE ON 2026-10-16. THE as_ FORM WAS NOT, AND THIS COMMENT CLAIMED OTHERWISE FOR FIVE DAYS – corrected 2026-10-21 after finding FIFTY live sites of the shape lamb.car(x)->as_int32(): ll_vm_mop3_port.cpp 21, ll_vm_mop3_extra.cpp 10, ll_xmop3_WiFi.cpp 7, ll_vm_mop3_bignum.cpp 5, ll_xmop3_PCA9685.cpp 3, ll_xmop3_WS2812.cpp 3, ll_vm_rdr.cpp 1, plus ten two-step folds in ll_vm_mop3_rxrs.cpp that bound the result to a local first. All now converted.

WHY THE WRONG CLAIM SURVIVED, WHICH IS THE PART WORTH KEEPING: THE REGEX BELOW USED TO READ (mustbe|coerce)_ AND SO COULD NOT MATCH as_ AT ALL. The sentence asserted completeness and shipped, in the same breath, the one check that was guaranteed to agree with it. A completeness claim is exactly what removes the reason to look again, so it has to be checkable by something that can say no – and the alternatives a grep must cover are as_, mustbe_ and coerce_, because as_ is the UNCHECKED reader and therefore the easiest one to reach for. as_ is now in the pattern.

WHAT A MATCH MEANS, since most matches are correct: only LL_int32 and LL_float32 are ever embedded, so as_int64, as_float64, codepoint, Char_t, any_str_t and Ptr_t come back as traced pointers and box NOTHING – 26 such sites are deliberately left as they are. A further 24 matches are these readers' own fallback paths, in this file. So triage a match by the WIDTH, not by the count.

BEFORE REPORTING A REMAINING COUNT, note that an argument class of [^()]* misses the nested form car(cdr(x))->mustbe_int32() and under-reports with no sign that it did – that grep answered 18 on the day the true match count was 29. Allow one nested level: ((?:lamb.)?c[ad]+r\s*((?:[^()]|([^()]*))*))\s*->\s*(as|mustbe|coerce)_(\w+)

Rationale, measurements and the migration record: [B846].

int car_type (Sexpr_t c)
int cdr_type (Sexpr_t c)
LL_int32 car_mustbe_int32 (Sexpr_t c)
LL_int32 cdr_mustbe_int32 (Sexpr_t c)
LL_float32 car_mustbe_float32 (Sexpr_t c)
LL_float32 cdr_mustbe_float32 (Sexpr_t c)
LL_int32 car_coerce_int32 (Sexpr_t c)
LL_int32 cdr_coerce_int32 (Sexpr_t c)
LL_int64 car_coerce_int64 (Sexpr_t c)
LL_int64 cdr_coerce_int64 (Sexpr_t c)
LL_float32 car_coerce_float32 (Sexpr_t c)
LL_float32 cdr_coerce_float32 (Sexpr_t c)
LL_float64 car_coerce_float64 (Sexpr_t c)
LL_float64 cdr_coerce_float64 (Sexpr_t c)
The "bang" functions are the "mutators" of GC literature.
void set_car_bang (Sexpr_t c, Sexpr_t val)
void set_cdr_bang (Sexpr_t c, Sexpr_t val)
void vector_set_bang (Sexpr_t vec, LL_int32 k, Sexpr_t val)
Sexpr_t reverse_bang (Sexpr_t l)
Equivalence tests and sequential search
Sexpr_t eq_q (Sexpr_t obj1, Sexpr_t obj2)
Sexpr_t eqv_q (Sexpr_t obj1, Sexpr_t obj2)
Sexpr_t equal_q (Sexpr_t obj1, Sexpr_t obj2)
Sexpr_t assq (Sexpr_t obj, Sexpr_t alist)
Interned symbols

There are just a few operations on symbols:

  • Remember a new symbol.
  • Check if symbol has already been seen.
  • Compare 2 symbols for equality.
  • At evaluation time, lookup the symbol in the current environment.

For best performance, symbols in LambLisp are interned. This means a single copy of the symbol's characters are stored in a data structure containing the set of all interned symbols. Once interned, symbols can be tested for equality by address rather than character-by-character.

Traditionally, the data structure was called oblist if an association list was used, or called obarray if an array was used. Arrays allow for a hash table implementation, which greatly reduces search time, and can be further optimized if the array size is a power of 2 (2^n).

LambLisp uses the term oblist for the interned symbol table, implemented using a 2^n hash table. Symbol hashes are computed only once and are stored with the symbol, speeding runtime lookups.

Sexpr_t oblist_query (Sexpr_t oblist, const char *identifier, Bool_t force, Sexpr_t env_exec)
Sexpr_t oblist_test (Sexpr_t oblist, const char *identifier, Sexpr_t env_exec)
Sexpr_t oblist_intern (Sexpr_t oblist, const char *identifier, Sexpr_t env_exec)
Sexpr_t oblist_analyze (Sexpr_t oblist, LL_int32 verbosity, Sexpr_t env_exec)
Hierarchical Dictionary type

LambLisp's high-performance hierarchical dictionary implementation is used internally to represent the runtime environment. Dictionaries are also directly usable in the Lisp applications, and the dictionary data type is the basis for the LambLisp Object System (LOBS).

Sexpr_t dict_new (LL_int32 framesize, Sexpr_t env_exec)
Sexpr_t dict_new (Sexpr_t env_exec)
Sexpr_t dict_add_empty_frame (Sexpr_t dict, LL_int32 framesize, Sexpr_t env_exec)
Sexpr_t dict_add_empty_frame (Sexpr_t dict, Sexpr_t env_exec)
Sexpr_t dict_add_keyval_frame (Sexpr_t dict, Sexpr_t keys, Sexpr_t vals, Sexpr_t env_exec)
Sexpr_t dict_add_keyval_frame_argv (Sexpr_t dict, Sexpr_t keys, Sexpr_t *argv, int nargs, Sexpr_t env_exec)
Sexpr_t dict_add_vector_frame (Sexpr_t parent, Sexpr_t names, Sexpr_t *vals, int ncaps, Sexpr_t env)
Sexpr_t dict_add_alist_frame (Sexpr_t dict, Sexpr_t alist, Sexpr_t env_exec)
Sexpr_t dict_append_bang (Sexpr_t d1, Sexpr_t d2, Sexpr_t env_exec)
void dict_bind_bang (Sexpr_t dict, Sexpr_t key, Sexpr_t value, Sexpr_t env_exec)
void dict_bind_local_bang (Sexpr_t dict, Sexpr_t key, Sexpr_t value, Sexpr_t env_exec)
void dict_rebind_bang (Sexpr_t dict, Sexpr_t key, Sexpr_t value, Sexpr_t env_exec)
void dict_bind_alist_bang (Sexpr_t dict, Sexpr_t alist, Sexpr_t env_exec)
void dict_rebind_alist_bang (Sexpr_t dict, Sexpr_t alist, Sexpr_t env_exec)
Sexpr_t dict_ref_q (Sexpr_t dict, Sexpr_t key)
Sexpr_t dict_ref (Sexpr_t dict, Sexpr_t key)
Sexpr_t dict_keys (Sexpr_t dict, Sexpr_t env_exec)
Sexpr_t dict_values (Sexpr_t dict, Sexpr_t env_exec)
Sexpr_t dict_to_alist (Sexpr_t dict, Sexpr_t env_exec)
Sexpr_t dict_to_2list (Sexpr_t dict, Sexpr_t env_exec)
Sexpr_t alist_to_dict (Sexpr_t alist, Sexpr_t env_exec)
Sexpr_t twolist_to_dict (Sexpr_t twolist, Sexpr_t env_exec)
Sexpr_t dict_analyze (Sexpr_t dict, LL_int32 verbosity=0)
Sexpr_t integrity_check (Sexpr_t env_exec, LL_int32 verbosity=1)
Reading and writing
Sexpr_t read (LL_Port &src, Sexpr_t env_exec)
Sexpr_t write_or_display (Sexpr_t sexpr, Bool_t do_write, WriteMode mode=WriteMode::Cyclic)
Sexpr_t write_simple (Sexpr_t sexpr)
String sprintf (Charst_t fmt, Sexpr_t args, Sexpr_t env_exec)
Sexpr_t printf (Sexpr_t args, LL_Port &outp)
Sexpr_t printf (Sexpr_t args)
Evaluation and function application
Sexpr_t eval (Sexpr_t sexpr, Sexpr_t env_exec)
Sexpr_t eval_list (Sexpr_t args, Sexpr_t env_exec)
Sexpr_t apply_proc_partial (Sexpr_t proc, Sexpr_t sexpr, Sexpr_t env_exec)
Sexpr_t map_proc (Sexpr_t proc, Sexpr_t lists, Sexpr_t env_exec)
Querying underlying system features defined by Scheme.
Sexpr_t r5_base_environment ()
Sexpr_t r5_interaction_environment ()
Sexpr_t lamb_oblist ()
Sexpr_t current_input_port ()
Sexpr_t current_output_port ()
Sexpr_t current_error_port ()
void set_current_input_port (Sexpr_t p)
void set_current_output_port (Sexpr_t p)
void set_current_error_port (Sexpr_t p)
bool reader_fold_case ()
void reader_fold_case_set (bool v)
Useful list processing used internally by the LambLisp virtual machine.
Sexpr_t append (Sexpr_t sexpr, Sexpr_t env_exec)
Sexpr_t append2 (Sexpr_t lis, Sexpr_t obj, Sexpr_t env_exec)
Sexpr_t list_copy (Sexpr_t sexpr, Sexpr_t env_exec)
Sexpr_t list_analyze (Sexpr_t sexpr, Sexpr_t env_exec)
Sexpr_t list_to_vector (Sexpr_t l, Sexpr_t env_exec)
Sexpr_t vector_to_list (Sexpr_t v, Sexpr_t env_exec)
Sexpr_t vector_copy (Sexpr_t from, Sexpr_t env_exec)
Sparse vectors

The sparse vector representation is a sorted association list, in which the car of each pair is the vector index and the cdr is the vector value at that index.

Sexpr_t vector_to_sparsevec (Sexpr_t vec, Sexpr_t skip, Sexpr_t env_exec)
Sexpr_t sparsevec_to_vector (Sexpr_t alist, Sexpr_t fill, Sexpr_t env_exec)
Makers for error types

These functions will sprintf a message into a buffer, and return an error object containing the message. They differ in that one (mk_error) creates a new error object, and the other (mk_syserror) avoids use of the memory manager by reusing the system error singleton.

Sexpr_t mk_error (Sexpr_t env_exec, Sexpr_t irritants, const char *fmt,...) CHECKPRINTF_pos4
Sexpr_t mk_error (Sexpr_t env_exec, const char *fmt,...) CHECKPRINTF_pos3
Sexpr_t mk_syserror (const char *fmt,...) CHECKPRINTF_pos2
Sexpr_t tag_error (Sexpr_t err, const char *kind)
Sexpr_t mk_syserror (Sexpr_t irritants, const char *fmt,...) CHECKPRINTF_pos3
Makers for simple compact types with embedded values, without external storage or dependencies

These types all have their values embedded in the Cell. Note that other types may also hold immediate values, but are "less simple" because they are C++ subtypes that are variations on the Lisp-declared types.

Sexpr_t gensym (Sexpr_t env_exec)
Sexpr_t mk_bool (Bool_t b, Sexpr_t env_exec)
Sexpr_t mk_character (ll_codepoint_t cp, Sexpr_t env_exec)
Sexpr_t mk_integer (LL_int64 i, Sexpr_t env_exec)
Sexpr_t mk_int32 (LL_int32 i, Sexpr_t env_exec)
Sexpr_t mk_int64 (LL_int64 i, Sexpr_t env_exec)
Sexpr_t mk_float32 (LL_float32 r, Sexpr_t env_exec)
Sexpr_t mk_float64 (LL_float64 d, Sexpr_t env_exec)
Sexpr_t mk_complex (LL_float32 r, LL_float32 i, Sexpr_t env_exec)
void rat_store_i64 (Sexpr_t c, bool is_num, LL_int64 v, Sexpr_t env_exec)
Sexpr_t mk_rational (LL_int64 num, LL_int64 den, Sexpr_t env_exec)
Sexpr_t mk_number (Charst_t str, Sexpr_t env_exec)
Sexpr_t mk_sharp_const (Charst_t name, Sexpr_t env_exec)
Makers for heap storage types

In principle, these types require space from the system heap. At GC time, they may also require specialized marking, and also specialized sweeping.

As an optimization, LambLisp also implements immediate types. When the size required for the object is small, the internal bytes of the Cell can be used to hold the contents, avoiding heap operations. Using immediate types in this way has been common practice for integers and real numbers. LambLisp makes some additional use of immediate types. Immediate types are a LambLisp internal optimization, and they are all subtypes of the main Lisp type.

The types that employ the immediate optimization are:

  • strings, providing up to 9 content bytes plus trailing 0 (32 bit word) or 22 content bytes (64 bit).
  • bytevectors a count plus up to 9 or 22 content bytes.
  • vectors of length 0, 1 or 2.

There are also EXTERNAL versions of those data types. These data types can be used to access data that should not be garbage collected, such as data contained in the application image, or C++ objects provided by another application.

A variant of the EXTERNAL types is also provided, so that objects created in C++ can be passed to LambLisp, along with an optional deleter function. In C++, the deleter is best implemented as a one-line C++ lambda function accepting 1 argument (a void* pointer to a C++ object). The deleter function casts the void* pointer to match the C++ object type, and applies the C++ delete or delete[] operators to free the object space and any dependent resources.

By passing the C++ objects to LambLisp, they can be automatically garbage collected when LambLisp has finished with them.

Sexpr_t mk_string (Sexpr_t env_exec, const char *fmt,...) CHECKPRINTF_pos3
void * payload_alloc (size_t nbytes, const char *who, Sexpr_t env_exec)
Sexpr_t mk_string (LL_int32 k, Charst_t src, Sexpr_t env_exec)
Sexpr_t mk_string (String s, Sexpr_t env_exec)
Sexpr_t mk_symbol_or_number (Charst_t str, Sexpr_t env_exec)
Sexpr_t mk_symbol (Charst_t str, Sexpr_t env_exec)
Sexpr_t mk_bytevector (LL_int32 k, Sexpr_t env_exec)
Sexpr_t mk_bytevector (LL_int32 k, Bytest_t src, Sexpr_t env_exec)
Sexpr_t mk_bytevector (LL_int32 k, LL_int32 fill, Sexpr_t env_exec)
Sexpr_t mk_bytevector_ext (LL_int32 k, Bytest_t ext, Sexpr_t env_exec)
Sexpr_t mk_intvector (LL_int32 k, Sexpr_t env_exec)
Sexpr_t mk_realvector (LL_int32 k, Sexpr_t env_exec)
Sexpr_t mk_intvector (LL_int32 k, LL_int32 fill, Sexpr_t env_exec)
Sexpr_t mk_realvector (LL_int32 k, LL_float32 fill, Sexpr_t env_exec)
Sexpr_t mk_vector (LL_int32 len, Sexpr_t fill, Sexpr_t env_exec)
Sexpr_t mk_hashtbl (LL_int32 len, Sexpr_t fill, Sexpr_t env_exec)
Sexpr_t mk_serial_port (Sexpr_t env_exec)
Sexpr_t mk_input_file_port (Charst_t name, Sexpr_t env_exec)
Sexpr_t mk_output_file_port (Charst_t name, Sexpr_t env_exec)
Network port makers (TCP and TLS)

These makers are available when LL_WIFI (ESP32 WiFi) or LL_POSIX (Linux/macOS) is defined. They are exposed in the Scheme interaction environment as open-tcp-client-port, open-tcp-server-port, server-accept, open-tls-client-port, and open-tls-server-port.

All network ports are bidirectional T_PORT_HEAP objects and work with the standard R7RS I/O procedures (read, write, read-char, write-string, close-port, etc.).

The LLIP (LambLisp Interaction Protocol) layer (llip-server.scm / llip-client.scm) is built entirely on top of these primitives. It speaks a line-oriented S-expression protocol over plain TCP (development) or TLS (production).

Wire protocol summary:

  • Server sends: (hello "llip/1.0") on connect.
  • Client sends: (auth "token"). Server replies (ok) or closes.
  • Each request is one S-expression per line; response is (ok ...) or (error "msg").
  • Supported ops: ping, bye, ls, stat, read, write, append, delete, rename, load, eval.
  • (bye) triggers a graceful close; EOF from the client also closes the session.
See also
data/llip-server.scm – Scheme server (load on the target board).
data/llip-client.scm – Scheme client (load on the host or peer board).
ll_tests/llip-tests.scm – integration test suite.
Sexpr_t mk_udp_port (unsigned local_port, Sexpr_t env_exec)
Sexpr_t mk_tcp_client_port (Charst_t host, unsigned port, Sexpr_t env_exec)
Sexpr_t mk_tcp_server_port (unsigned port, Sexpr_t env_exec)
Sexpr_t server_accept_port (LL_Port *server, Sexpr_t env_exec)
Sexpr_t mk_tls_client_port (Charst_t host, unsigned port, Charst_t ca_cert_pem, Sexpr_t env_exec)
Sexpr_t mk_tls_server_port (unsigned port, Charst_t cert_pem, Charst_t key_pem, Sexpr_t env_exec)
Makers for interface to native procedures and C++ objects.
Sexpr_t mk_Mop3_procst_t (Mop3st_t f, Sexpr_t env_exec)
Sexpr_t mk_Mop3_procst_argv_t (Mop3st_t f, Mop3st_argv_t fa, Sexpr_t env_exec)
Sexpr_t mk_Mop3_nprocst_t (Mop3st_t f, Sexpr_t env_exec)
Sexpr_t mk_cppobj (void *obj, CPPDeleterPtr deleter, Sexpr_t env_exec)
Makers for pair types.

These pair types all have the same structure for construction and garbage collection purposes, but are not Scheme pair types. LambLisp*'s scalable type system presents opportunities for efficiency when implementing common operations on lists. These are executed by the LambLisp virtual machine at C++ speed, rather than at the slower speed of the Lisp evaluation loop.

  • procedures ... the evaluator must be able to identify these directly.
  • dictionaries ... often containing cycles when used as environments, so a depth limit is imposed when traversing.
  • thunks ... used in combination with the trampoline technique to implement tail recursion.

Note that there is no need for mk_pair(), it is just cons().

Sexpr_t mk_macro (Sexpr_t nlam, Sexpr_t env_nlam, Sexpr_t env_exec)
Sexpr_t mk_procedure (Sexpr_t formals, Sexpr_t body, Sexpr_t env_proc, Sexpr_t env_exec)
Sexpr_t mk_nprocedure (Sexpr_t formals, Sexpr_t body, Sexpr_t env_nproc, Sexpr_t env_exec)
Sexpr_t mk_thunk_sexpr (Sexpr_t sexpr, Sexpr_t env_thunk, Sexpr_t env_exec)
Sexpr_t mk_thunk_body (Sexpr_t body, Sexpr_t env_thunk, Sexpr_t env_exec)
Sexpr_t mk_dict (Sexpr_t frame, Sexpr_t base, Sexpr_t env_exec)
Sexpr_t mk_dict (LL_int32 framesize, Sexpr_t env_exec)
Macro support

These macro primitives behave as described in Steele's Common Lisp.

Sexpr_t macroexpand (Sexpr_t form, Sexpr_t env_exec)
Sexpr_t macroexpand1 (Sexpr_t form, Sexpr_t env_exec)
Sexpr_t macroexpand (Sexpr_t proc, Sexpr_t args, Sexpr_t env_exec)
Sexpr_t macroexpand1 (Sexpr_t proc, Sexpr_t args, Sexpr_t env_exec)

Native Code Generator (NCG)

NcgFrame * ncg_frame_top = nullptr
NcgFrame * ncg_frame_pool_head = nullptr
int ncg_frame_pool_count = 0
NcgErrJmp * ncg_err_ctx = nullptr
void ncg_mark_frames ()
Sexpr_t ncg_eval (Sexpr_t bc, Sexpr_t args, Sexpr_t env_exec)
Sexpr_t ncg_eval_argv (Sexpr_t bc, Sexpr_t *argv, int nargs, Sexpr_t env_exec)
Sexpr_t ncg_compile (Sexpr_t bc, Sexpr_t env_exec, Sexpr_t captured_names=nullptr)
void ncg_disassemble (Sexpr_t bc, Sexpr_t env_exec)
void ncg_vector_set_ptr (Sexpr_t *data_ptr, LL_int32 k, Sexpr_t val)
Sexpr_t ncg_vref_fast (Sexpr_t vec, Sexpr_t idx)
Sexpr_t ncg_vset_fast (Sexpr_t vec, Sexpr_t idx, Sexpr_t val)
Sexpr_t ncg_cons_fast (Sexpr_t a, Sexpr_t d)
Sexpr_t ncg_box_fast (LL_int64 v)

native-recursion guard (catch C-stack overflow before it SIGSEGVs)

Both the interpreter (eval) and the compiled path (ncg_eval/ncg_eval_argv) recurse on the native C stack; unbounded/non-tail Scheme recursion overflows it into the OS guard page = an uncatchable SIGSEGV. Stack grows down, so the outermost frame has the highest address; we track that high-water mark as the base and, once a recursive call has grown more than eval_stack_budget_ bytes below it, raise a normal (catchable) "recursion too deep" error. eval_recursion_check() is inlined into the hot recursion entry points; the throw lives in the cold out-of-line eval_recursion_overflow(). TUNABLE FROM SCHEME: (recursion-limit) / (recursion-limit BYTES) reads and sets eval_stack_budget_ (mop3_recursion_limit, ll_vm_mop3_extra.cpp), clamped at LL_EVAL_STACK_BUDGET_MIN. Lowering it makes this guard trip at shallow depth, which is how ll_tests/bug-tests.scm (b70-*) asserts the guard in ~1 ms instead of ~20 s and ~39 k frames. IF YOU CHANGE THE CHECK BELOW, RUN THAT TEST: a regression here is not a FAIL line, it is a SIGSEGV that ends the run, so the suite's declared-total check is what reports it.

const char * eval_stack_base_ = nullptr
size_t eval_stack_budget_ = LL_EVAL_STACK_BUDGET
LL_int32 eval_root_depth_max_ = LL_EVAL_ROOT_DEPTH_MAX
void eval_recursion_overflow (const char *where)
void eval_recursion_check (const char *where)

Detailed Description

JIT exception-escape context; opaque to library users (ll_vm_internal.h).

THE DEPTH IDIOM, WRITTEN OUT RATHER THAN USING A FRAME MACRO. gc_root_pop(n) is deprecated, and a COUNTED pop is its worst form: the number is maintained by hand and nothing checks it against the pushes above. A depth restore has no count to get wrong. The *_gc_frame macros cannot be used here because each hardcodes the name of the Lamb it reaches through (lamb., lamb->, mem->), and this macro's is _llcm_l – a local bound so that lambref is evaluated exactly once. So the two lines are spelled out; they are the same { top = depth(); ...; setdepth(top); } shape the macros expand to.

The Lamb class represents a single Lisp virtual machine. To closely match the Arduino-style of loop-based control, LambLisp provides begin(), loop(), and end().

To initialize the LambLisp VM, begin() should be called once before any other call. Generally LambLisp loop() will be called one time for every main loop(), but it is not necessary. If end() is called, all LambLisp resources are freed; Lamb::setup() can be called again to restart that LambLisp VM.

The LambLisp VM functions are grouped this way:

  • Logging functions
  • Build and version informational functions.
  • Cell constructors, getters & setters
  • Base data structures for the interpreter: alist, hash tables, and environments, along with their getters & setters.
  • Printers
  • Partial evaluation and application
  • Port access from Lisp
  • Vector, list, and dictionary utilities, used internally and also available in Lisp.
  • GC interface functions to protect critical sections.
  • Bindings
  • Makers

Member Typedef Documentation

◆ Mop3st_t

typedef Sexpr_t(* Lamb::Mop3st_t) (Lamb &lamb, Sexpr_t sexpr, Sexpr_t env_exec)

This is a pointer to a C++ native function that interacts directly with the S-expression in the given environment. Every LambLisp native function shares this signature. New external functions that conform to this signature can be used from within LambLisp and will run at full speed.

◆ Mop3st_argv_t

typedef Sexpr_t(* Lamb::Mop3st_argv_t) (Lamb &lamb, Sexpr_t *argv, int nargs, Sexpr_t env_exec)

direct-argv calling convention; argv[0..nargs-1] are already-evaluated args. GC CONTRACT for argv variants: the caller (apply_fast1/2_argv, ncg) passes a C-array argv that is NOT on the root stack, so a variant MUST read every argv[i] it needs into a local BEFORE its first allocation (arg cells are otherwise unrooted and can be swept mid-call). To build a Scheme list from argv, use argv_to_list(), which roots all elements for the build.

Constructor & Destructor Documentation

◆ ~Lamb()

Lamb::~Lamb ( )

Real teardown: finalize every cell, free the blocks and the memory manager. Pairs with new Lamb + setup(). Before that the class had no destructor at all and nothing ever tore an instance down, so the leaks below had never been exercised.

Member Function Documentation

◆ setup()

Sexpr_t Lamb::setup ( void )

Run once after base platform has started. In particular, Serial should be initialized before calling setup().

◆ loop()

Sexpr_t Lamb::loop ( void )

Run often to maintain control. The main responsibility of C++ Lamb::loop() is to call the LambLisp function of the same name (loop).

◆ end()

void Lamb::end ( void )

Reset roots and force a full collection. NOTE: despite the name this does NOT release anything – the pool, the blocks and the payloads all stay. Use the destructor for real teardown.

◆ log()

void Lamb::log ( const char * fmt,
... )

C-level printf feature with a limit on the length of strings produced. Takes care of log prompt.

◆ printf() [1/3]

void Lamb::printf ( const char * fmt,
... )

C-level printf feature with a limit on the length of strings produced.

◆ debug() [1/2]

bool Lamb::debug ( void )

Return the state of the Lamb internal debug flag.

◆ debug() [2/2]

void Lamb::debug ( bool onoff)

Set the state of the Lamb debug flag.

◆ build_isDebug()

bool Lamb::build_isDebug ( )

Return true if this build is not checked in. This is unrelated to the runtime debug flag.

◆ build_version()

unsigned long Lamb::build_version ( )

Return the build version as a long integer (implemented as a UTC time stamp).

◆ build_UTC()

unsigned long Lamb::build_UTC ( )

Return the UTC time of this build.

◆ build_pushUTC()

unsigned long Lamb::build_pushUTC ( )

Return the UTC time this repo was last pushed.

◆ build_buildRelease()

const char * Lamb::build_buildRelease ( )

Return a pointer to a character array containing the release description.

◆ build_buildDate()

const char * Lamb::build_buildDate ( )

Return a pointer to a character array containing the build date.

◆ build_pushDate()

const char * Lamb::build_pushDate ( )

Return a pointer to a character array containing the date this repo was last pushed.

◆ expand()

void Lamb::expand ( )

Add another block of cells to the population.

◆ tcons() [1/2]

Sexpr_t Lamb::tcons ( LL_int32 typ,
Word_t a,
Word_t b,
Sexpr_t env_exec )

Generic cell constructor with no GC protection for its arguments.

Why every constructor takes an env_exec

It is easy to read mk_int32(7, env_exec) and wonder what making an integer has to do with an environment. The answer is garbage collection, and it is worth knowing before you write a procedure that allocates.

Any allocation may run a slice of the collector before it returns. The env_exec you pass is handed to that collection as a ROOT, so the environment – and everything reachable from it, which is most of your program's live data – is guaranteed to survive the collection your own allocation just triggered. Pass the env_exec your procedure was called with; do not pass NIL to save a parameter, and do not invent one.

This protects the ENVIRONMENT. It does not protect intermediate values you are building on the C stack – see the garbage-collection rules for C++ procedures in the manual, and mop3_gc_frame.

◆ tcons() [2/2]

Sexpr_t Lamb::tcons ( LL_int32 typ,
Sexpr_t a,
Sexpr_t b,
Sexpr_t env_exec )

Constructor for any pair type; protects both S-expression arguments.

◆ cons()

Sexpr_t Lamb::cons ( Sexpr_t a,
Sexpr_t b,
Sexpr_t env_exec )
inline

Cell constructor for normal pairs.

◆ gc_root_push()

void Lamb::gc_root_push ( Sexpr_t p)

Preserve the cell given from GC, until popped.

◆ materialized()

Sexpr_t Lamb::materialized ( Sexpr_t box)
inline

Hand back an accessor's fresh box, ROOTED first – unconditional; see the definition.

Hand back an accessor's freshly-boxed cell, ROOTED.

UNCONDITIONAL SINCE 2026-10-23 (owner: "remove the materialize flag, and leave the feature ON, materialize as required and root-at-materialize"). There is no flag and no off path: if an accessor materialised a box, that box is on the rootstack before the caller ever sees it.

WHY THIS EXISTS. Lamb::car/Lamb::cdr must BOX when a pair slot holds an embedded LL_int32/LL_float32 – the accessor hands back a FRESH cell that nothing holds, because only the value BITS were ever in the parent. Making the CALLER protect it is an obligation that CANNOT BE CHECKED PER SITE: whether a given car(x) boxes depends on the WIDTH of the value the CALLER passed, which is invisible at the call site. That is why the 28 //!< reachable via sexpr root comments were deleted rather than corrected, and why [B846]/[B856]/[P282] were intermittent. Rooting here DELETES the question rather than answering it.

WHAT THE OBLIGATION REDUCES TO, and it is checkable: "every frame chops" plus "a loop body carries its own frame" – both enforced by w3_ai_scripts/scan_frame_escape.py.

WHAT IT DOES NOT COVER: a value from an ALLOCATOR. acc = cons(val, acc), result = eval(...), env = dict_add_keyval_frame(...) hand back fresh objects no accessor materialised, so those still need root-on-need and a per-iteration re-push; the scanner reports them as [ALLOCATOR].

WHAT MADE IT VIABLE was removing the per-element materialisations from the ten arithmetic folds, NOT the rooting itself. With the folds still boxing per element, (apply + lst) over 100,000 out-of-cache integers peaked at 100,024 rootstack entries – one per element, linear, against an ESP32 cap of 65,536. With the folds reading scalars from the slot it is 28, flat 1k to 100k.

IT IS A FUNCTION, NOT A MACRO, deliberately: a macro would have to be a statement-expression to name its argument once, and the boxing sites sit inside return expressions on the hottest path in the interpreter.

NOTE WHAT IS NOT ROOTED HERE: the POINTER path. A slot holding a traced reference is handed back untouched and pushes nothing, so the cost falls only where a box is actually created, i.e. on embedded numerics.

◆ gc_root_pop()

void Lamb::gc_root_pop ( LL_int32 n = 1)

Release the preserved cells for normal GC processing.

◆ gc_root_depth()

LL_int32 Lamb::gc_root_depth ( )

Return current rootstack depth (save before a try block).

◆ gc_root_depth_max()

LL_int32 Lamb::gc_root_depth_max ( )

Peak rootstack depth since boot or the last reset. gc_root_depth() is instantaneous and cannot tell a loop that pushes per allocation from a deep call chain; the peak can, and an overflow otherwise leaves no record of how fast the depth got there.

◆ gc_root_depth_max_reset()

void Lamb::gc_root_depth_max_reset ( )

Re-baseline the peak to the CURRENT depth, so one workload can be measured in isolation. Not a reset to 0: a peak below the live depth could not be true.

◆ gc_root_setdepth()

void Lamb::gc_root_setdepth ( LL_int32 n)

Restore rootstack to saved depth, discarding entries above n (call in catch to drop orphaned roots without disturbing the caller's entries).

◆ gc_idle_task_us()

void Lamb::gc_idle_task_us ( LL_int32 us,
Sexpr_t env_exec )

Run GC for up to us microseconds of spare time; bank extra quanta as credit to skip during later allocations.

◆ gc_collect()

void Lamb::gc_collect ( Sexpr_t env_exec)

Run one complete GC cycle (mark + sweep); leaves heap in idle phase with all unreachable cells freed.

◆ eval_progress()

uint32_t Lamb::eval_progress ( ) const

A monotonic token that advances whenever the evaluator allocates.

It answers one question: IS THE EVALUATOR MAKING PROGRESS? A long computation and a stalled one look identical from outside the interpreter and are easy to tell apart from inside it – a long one keeps allocating, a stalled one does not. So a supervisor can sample this, wait, and sample again: unchanged means STOPPED, not slow, and a timeout can then be set from how long one uninterruptible step may legitimately take rather than from how long a user's program might run.

Sample it from a timer, a second task or a monitoring loop – never from the computation being watched, which is the thing that may be stuck. The value is an OPAQUE TOKEN, not a count: compare two samples and nothing else. It wraps, which is harmless for that use.

Reading it through (Platform.eval-progress) itself allocates, so two consecutive reads differ by a few counts with nothing in between. Compare the SIZE of a delta against an idle baseline, not its presence.

◆ gc_Qm()

LL_int32 Lamb::gc_Qm ( ) const

return current mark quantum size.

◆ gc_pass_n()

void Lamb::gc_pass_n ( LL_int32 nsteps)

run nsteps mark steps; no-op if not in marking phase.

◆ mem_dbg_t_gc_total_us()

unsigned long Lamb::mem_dbg_t_gc_total_us ( )

monotonic total us in gc_pass – measured GC load

◆ mem_dbg_nfree()

LL_int32 Lamb::mem_dbg_nfree ( )

free cells right now (instantaneous)

◆ mem_dbg_total_cells()

LL_int32 Lamb::mem_dbg_total_cells ( )

total cells in the pool right now

◆ set_repl_quiet()

void Lamb::set_repl_quiet ( bool q)

Quiet REPL mode: suppress the per-char redisplay prompt reprint and the "Input:" echo. NOT platform-specific despite its old home – the terminal reaches the platform only through LambStdio (LL_Term::get_input_serial/get_output_serial both return it), and the flag's readers are the REPL echo and the (quiet) / (verbose) procedures.

◆ ncg_frame_pool_n()

LL_int32 Lamb::ncg_frame_pool_n ( )
inline

Frames currently in the free-list pool.

◆ ncg_frame_pool_init()

void Lamb::ncg_frame_pool_init ( LL_int32 n)

Allocate n frames and push them onto the pool free list.

◆ ncg_frame_pool_free()

void Lamb::ncg_frame_pool_free ( )

release them; nothing did, so every instance leaked the pool.

◆ car()

Sexpr_t Lamb::car ( Sexpr_t l)
inline

Return the first element (car) of a pair cell; throws if l is not a pair type.

◆ cdr()

Sexpr_t Lamb::cdr ( Sexpr_t l)
inline

Return the second element (cdr) of a pair cell; throws if l is not a pair type.

◆ prechecked_anypair_get_car()

Sexpr_t Lamb::prechecked_anypair_get_car ( Sexpr_t c)
inline

Car of a known pair; handles embedded int32/float32; no type check.

◆ prechecked_anypair_get_cdr()

Sexpr_t Lamb::prechecked_anypair_get_cdr ( Sexpr_t c)
inline

Cdr of a known pair; handles embedded int32/float32; no type check.

◆ caar()

Sexpr_t Lamb::caar ( Sexpr_t l)
inline

Composition: car(car(l)).

◆ cadr()

Sexpr_t Lamb::cadr ( Sexpr_t l)
inline

Composition: car(cdr(l)); returns the second element of a list.

◆ car_type()

int Lamb::car_type ( Sexpr_t c)
inline

The TYPE TAG of a pair's car, without materialising it. The missing member of this family: every other reader here answers "what is the value", and the callers that dispatch on type had no way to ask without boxing first – so they called the accessor, got a fresh cell, read ->type() off it and threw the cell away. Ten arithmetic folds did exactly that. EMBEDDED WIDTHS ANSWER FROM THE FLAGS; anything else is a traced pointer, and reading its type through the Cell-level prechecked_anypair_get_car() allocates nothing either. So this never boxes, on any path. THE CALLER STILL NEEDS THE CELL for a wide operand (INT64/FLOAT64/BIGNUM/RATIONAL/COMPLEX) – fetch it in that branch, where the accessor boxes nothing because the slot holds a pointer.

◆ car_mustbe_int32()

LL_int32 Lamb::car_mustbe_int32 ( Sexpr_t c)
inline

car(c)->mustbe_int32() with no box.

◆ cdr_mustbe_int32()

LL_int32 Lamb::cdr_mustbe_int32 ( Sexpr_t c)
inline

cdr(c)->mustbe_int32() with no box.

◆ car_mustbe_float32()

LL_float32 Lamb::car_mustbe_float32 ( Sexpr_t c)
inline

car(c)->mustbe_float32() with no box.

◆ cdr_mustbe_float32()

LL_float32 Lamb::cdr_mustbe_float32 ( Sexpr_t c)
inline

cdr(c)->mustbe_float32() with no box.

◆ car_coerce_int32()

LL_int32 Lamb::car_coerce_int32 ( Sexpr_t c)
inline

car(c)->coerce_int32() with no box; truncates an embedded float as the original does.

◆ cdr_coerce_int32()

LL_int32 Lamb::cdr_coerce_int32 ( Sexpr_t c)
inline

cdr(c)->coerce_int32() with no box.

◆ car_coerce_int64()

LL_int64 Lamb::car_coerce_int64 ( Sexpr_t c)
inline

car(c)->coerce_int64() with no box.

◆ cdr_coerce_int64()

LL_int64 Lamb::cdr_coerce_int64 ( Sexpr_t c)
inline

cdr(c)->coerce_int64() with no box.

◆ car_coerce_float32()

LL_float32 Lamb::car_coerce_float32 ( Sexpr_t c)
inline

car(c)->coerce_float32() with no box.

◆ cdr_coerce_float32()

LL_float32 Lamb::cdr_coerce_float32 ( Sexpr_t c)
inline

cdr(c)->coerce_float32() with no box.

◆ car_coerce_float64()

LL_float64 Lamb::car_coerce_float64 ( Sexpr_t c)
inline

car(c)->coerce_float64() with no box.

◆ cdr_coerce_float64()

LL_float64 Lamb::cdr_coerce_float64 ( Sexpr_t c)
inline

cdr(c)->coerce_float64() with no box.

◆ cdar()

Sexpr_t Lamb::cdar ( Sexpr_t l)
inline

Composition: cdr(car(l)).

◆ cddr()

Sexpr_t Lamb::cddr ( Sexpr_t l)
inline

Composition: cdr(cdr(l)).

◆ caddr()

Sexpr_t Lamb::caddr ( Sexpr_t l)
inline

Composition: car(cdr(cdr(l))); returns the third element of a list.

◆ cdddr()

Sexpr_t Lamb::cdddr ( Sexpr_t l)
inline

Composition: cdr(cdr(cdr(l))).

◆ cadddr()

Sexpr_t Lamb::cadddr ( Sexpr_t l)
inline

Composition: car(cdr(cdr(cdr(l)))); returns the fourth element of a list.

◆ cddddr()

Sexpr_t Lamb::cddddr ( Sexpr_t l)
inline

Composition: cdr(cdr(cdr(cdr(l)))).

◆ set_car_bang()

void Lamb::set_car_bang ( Sexpr_t c,
Sexpr_t val )

Replace the car field in the cell with val. GC flags will be maintained as required.

◆ set_cdr_bang()

void Lamb::set_cdr_bang ( Sexpr_t c,
Sexpr_t val )

Replace the cdr field in the cell with val. GC flags will be maintained as required.

◆ vector_set_bang()

void Lamb::vector_set_bang ( Sexpr_t vec,
LL_int32 k,
Sexpr_t val )

Replace the specified vector element with val. GC flags will be maintained as required.

◆ reverse_bang()

Sexpr_t Lamb::reverse_bang ( Sexpr_t l)

Reverse the list in-place and return the new list head (the former list tail).

◆ eq_q()

Sexpr_t Lamb::eq_q ( Sexpr_t obj1,
Sexpr_t obj2 )

Return true if 2 cells are the same cell, or are atoms with the same value.

◆ eqv_q()

Sexpr_t Lamb::eqv_q ( Sexpr_t obj1,
Sexpr_t obj2 )

Return true if 1 cells are the same cell, or are atoms with the same value.

◆ equal_q()

Sexpr_t Lamb::equal_q ( Sexpr_t obj1,
Sexpr_t obj2 )

Returns true when obj1 and obj2 are eqv?, and also all their descendants.

◆ assq()

Sexpr_t Lamb::assq ( Sexpr_t obj,
Sexpr_t alist )

Search an association list for a matching key, and return the (key . value) pair, or false if not found.

◆ oblist_query()

Sexpr_t Lamb::oblist_query ( Sexpr_t oblist,
const char * identifier,
Bool_t force,
Sexpr_t env_exec )

Look up identifier in oblist. If force is true, intern the symbol (creating it if absent) and return it. If force is false, return the existing symbol or HASHF if not found.

◆ oblist_test()

Sexpr_t Lamb::oblist_test ( Sexpr_t oblist,
const char * identifier,
Sexpr_t env_exec )
inline

Return the interned symbol for identifier, or HASHF if not present.

◆ oblist_intern()

Sexpr_t Lamb::oblist_intern ( Sexpr_t oblist,
const char * identifier,
Sexpr_t env_exec )
inline

Return the interned symbol for identifier, creating it if necessary.

◆ oblist_analyze()

Sexpr_t Lamb::oblist_analyze ( Sexpr_t oblist,
LL_int32 verbosity,
Sexpr_t env_exec )

Report statistics on the oblist hash table (load factor, chain lengths, etc.) at the given verbosity level. Returns NIL.

◆ dict_new() [1/2]

Sexpr_t Lamb::dict_new ( LL_int32 framesize,
Sexpr_t env_exec )
inline

Return a new empty dictionary with the given top frame size.

◆ dict_new() [2/2]

Sexpr_t Lamb::dict_new ( Sexpr_t env_exec)
inline

Return a new empty dictionary with alist top frame.

◆ dict_add_empty_frame() [1/2]

Sexpr_t Lamb::dict_add_empty_frame ( Sexpr_t dict,
LL_int32 framesize,
Sexpr_t env_exec )

Prepend a new empty frame to dict and return the extended dictionary. Frame storage is chosen by framesize: < 4 = alist; 4–7 = 2-slot hash table; >= 8 = heap-allocated hash table of framesize slots.

◆ dict_add_empty_frame() [2/2]

Sexpr_t Lamb::dict_add_empty_frame ( Sexpr_t dict,
Sexpr_t env_exec )
inline

Returns a new dictionary with an empty alist top frame and dict as parent.

◆ dict_add_keyval_frame()

Sexpr_t Lamb::dict_add_keyval_frame ( Sexpr_t dict,
Sexpr_t keys,
Sexpr_t vals,
Sexpr_t env_exec )

Returns a new dictionary with a new top frame containing the keys bound to the values.

◆ dict_add_keyval_frame_argv()

Sexpr_t Lamb::dict_add_keyval_frame_argv ( Sexpr_t dict,
Sexpr_t keys,
Sexpr_t * argv,
int nargs,
Sexpr_t env_exec )

same as dict_add_keyval_frame but values come from argv[] instead of a cons list.

◆ dict_add_vector_frame()

Sexpr_t Lamb::dict_add_vector_frame ( Sexpr_t parent,
Sexpr_t names,
Sexpr_t * vals,
int ncaps,
Sexpr_t env )

compact kv-vec frame (T_SVEC_IMM for ncaps==1); alist fallback for ncaps>1.

◆ dict_add_alist_frame()

Sexpr_t Lamb::dict_add_alist_frame ( Sexpr_t dict,
Sexpr_t alist,
Sexpr_t env_exec )
inline

Returns a new dictionary with the alist bindings added in a new frame on top of the base dictionary.

◆ dict_append_bang()

Sexpr_t Lamb::dict_append_bang ( Sexpr_t d1,
Sexpr_t d2,
Sexpr_t env_exec )

Append d2 as a parent dictionary of d1.

◆ dict_bind_bang()

void Lamb::dict_bind_bang ( Sexpr_t dict,
Sexpr_t key,
Sexpr_t value,
Sexpr_t env_exec )

Modify the target dictionary; value is re-assigned to key wherever first found in chain, else created in the top frame.

◆ dict_bind_local_bang()

void Lamb::dict_bind_local_bang ( Sexpr_t dict,
Sexpr_t key,
Sexpr_t value,
Sexpr_t env_exec )

Modify only the top frame of dict; update key if found there, else create it there. Never walks parent frames. Use for define/define-syntax per R7RS 5.3.2.

◆ dict_rebind_bang()

void Lamb::dict_rebind_bang ( Sexpr_t dict,
Sexpr_t key,
Sexpr_t value,
Sexpr_t env_exec )

Modify the target dictionary; value is assigned to key wherever first found in env, else error if not found.

◆ dict_bind_alist_bang()

void Lamb::dict_bind_alist_bang ( Sexpr_t dict,
Sexpr_t alist,
Sexpr_t env_exec )

Modify the target dictionary; binding keys in the alist to their corresponding values.

◆ dict_rebind_alist_bang()

void Lamb::dict_rebind_alist_bang ( Sexpr_t dict,
Sexpr_t alist,
Sexpr_t env_exec )

Modify the target dictionary; rebinding keys in the alist to their corresponding values.

◆ dict_ref_q()

Sexpr_t Lamb::dict_ref_q ( Sexpr_t dict,
Sexpr_t key )

Returns (key value) pair, or false if key is unbound.

◆ dict_ref()

Sexpr_t Lamb::dict_ref ( Sexpr_t dict,
Sexpr_t key )

Returns the value associated with the key in the given dictionary; throws error if key is unbound.

◆ dict_keys()

Sexpr_t Lamb::dict_keys ( Sexpr_t dict,
Sexpr_t env_exec )

Note that keys and values are guaranteed to return in the corresponding order in dict_keys and dict_values, duplicate keys may occur.

Return a list of all the keys in this dictionary. If the dictionary has parents, keys may appear multiple times.

◆ dict_values()

Sexpr_t Lamb::dict_values ( Sexpr_t dict,
Sexpr_t env_exec )

Return a list of all the values in this dictionary. If the dictionary has parents, values may appear multiple times for each key.

◆ dict_to_alist()

Sexpr_t Lamb::dict_to_alist ( Sexpr_t dict,
Sexpr_t env_exec )

Convert the dictionary to an alist, retaining only the top-level (key . value) pairs.

◆ dict_to_2list()

Sexpr_t Lamb::dict_to_2list ( Sexpr_t dict,
Sexpr_t env_exec )

Convert the dictionary to a list of 2-element lists, retaining only the top-level (key value) sublists.

◆ alist_to_dict()

Sexpr_t Lamb::alist_to_dict ( Sexpr_t alist,
Sexpr_t env_exec )

Convert an alist into a dictionary. The resulting dictionary has no parent.

◆ twolist_to_dict()

Sexpr_t Lamb::twolist_to_dict ( Sexpr_t twolist,
Sexpr_t env_exec )

Convert a list of 2-element lists into a dictionary. The resulting dictionary has no parent.

◆ dict_analyze()

Sexpr_t Lamb::dict_analyze ( Sexpr_t dict,
LL_int32 verbosity = 0 )

Internal integrity check.

◆ integrity_check()

Sexpr_t Lamb::integrity_check ( Sexpr_t env_exec,
LL_int32 verbosity = 1 )

GC heap integrity check; callable from mop3.

◆ read()

Sexpr_t Lamb::read ( LL_Port & src,
Sexpr_t env_exec )
inline

Read one S-expression from src; returns OBJ_EOF at end of input.

◆ write_or_display()

Sexpr_t Lamb::write_or_display ( Sexpr_t sexpr,
Bool_t do_write,
WriteMode mode = WriteMode::Cyclic )

Write sexpr to the current output port. do_write true => write semantics (strings quoted, chars as #\\x); false => display (strings/chars as-is). mode selects datum-label emission: Cyclic labels nodes on a cycle (default, terminates on cycles), Shared labels all shared nodes, Simple emits no labels. Returns OBJ_VOID.

◆ write_simple()

Sexpr_t Lamb::write_simple ( Sexpr_t sexpr)

Write sexpr using write semantics without following shared structure. Returns OBJ_VOID.

◆ sprintf()

String Lamb::sprintf ( Charst_t fmt,
Sexpr_t args,
Sexpr_t env_exec )

Format a string from a template and a list of S-expression arguments. Format specifiers: ~a / ~A inserts the next argument using display formatting; ~n or ~% inserts a newline; ~~ inserts a literal ~. Backslash escapes \\n (newline) and \\\\ (literal backslash) are also recognized. Throws if the argument count does not match the format string.

◆ printf() [2/3]

Sexpr_t Lamb::printf ( Sexpr_t args,
LL_Port & outp )

Format and write to outp; args is (fmt-string val ...). See sprintf() for format specifier syntax. Returns OBJ_VOID.

◆ printf() [3/3]

Sexpr_t Lamb::printf ( Sexpr_t args)

Format and write to the current output port; args is (fmt-string val ...). See sprintf() for format specifier syntax. Returns OBJ_VOID.

◆ eval()

Sexpr_t Lamb::eval ( Sexpr_t sexpr,
Sexpr_t env_exec )

Evaluate the S-expression in the environment provided.

◆ eval_list()

Sexpr_t Lamb::eval_list ( Sexpr_t args,
Sexpr_t env_exec )

Evaluate the list of S-expressions in the environment provided, and return a lkist of results.

◆ apply_proc_partial()

Sexpr_t Lamb::apply_proc_partial ( Sexpr_t proc,
Sexpr_t sexpr,
Sexpr_t env_exec )

Evaluate the function arguments and then apply the function to them. The result may be a tail requiring further evaluation.

◆ map_proc()

Sexpr_t Lamb::map_proc ( Sexpr_t proc,
Sexpr_t lists,
Sexpr_t env_exec )

Apply the procecure to the lists per R5RS.

◆ lamb_oblist()

Sexpr_t Lamb::lamb_oblist ( )
inline

the current in/out/error ports are ordinary global bindings in the base env (which is permanently rooted), so the ports are traced like any other binding – NOT special GC roots. These read/write the cached binding cell's cdr (the port value) for O(1) access.

◆ set_current_input_port()

void Lamb::set_current_input_port ( Sexpr_t p)
inline

for with-input-from-file

◆ set_current_output_port()

void Lamb::set_current_output_port ( Sexpr_t p)
inline

for with-output-to-file

◆ load()

Sexpr_t Lamb::load ( Charst_t name,
Sexpr_t env_exec,
LL_int32 verbosity = 0 )

Load and evaluate S-expressions from the named file.

◆ append()

Sexpr_t Lamb::append ( Sexpr_t sexpr,
Sexpr_t env_exec )

Concatenate a list of lists per R5RS append; the last element may be a non-list, producing an improper list. Returns the concatenated result.

◆ append2()

Sexpr_t Lamb::append2 ( Sexpr_t lis,
Sexpr_t obj,
Sexpr_t env_exec )

Destructively attach obj to the end of lis, which must be a proper list.

◆ list_copy()

Sexpr_t Lamb::list_copy ( Sexpr_t sexpr,
Sexpr_t env_exec )

Return a shallow copy of the list: new pair cells with the same elements in the same order.

◆ list_analyze()

Sexpr_t Lamb::list_analyze ( Sexpr_t sexpr,
Sexpr_t env_exec )

Traverse sexpr using Floyd's cycle-detection algorithm and return a structural description:

  • proper list of length N → integer N
  • improper list (non-NIL terminator) after N pairs → (improper . N)
  • circular structure → #f

◆ list_to_vector()

Sexpr_t Lamb::list_to_vector ( Sexpr_t l,
Sexpr_t env_exec )

Convert a proper list to a heap-allocated S-expression vector (T_SVEC_HEAP); list order is preserved.

◆ vector_to_list()

Sexpr_t Lamb::vector_to_list ( Sexpr_t v,
Sexpr_t env_exec )

Convert an S-expression vector to a proper list; element order is preserved.

◆ vector_copy()

Sexpr_t Lamb::vector_copy ( Sexpr_t from,
Sexpr_t env_exec )

Return a shallow copy of the S-expression vector from.

◆ vector_to_sparsevec()

Sexpr_t Lamb::vector_to_sparsevec ( Sexpr_t vec,
Sexpr_t skip,
Sexpr_t env_exec )

Convert vec to a sorted (index . value) alist, omitting elements equal to skip.

◆ sparsevec_to_vector()

Sexpr_t Lamb::sparsevec_to_vector ( Sexpr_t alist,
Sexpr_t fill,
Sexpr_t env_exec )

Reconstruct a dense vector from a sparse (index . value) alist; positions not present in the alist are initialized to fill.

◆ mk_error() [1/2]

Sexpr_t Lamb::mk_error ( Sexpr_t env_exec,
Sexpr_t irritants,
const char * fmt,
... )

Allocate a NEW T_ERROR cell with these irritants. Unlike Cell::mk_error(), which reuses one shared object, several of these can be live at once. This is the one to raise from a mop3.

◆ mk_error() [2/2]

Sexpr_t Lamb::mk_error ( Sexpr_t env_exec,
const char * fmt,
... )

Allocate a NEW T_ERROR cell. See the irritants overload above.

◆ mk_syserror() [1/2]

Sexpr_t Lamb::mk_syserror ( const char * fmt,
... )

Fill and return the SHARED system error object (OBJ_SYSERROR). Allocates nothing; only one exists at a time.

◆ tag_error()

Sexpr_t Lamb::tag_error ( Sexpr_t err,
const char * kind )

Tag a fresh T_ERROR with a kind marker in its irritants (e.g. "%file-error"/"%read-error") so file-error?/read-error? can classify it. Returns err.

◆ mk_syserror() [2/2]

Sexpr_t Lamb::mk_syserror ( Sexpr_t irritants,
const char * fmt,
... )

As above, with irritants. Still the shared object.

◆ gensym()

Sexpr_t Lamb::gensym ( Sexpr_t env_exec)

Produce a new unique symbol.

Perf: these makers allocate a blank cell then embed the value with ->set(), so the tcons a/b args are throwaways – pass (Word_t)0,(Word_t)0 to hit the NON-pushing Word_t tcons overload. Do NOT change back to NIL, NIL: that selects the Sexpr_t overload, whose frame roots (a,b) and pushes NIL twice on EVERY mk_int32/float32/... (i.e. every materializing car/cdr) for nothing.

◆ mk_bool()

Sexpr_t Lamb::mk_bool ( Bool_t b,
Sexpr_t env_exec )
inline

Return HASHT or HASHF.

◆ mk_character()

Sexpr_t Lamb::mk_character ( ll_codepoint_t cp,
Sexpr_t env_exec )
inline

Allocate a new T_CHAR cell holding Unicode codepoint cp.

◆ mk_integer()

Sexpr_t Lamb::mk_integer ( LL_int64 i,
Sexpr_t env_exec )
inline

mk_integer: T_INT32 if fits, else T_INT64. Returns cached cell for [-2048..4096].

The double write (tcons then ->set(v)) looks redundant and is kept: three ways of removing it were measured and all three were SLOWER by 1.5-3.4%, including a control that provably could not have touched the cons path – so the effect is code layout, not sharing. Measurements in [B846].

◆ mk_int32()

Sexpr_t Lamb::mk_int32 ( LL_int32 i,
Sexpr_t env_exec )
inline

<T_INT32 cell; returns cached cell for small values.

◆ mk_int64()

Sexpr_t Lamb::mk_int64 ( LL_int64 i,
Sexpr_t env_exec )
inline

T_INT64 cell (always allocates).

◆ mk_float32()

Sexpr_t Lamb::mk_float32 ( LL_float32 r,
Sexpr_t env_exec )
inline

is what decides whether it is worth it.

T_FLOAT32 cell. ALWAYS ALLOCATES – unlike mk_int32, which returns a shared sint_cache cell for small values. That asymmetry is an embedded float read through car/cdr materializes a FRESH cell every time, so (eq? x x) on a NaN can differ between the interpreter and the compiler. Compare floats with =, not eq?.

◆ mk_float64()

Sexpr_t Lamb::mk_float64 ( LL_float64 d,
Sexpr_t env_exec )
inline

T_FLOAT64 cell.

◆ mk_complex()

Sexpr_t Lamb::mk_complex ( LL_float32 r,
LL_float32 i,
Sexpr_t env_exec )
inline

T_COMPLEX cell.

◆ rat_store_i64()

void Lamb::rat_store_i64 ( Sexpr_t c,
bool is_num,
LL_int64 v,
Sexpr_t env_exec )
inline

Store an int64 exact value into a rational slot: embedded int32 if it fits, else a boxed T_INT64.

< boxed (issued -> survives; stored immediately)

◆ mk_rational()

Sexpr_t Lamb::mk_rational ( LL_int64 num,
LL_int64 den,
Sexpr_t env_exec )
inline

Canonical rational from int64 components: GCD-reduces, normalises sign, returns an integer when den==1, else a T_RATIONAL whose components box to T_INT64 when they overflow int32 (– no float64 fallback, so exactness is preserved). When LL_RATIONAL=0, always returns inexact float64.

◆ mk_number()

Sexpr_t Lamb::mk_number ( Charst_t str,
Sexpr_t env_exec )

Parse str as a number; returns T_INT32/T_INT64, T_FLOAT32/T_FLOAT64, T_RATIONAL, or T_COMPLEX as appropriate, or OBJ_UNDEF if str is not a valid numeric literal.

◆ mk_sharp_const()

Sexpr_t Lamb::mk_sharp_const ( Charst_t name,
Sexpr_t env_exec )

Parse a #-prefixed constant name (e.g., "t", "f", "true", "false") and return the corresponding singleton; returns OBJ_UNDEF if unrecognized.

◆ mk_string() [1/2]

Sexpr_t Lamb::mk_string ( Sexpr_t env_exec,
const char * fmt,
... )

Build a string with PRINTF FORMATTING – fmt is a format, not content.

NEVER PASS RUNTIME DATA AS `fmt`. *** `mk_string(env_exec, buf)` compiles, reads like

"make a string out of buf", and is a printf call with no arguments behind it. A s in the data then dereferences an uninitialised argument slot and a d prints a stack word. Measured on an SD card: a 7-byte file containing asbdc came back as 20 bytes, one of them a DRAM stack address, and the same call measured its result with strlen, so every file with a zero byte in it was silently shortened AND reported as a success. Five call sites had it – a file's CONTENT, two FILENAMES off a card and a filesystem, and two where the data was literal only by luck of the table feeding it.

So: for CONTENT use the length-carrying overload below, which copies k bytes and cares nothing for zeros or percent signs. For a filename or any other NUL-terminated runtime string, pass "s" as the format. CHECKPRINTF_pos3 gives the compiler the attribute it needs to catch this, and the build does not ask it to: -Wformat alone does not warn on a non-literal format – that is -Wformat-security.

◆ payload_alloc()

void * Lamb::payload_alloc ( size_t nbytes,
const char * who,
Sexpr_t env_exec )

Allocate a bulk PAYLOAD (string/bytevector/vector/bignum/bytecode body) with a DEFINED out-of-memory policy, mirroring what gc_urgent() does for cells.

ll_payload_alloc_bytes() falls back to malloc() when PSRAM is full, and malloc() returns NULL – which every call site then wrote through (memset/memcpy on address 0), producing Guru Meditation Error: StoreProhibited and a BOOT LOOP that only a reflash cleared. A real-time node must not die that way, and must not die silently.

Policy, in order:

  1. try the allocation;
  2. on failure run the collector to completion – payload memory is reclaimed by the SWEEP (finalize_one_cell frees the payloads of dead cells), so exhaustion is often transient – and retry ONCE;
  3. if it still fails, throw a normal LambLisp error. It is catchable with guard, so an application can shed load, and the REPL survives to report it. Never returns NULL.

◆ mk_string() [2/2]

Sexpr_t Lamb::mk_string ( LL_int32 k,
Charst_t src,
Sexpr_t env_exec )

THE ONE TO USE FOR CONTENT: copies exactly k bytes from src, so embedded zero bytes survive and nothing in src is interpreted. See the printf overload's warning above.

◆ mk_symbol_or_number()

Sexpr_t Lamb::mk_symbol_or_number ( Charst_t str,
Sexpr_t env_exec )

Try to parse str as a number; if that fails, intern it as a symbol. Used by the reader for unquoted tokens.

◆ mk_symbol()

Sexpr_t Lamb::mk_symbol ( Charst_t str,
Sexpr_t env_exec )
inline

Intern str in the oblist and return the symbol S-expression. Two calls with identical strings always return the same cell pointer.

◆ mk_bytevector() [1/3]

Sexpr_t Lamb::mk_bytevector ( LL_int32 k,
Sexpr_t env_exec )

Simplest heap allocation with no initialization.

◆ mk_bytevector() [2/3]

Sexpr_t Lamb::mk_bytevector ( LL_int32 k,
Bytest_t src,
Sexpr_t env_exec )

Heap allocation with initialization.

◆ mk_bytevector() [3/3]

Sexpr_t Lamb::mk_bytevector ( LL_int32 k,
LL_int32 fill,
Sexpr_t env_exec )

Heap allocation with initialization.

◆ mk_bytevector_ext()

Sexpr_t Lamb::mk_bytevector_ext ( LL_int32 k,
Bytest_t ext,
Sexpr_t env_exec )
inline

Injection of externally allocated memory, which will not be freed at GC time.

◆ mk_intvector()

Sexpr_t Lamb::mk_intvector ( LL_int32 k,
Sexpr_t env_exec )

Note that integer vectors and real vectors are just bytevectors underneath.

Allocates a bytevector from the heap to ensure proper alignment (no IMM type).

◆ mk_realvector()

Sexpr_t Lamb::mk_realvector ( LL_int32 k,
Sexpr_t env_exec )

Allocates a bytevector from the heap to ensure proper alignment (no IMM type).

◆ mk_vector()

Sexpr_t Lamb::mk_vector ( LL_int32 len,
Sexpr_t fill,
Sexpr_t env_exec )

Allocate a T_SVEC_HEAP vector of len elements, all initialized to fill.

◆ mk_hashtbl()

Sexpr_t Lamb::mk_hashtbl ( LL_int32 len,
Sexpr_t fill,
Sexpr_t env_exec )

Allocate a T_SVEC2N_HEAP hash-table vector; actual size is rounded up to the next power of 2, minimum len slots, all initialized to fill.

◆ mk_serial_port()

Sexpr_t Lamb::mk_serial_port ( Sexpr_t env_exec)

Wrap the platform serial port (Arduino Serial or POSIX stdin/stdout) as a LambLisp port object.

◆ mk_input_file_port()

Sexpr_t Lamb::mk_input_file_port ( Charst_t name,
Sexpr_t env_exec )

Open the named filesystem file for reading; return a port. Throws if the file cannot be opened.

◆ mk_output_file_port()

Sexpr_t Lamb::mk_output_file_port ( Charst_t name,
Sexpr_t env_exec )

Open the named filesystem file for writing; return a port. Throws if the file cannot be opened.

◆ mk_udp_port()

Sexpr_t Lamb::mk_udp_port ( unsigned local_port,
Sexpr_t env_exec )

open a UDP datagram socket.

◆ mk_tcp_client_port()

Sexpr_t Lamb::mk_tcp_client_port ( Charst_t host,
unsigned port,
Sexpr_t env_exec )

Open a TCP connection to host:port; return a bidirectional port. Scheme: (open-tcp-client-port host port). Returns #f on connection failure (it does NOT throw, and does not report at all).

◆ mk_tcp_server_port()

Sexpr_t Lamb::mk_tcp_server_port ( unsigned port,
Sexpr_t env_exec )

Create a TCP listener on port; return a server port. Scheme: (open-tcp-server-port port). Use server_accept_port() to accept incoming connections.

◆ server_accept_port()

Sexpr_t Lamb::server_accept_port ( LL_Port * server,
Sexpr_t env_exec )

Accept one pending connection from server; returns a bidirectional client port, or HASHF (#f) if no connection is waiting (non-blocking). Scheme: (server-accept server-port).

◆ mk_tls_client_port()

Sexpr_t Lamb::mk_tls_client_port ( Charst_t host,
unsigned port,
Charst_t ca_cert_pem,
Sexpr_t env_exec )

Open a TLS client connection to host:port. Pass a PEM CA certificate string in ca_cert_pem to verify the server; pass nullptr to skip verification (insecure, development only). Scheme: (open-tls-client-port host port) or (open-tls-client-port host port ca-cert-pem).

◆ mk_tls_server_port()

Sexpr_t Lamb::mk_tls_server_port ( unsigned port,
Charst_t cert_pem,
Charst_t key_pem,
Sexpr_t env_exec )

Create a TLS server on port using PEM-encoded certificate cert_pem and private key key_pem. Scheme: (open-tls-server-port port cert-pem key-pem).

◆ mk_input_string_port()

Sexpr_t Lamb::mk_input_string_port ( Charst_t inp,
Sexpr_t env_exec )

Wrap the C string inp as an input port; reading consumes characters left to right.

◆ mk_output_string_port()

Sexpr_t Lamb::mk_output_string_port ( Sexpr_t env_exec)

Create an output port that accumulates written characters; retrieve the result with Scheme get-output-string.

◆ mk_input_bytevector_port()

Sexpr_t Lamb::mk_input_bytevector_port ( ByteVec_t src,
LL_int32 n,
Sexpr_t env_exec )

Wrap n bytes of src as a binary input port.

◆ mk_output_bytevector_port()

Sexpr_t Lamb::mk_output_bytevector_port ( Sexpr_t env_exec)

Create a binary output port that accumulates written bytes; retrieve the result with Scheme get-output-bytevector.

◆ mk_Mop3_procst_t()

Sexpr_t Lamb::mk_Mop3_procst_t ( Mop3st_t f,
Sexpr_t env_exec )
inline

car=0 (no argv variant)

◆ mk_Mop3_procst_argv_t()

Sexpr_t Lamb::mk_Mop3_procst_argv_t ( Mop3st_t f,
Mop3st_argv_t fa,
Sexpr_t env_exec )
inline

car=argv fn, cdr=standard fn

◆ mk_macro()

Sexpr_t Lamb::mk_macro ( Sexpr_t nlam,
Sexpr_t env_nlam,
Sexpr_t env_exec )
inline

Allocate a T_MACRO cell: nlam is the nlambda transformer, env_nlam is its definition environment. At expansion time the transformer receives the unevaluated argument list; its result is evaluated in the call-site environment.

◆ mk_procedure()

Sexpr_t Lamb::mk_procedure ( Sexpr_t formals,
Sexpr_t body,
Sexpr_t env_proc,
Sexpr_t env_exec )
inline

Allocate a T_PROC closure: formals is the parameter list, body is the list of body expressions, env_proc is the captured lexical environment.

◆ mk_nprocedure()

Sexpr_t Lamb::mk_nprocedure ( Sexpr_t formals,
Sexpr_t body,
Sexpr_t env_nproc,
Sexpr_t env_exec )
inline

As mk_procedure() but returns a T_NPROC cell; arguments are passed unevaluated at call time.

◆ mk_thunk_sexpr()

Sexpr_t Lamb::mk_thunk_sexpr ( Sexpr_t sexpr,
Sexpr_t env_thunk,
Sexpr_t env_exec )
inline

Allocate a T_THUNK_SEXPR trampoline cell: sexpr will be evaluated in env_thunk on the next trampoline iteration.

◆ mk_thunk_body()

Sexpr_t Lamb::mk_thunk_body ( Sexpr_t body,
Sexpr_t env_thunk,
Sexpr_t env_exec )
inline

Allocate a T_THUNK_BODY trampoline cell: body is a list of expressions evaluated in sequence in env_thunk; the last expression's value is the result.

◆ macroexpand() [1/2]

Sexpr_t Lamb::macroexpand ( Sexpr_t form,
Sexpr_t env_exec )

Fully expand all macros in form, applying transformers repeatedly until no further expansion is possible. Returns the fully expanded form.

◆ macroexpand1() [1/2]

Sexpr_t Lamb::macroexpand1 ( Sexpr_t form,
Sexpr_t env_exec )

Apply at most one macro expansion step to form. Returns the expansion if form is a macro call, otherwise returns form unchanged.

◆ macroexpand() [2/2]

Sexpr_t Lamb::macroexpand ( Sexpr_t proc,
Sexpr_t args,
Sexpr_t env_exec )

Fully expand the macro proc applied to args.

◆ macroexpand1() [2/2]

Sexpr_t Lamb::macroexpand1 ( Sexpr_t proc,
Sexpr_t args,
Sexpr_t env_exec )

Apply one expansion step of macro proc to args.

◆ ncg_mark_frames()

void Lamb::ncg_mark_frames ( )

Push all NCG live-frame values onto GC markstack; called from start_marking().

◆ ncg_eval()

Sexpr_t Lamb::ncg_eval ( Sexpr_t bc,
Sexpr_t args,
Sexpr_t env_exec )

Execute a T_BYTECODE cell that has been NCG-compiled.

◆ ncg_eval_argv()

Sexpr_t Lamb::ncg_eval_argv ( Sexpr_t bc,
Sexpr_t * argv,
int nargs,
Sexpr_t env_exec )

Phase 6: direct NCG→NCG call; takes argv[] instead of list, skips apply_proc_partial.

◆ ncg_compile()

Sexpr_t Lamb::ncg_compile ( Sexpr_t bc,
Sexpr_t env_exec,
Sexpr_t captured_names = nullptr )

Compile a T_BYTECODE cell to native code, and record it in the procedure's own native-code slot. (The slot table is internal: it is declared in ll_vm_ncg.h, which is not part of the customer package, so this comment describes the effect rather than naming a constant the reader cannot see.).

◆ ncg_disassemble()

void Lamb::ncg_disassemble ( Sexpr_t bc,
Sexpr_t env_exec )

Log basic info about an NCG-compiled proc.

◆ ncg_vector_set_ptr()

void Lamb::ncg_vector_set_ptr ( Sexpr_t * data_ptr,
LL_int32 k,
Sexpr_t val )

Write-barrier store for inlined NCG_STORE_LOCAL (Phase 1).

◆ ncg_vref_fast()

Sexpr_t Lamb::ncg_vref_fast ( Sexpr_t vec,
Sexpr_t idx )

fast vector-ref (T_SVEC + T_INT32 fast path).

◆ ncg_vset_fast()

Sexpr_t Lamb::ncg_vset_fast ( Sexpr_t vec,
Sexpr_t idx,
Sexpr_t val )

fast vector-set (write-barrier inline).

◆ ncg_cons_fast()

Sexpr_t Lamb::ncg_cons_fast ( Sexpr_t a,
Sexpr_t d )

fast cons; returns nullptr if Nfree<=yuasa_M or sweeping.

◆ ncg_box_fast()

Sexpr_t Lamb::ncg_box_fast ( LL_int64 v)

fast integer box; nullptr if Nfree<=yuasa_M or sweeping.

◆ eval_recursion_overflow()

void Lamb::eval_recursion_overflow ( const char * where)

cold path: throws the catchable "recursion too deep" error

◆ eval_recursion_check()

void Lamb::eval_recursion_check ( const char * where)
inline

< hot path: cheap C-stack + root-stack depth check at a recursion entry

◆ diag_memory_report()

void Lamb::diag_memory_report ( )

NOT #if LL_ESP32: the report is platform-independent now; a host answers "HEAP".

dump GC-buffer / cell-block placement (INTERNAL vs PSRAM) + sizes.

Member Data Documentation

◆ ncg_frame_top

NcgFrame* Lamb::ncg_frame_top = nullptr

linked list of live frames; head threaded through frame->prev

◆ ncg_frame_pool_head

NcgFrame* Lamb::ncg_frame_pool_head = nullptr

free-list head; recycled frames linked via frame->prev

◆ ncg_frame_pool_count

int Lamb::ncg_frame_pool_count = 0

frames currently available in the pool

◆ ncg_err_ctx

NcgErrJmp* Lamb::ncg_err_ctx = nullptr

innermost escape context; set by ncg_eval before JIT call

◆ eval_stack_base_

const char* Lamb::eval_stack_base_ = nullptr

highest stack address seen at a recursion entry (== outermost frame)

◆ eval_stack_budget_

size_t Lamb::eval_stack_budget_ = LL_EVAL_STACK_BUDGET

trip threshold: bytes of C-stack growth below base; tunable at runtime

◆ eval_root_depth_max_

LL_int32 Lamb::eval_root_depth_max_ = LL_EVAL_ROOT_DEPTH_MAX

trip threshold: rootstack depth (cap is 65536); tunable at runtime