![]() |
LambLisp 01 Red Fox Alpha
From AI to Actuator
|
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:
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:
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:
| |
| 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.
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) |
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:
| 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.
| 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.
| Lamb::~Lamb | ( | ) |
| Sexpr_t Lamb::setup | ( | void | ) |
Run once after base platform has started. In particular, Serial should be initialized before calling setup().
| 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).
| 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.
| void Lamb::log | ( | const char * | fmt, |
| ... ) |
C-level printf feature with a limit on the length of strings produced. Takes care of log prompt.
| void Lamb::printf | ( | const char * | fmt, |
| ... ) |
C-level printf feature with a limit on the length of strings produced.
| bool Lamb::debug | ( | void | ) |
Return the state of the Lamb internal debug flag.
| void Lamb::debug | ( | bool | onoff | ) |
Set the state of the Lamb debug flag.
| bool Lamb::build_isDebug | ( | ) |
Return true if this build is not checked in. This is unrelated to the runtime debug flag.
| unsigned long Lamb::build_version | ( | ) |
Return the build version as a long integer (implemented as a UTC time stamp).
| unsigned long Lamb::build_UTC | ( | ) |
Return the UTC time of this build.
| unsigned long Lamb::build_pushUTC | ( | ) |
Return the UTC time this repo was last pushed.
| const char * Lamb::build_buildRelease | ( | ) |
Return a pointer to a character array containing the release description.
| const char * Lamb::build_buildDate | ( | ) |
Return a pointer to a character array containing the build date.
| const char * Lamb::build_pushDate | ( | ) |
Return a pointer to a character array containing the date this repo was last pushed.
| void Lamb::expand | ( | ) |
Add another block of cells to the population.
| 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.
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.
| 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.
|
inline |
Cell constructor for normal pairs.
| void Lamb::gc_root_push | ( | Sexpr_t | p | ) |
Preserve the cell given from GC, until popped.
|
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.
| void Lamb::gc_root_pop | ( | LL_int32 | n = 1 | ) |
Release the preserved cells for normal GC processing.
| LL_int32 Lamb::gc_root_depth | ( | ) |
Return current rootstack depth (save before a try block).
| 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.
| 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.
| 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).
| 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.
| 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.
| 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.
| LL_int32 Lamb::gc_Qm | ( | ) | const |
return current mark quantum size.
| void Lamb::gc_pass_n | ( | LL_int32 | nsteps | ) |
run nsteps mark steps; no-op if not in marking phase.
| unsigned long Lamb::mem_dbg_t_gc_total_us | ( | ) |
monotonic total us in gc_pass – measured GC load
| LL_int32 Lamb::mem_dbg_nfree | ( | ) |
free cells right now (instantaneous)
| LL_int32 Lamb::mem_dbg_total_cells | ( | ) |
total cells in the pool right now
| 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.
|
inline |
Frames currently in the free-list pool.
| void Lamb::ncg_frame_pool_init | ( | LL_int32 | n | ) |
Allocate n frames and push them onto the pool free list.
| void Lamb::ncg_frame_pool_free | ( | ) |
release them; nothing did, so every instance leaked the pool.
|
inline |
Return the first element (car) of a pair cell; throws if l is not a pair type.
|
inline |
Return the second element (cdr) of a pair cell; throws if l is not a pair type.
|
inline |
Car of a known pair; handles embedded int32/float32; no type check.
|
inline |
Cdr of a known pair; handles embedded int32/float32; no type check.
|
inline |
Composition: car(car(l)).
|
inline |
Composition: car(cdr(l)); returns the second element of a list.
|
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.
|
inline |
car(c)->mustbe_int32() with no box.
|
inline |
cdr(c)->mustbe_int32() with no box.
|
inline |
car(c)->mustbe_float32() with no box.
|
inline |
cdr(c)->mustbe_float32() with no box.
|
inline |
car(c)->coerce_int32() with no box; truncates an embedded float as the original does.
|
inline |
cdr(c)->coerce_int32() with no box.
|
inline |
car(c)->coerce_int64() with no box.
|
inline |
cdr(c)->coerce_int64() with no box.
|
inline |
car(c)->coerce_float32() with no box.
|
inline |
cdr(c)->coerce_float32() with no box.
|
inline |
car(c)->coerce_float64() with no box.
|
inline |
cdr(c)->coerce_float64() with no box.
|
inline |
Composition: cdr(car(l)).
|
inline |
Composition: cdr(cdr(l)).
|
inline |
Composition: car(cdr(cdr(l))); returns the third element of a list.
|
inline |
Composition: cdr(cdr(cdr(l))).
|
inline |
| 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.
| 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.
| 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.
| Sexpr_t Lamb::reverse_bang | ( | Sexpr_t | l | ) |
Reverse the list in-place and return the new list head (the former list tail).
| 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.
| 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.
| Sexpr_t Lamb::equal_q | ( | Sexpr_t | obj1, |
| Sexpr_t | obj2 ) |
Returns true when obj1 and obj2 are eqv?, and also all their descendants.
| 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.
| 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.
|
inline |
Return the interned symbol for identifier, or HASHF if not present.
|
inline |
Return the interned symbol for identifier, creating it if necessary.
| 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.
|
inline |
Return a new empty dictionary with the given top frame size.
|
inline |
Return a new empty dictionary with alist top frame.
| 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.
|
inline |
Returns a new dictionary with an empty alist top frame and dict as parent.
| 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.
| 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.
| 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.
|
inline |
Returns a new dictionary with the alist bindings added in a new frame on top of the base dictionary.
| Sexpr_t Lamb::dict_append_bang | ( | Sexpr_t | d1, |
| Sexpr_t | d2, | ||
| Sexpr_t | env_exec ) |
Append d2 as a parent dictionary of d1.
| 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.
| 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.
| 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.
| 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.
| 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.
| Sexpr_t Lamb::dict_ref_q | ( | Sexpr_t | dict, |
| Sexpr_t | key ) |
Returns (key value) pair, or false if key is unbound.
| 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.
| 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.
| 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.
| 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.
| 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.
| 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.
| 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.
| Sexpr_t Lamb::dict_analyze | ( | Sexpr_t | dict, |
| LL_int32 | verbosity = 0 ) |
Internal integrity check.
| Sexpr_t Lamb::integrity_check | ( | Sexpr_t | env_exec, |
| LL_int32 | verbosity = 1 ) |
GC heap integrity check; callable from mop3.
|
inline |
Read one S-expression from src; returns OBJ_EOF at end of input.
| 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.
| Sexpr_t Lamb::write_simple | ( | Sexpr_t | sexpr | ) |
Write sexpr using write semantics without following shared structure. Returns OBJ_VOID.
| 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.
| 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.
| 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.
| Sexpr_t Lamb::eval | ( | Sexpr_t | sexpr, |
| Sexpr_t | env_exec ) |
Evaluate the S-expression in the environment provided.
| 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.
| 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.
| Sexpr_t Lamb::map_proc | ( | Sexpr_t | proc, |
| Sexpr_t | lists, | ||
| Sexpr_t | env_exec ) |
Apply the procecure to the lists per R5RS.
|
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.
|
inline |
for with-input-from-file
|
inline |
for with-output-to-file
| Sexpr_t Lamb::load | ( | Charst_t | name, |
| Sexpr_t | env_exec, | ||
| LL_int32 | verbosity = 0 ) |
Load and evaluate S-expressions from the named file.
| 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.
| 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.
| 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.
| 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:
| 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.
| 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.
| Sexpr_t Lamb::vector_copy | ( | Sexpr_t | from, |
| Sexpr_t | env_exec ) |
Return a shallow copy of the S-expression vector from.
| 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.
| 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.
| 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.
| Sexpr_t Lamb::mk_error | ( | Sexpr_t | env_exec, |
| const char * | fmt, | ||
| ... ) |
Allocate a NEW T_ERROR cell. See the irritants overload above.
| 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.
| 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.
| Sexpr_t Lamb::mk_syserror | ( | Sexpr_t | irritants, |
| const char * | fmt, | ||
| ... ) |
As above, with irritants. Still the shared object.
| 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.
|
inline |
Return HASHT or HASHF.
|
inline |
Allocate a new T_CHAR cell holding Unicode codepoint cp.
|
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].
|
inline |
<T_INT32 cell; returns cached cell for small values.
|
inline |
T_INT64 cell (always allocates).
|
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?.
|
inline |
T_FLOAT64 cell.
|
inline |
T_COMPLEX cell.
|
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)
|
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.
| 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.
| 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.
| 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.
| 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:
| 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.
| 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.
|
inline |
Intern str in the oblist and return the symbol S-expression. Two calls with identical strings always return the same cell pointer.
| Sexpr_t Lamb::mk_bytevector | ( | LL_int32 | k, |
| Sexpr_t | env_exec ) |
Simplest heap allocation with no initialization.
| Sexpr_t Lamb::mk_bytevector | ( | LL_int32 | k, |
| Bytest_t | src, | ||
| Sexpr_t | env_exec ) |
Heap allocation with initialization.
| Sexpr_t Lamb::mk_bytevector | ( | LL_int32 | k, |
| LL_int32 | fill, | ||
| Sexpr_t | env_exec ) |
Heap allocation with initialization.
|
inline |
Injection of externally allocated memory, which will not be freed at GC time.
| 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).
| 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).
| 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.
| 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.
| 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.
| 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.
| 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.
| Sexpr_t Lamb::mk_udp_port | ( | unsigned | local_port, |
| Sexpr_t | env_exec ) |
open a UDP datagram socket.
| 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).
| 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.
| 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).
| 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).
| 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).
| 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.
| 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.
| 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.
| 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.
|
inline |
car=0 (no argv variant)
|
inline |
car=argv fn, cdr=standard fn
|
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.
|
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.
|
inline |
As mk_procedure() but returns a T_NPROC cell; arguments are passed unevaluated at call time.
|
inline |
Allocate a T_THUNK_SEXPR trampoline cell: sexpr will be evaluated in env_thunk on the next trampoline iteration.
|
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.
| 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.
| 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.
| Sexpr_t Lamb::macroexpand | ( | Sexpr_t | proc, |
| Sexpr_t | args, | ||
| Sexpr_t | env_exec ) |
Fully expand the macro proc applied to args.
| Sexpr_t Lamb::macroexpand1 | ( | Sexpr_t | proc, |
| Sexpr_t | args, | ||
| Sexpr_t | env_exec ) |
Apply one expansion step of macro proc to args.
| void Lamb::ncg_mark_frames | ( | ) |
Push all NCG live-frame values onto GC markstack; called from start_marking().
| 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.
| 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.
| 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.).
| void Lamb::ncg_disassemble | ( | Sexpr_t | bc, |
| Sexpr_t | env_exec ) |
Log basic info about an NCG-compiled proc.
| 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).
| Sexpr_t Lamb::ncg_vref_fast | ( | Sexpr_t | vec, |
| Sexpr_t | idx ) |
fast vector-ref (T_SVEC + T_INT32 fast path).
| Sexpr_t Lamb::ncg_vset_fast | ( | Sexpr_t | vec, |
| Sexpr_t | idx, | ||
| Sexpr_t | val ) |
fast vector-set (write-barrier inline).
| Sexpr_t Lamb::ncg_cons_fast | ( | Sexpr_t | a, |
| Sexpr_t | d ) |
fast cons; returns nullptr if Nfree<=yuasa_M or sweeping.
| Sexpr_t Lamb::ncg_box_fast | ( | LL_int64 | v | ) |
fast integer box; nullptr if Nfree<=yuasa_M or sweeping.
| void Lamb::eval_recursion_overflow | ( | const char * | where | ) |
cold path: throws the catchable "recursion too deep" error
|
inline |
< hot path: cheap C-stack + root-stack depth check at a recursion entry
| 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.
| NcgFrame* Lamb::ncg_frame_top = nullptr |
linked list of live frames; head threaded through frame->prev
| NcgFrame* Lamb::ncg_frame_pool_head = nullptr |
free-list head; recycled frames linked via frame->prev
| int Lamb::ncg_frame_pool_count = 0 |
frames currently available in the pool
| NcgErrJmp* Lamb::ncg_err_ctx = nullptr |
innermost escape context; set by ncg_eval before JIT call
| const char* Lamb::eval_stack_base_ = nullptr |
highest stack address seen at a recursion entry (== outermost frame)
| size_t Lamb::eval_stack_budget_ = LL_EVAL_STACK_BUDGET |
trip threshold: bytes of C-stack growth below base; tunable at runtime
| LL_int32 Lamb::eval_root_depth_max_ = LL_EVAL_ROOT_DEPTH_MAX |
trip threshold: rootstack depth (cap is 65536); tunable at runtime