Changelog
All notable changes to mino, following Keep a Changelog format.
All notable changes to mino are recorded here. The format follows
Keep a Changelog and the project
adheres to Semantic Versioning.
[0.17.0] — Proper tail calls and core library
Proper tail call optimization in the evaluator. All function calls in
tail position run in constant stack space, including mutual recursion.
Plus ~80 new core.mino definitions bringing the standard library close
to feature parity with core language functions.
Added
- Proper tail calls:
MINO_TAIL_CALLevaluator type. The evaluator tracks tail position and returns a trampoline sentinel instead of recursing.apply_callablehandles bothMINO_RECUR(self-recursion) andMINO_TAIL_CALL(general tail calls).loop/recur/trampolineremain as convenient iteration constructs. - Type predicates:
true?,false?,boolean?,int?,float?,some?,list?,atom?,not-any?,not-every?. - Sequence navigation:
next,nfirst,fnext,nnext. - Map entry accessors:
key,val. - Control flow macros:
if-not,when-not,if-let,when-let,if-some,when-some. - Sequence functions:
last,butlast,nthrest,nthnext,take-last,drop-last,split-at,split-with,mapv,filterv,sort-by. - Collection utilities:
get-in,assoc-in,update-in,merge-with,reduce-kv,replace,str-replace. - Bitwise compositions:
bit-and-not,bit-test,bit-set,bit-clear,bit-flip. - Lazy combinators:
keep,keep-indexed,map-indexed,partition-all,reductions,dedupe. - Higher-order:
every-pred,some-fn,fnil,memoize,trampoline. - Threading macros:
as->,cond->,cond->>. - Iteration:
doto,dotimes,doseq. - Utilities:
remove,vec,rand-int,rand-nth,run!,blank?,comparator,shuffle,time. - Tree walking:
flatten,tree-seq,walk,postwalk,prewalk,postwalk-replace,prewalk-replace. - Regex:
re-seq. - Complex macros:
condp,case,for(single binding with:when). - Test suite: 300 tests, 664 assertions (up from 228/511).
[0.16.0] — Complete C primitive layer
Adds every C primitive needed to implement the non-JVM parts of
clojure.core. The pure mino compositions come in a later version;
this version focuses on the C foundation.
Added
- Math functions:
math-floor,math-ceil,math-round,math-sqrt,math-pow,math-log,math-exp,math-sin,math-cos,math-tan,math-atan2. All thin wrappers around. Constantmath-pi. hash: exposes the internal FNV-1a hash used by HAMT and sets.compare: general comparison returning -1, 0, or 1 for numbers, strings, keywords, and symbols. nil sorts first.sortwith comparator:(sort comp coll)accepts a boolean comparator (like<) or a three-way comparator (likecompare).symbolandkeywordconstructors from strings (reverse ofname).eval: evaluate a form at runtime, exposingmino_evalto mino code.rand: random float in [0.0, 1.0) via ANSI Crand().time-ms: monotonic milliseconds viaclock_gettime. Registered inmino_install_io.- Regex:
re-findandre-matchesvia bundled tiny-regex-c (public domain, ANSI C, all platforms). Supports.,*,+,?,^,$, character classes, and\d,\w,\sshorthand.
Changed
mino-fsnoted in backlog: file system operations belong in a separate library following the babashka/fs pattern.slurp/spitmarked for eventual migration.- Makefile builds
re.ofrom vendoredre.c.
[0.15.0] — Test framework and dogfooding
Replaces all shell test scripts with mino-based tests. The language
now tests itself.
Added
- File argument support:
./mino script.minoevaluates a file and exits. Exit code 1 on eval failure. - CWD-relative module resolver:
(require "test")resolves to./test.mino. Wired inmain.cviamino_set_resolver. exitprimitive:(exit code)terminates the process. Registered inmino_install_io.test.mino: test framework written in mino itself. Implementsdeftest,is,testing, andrun-testsfollowing clojure.test conventions.- Mino test suite: 203 tests with 427 assertions across 16 files, replacing the 371-line smoke.sh and 131-line crash_test.sh.
- Reader fuzz tests: 51 adversarial reader tests in mino using
read-string+try/catch.
Changed
read-stringthrows catchable exceptions on parse errors. Previously propagated as fatal C-level errors; now caught bytry/catchwhen inside atryblock.- Makefile:
make testruns./mino tests/run.mino. - Shell scripts removed:
tests/smoke.shandfuzz/crash_test.shdeleted. No.shfiles in test infra.
[0.14.0] — Lazy sequences, complete C core, core.mino expansion
Lazy sequences land as a first-class type, enabling infinite data
structures and demand-driven evaluation. The C core gains its final
set of primitives; seven sequence operations move from C to lazy mino
implementations. core.mino nearly doubles in size.
Added
- Lazy sequences (
MINO_LAZY): deferred computation with cached results.lazy-seqspecial form captures body and environment; forced on first access viafirst,rest,count, or printing. Thunk and captured env released after forcing for GC. seq: coerce any collection (list, vector, map, set, string, lazy-seq) to a cons sequence. Returns nil for empty collections.realized?: check if a lazy sequence has been forced.dissoc: remove key(s) from a map.mod,rem,quot: arithmetic primitives.moduses floored division,remuses truncated division.- Bitwise operations:
bit-and,bit-or,bit-xor,bit-not,bit-shift-left,bit-shift-right. Integer-only. name: extract string from keyword or symbol.int,float: type coercion between integer and float.char-at: character access by index (returns single-char string).pr-str: print values to string in readable form.read-string: parse one mino form from a string.format: string formatting with%s,%d,%f,%%.- core.mino definitions (~40 new):
second,ffirst,inc,dec,zero?,pos?,neg?,even?,odd?,abs,max,min,not-empty,constantly,boolean,seq?,merge,select-keys,find,zipmap,frequencies,group-by,juxt,mapcat,take-while,drop-while,iterate,cycle,repeatedly,interleave,interpose,distinct,partition,partition-by,doall,dorun.
Breaking
stdlib.minorenamed tocore.mino. The bundled mino source file, Makefile build rule, generated header, and all internal references now usecore.mino/core_mino.h. Embedders that reference the generated header by name must update.map,filter,take,drop,concat,range,repeatmoved from C to core.mino and are now lazy. Code that relied on these being strict (fully realized on return) may behave differently. Usedoallto force eager evaluation where needed.update,some,every?moved from C to core.mino. These are no longer available as C primitives.updatenow accepts extra args:(update m :k f arg1 arg2).rangeandrepeatsignatures changed.repeatnow takes(repeat n x)instead of the old(repeat count value)(same args, but now returns a lazy seq).rangewith no args is no longer supported (was an error before too).- C primitive count: 57 to 50 (net: +11 new, -18 moved to mino).
- Cons printer forces lazy tails for correct output.
list_lengthforces lazy tails for correctcount.
[0.13.0] — Atoms, spit, stdlib architecture
Establishes the three-tier architecture: C runtime (irreducible
primitives), bundled stdlib.mino (macros and compositions), and
future mino-std package. Delivers atoms and spit.
Added
- Atoms (
MINO_ATOM): mutable reference cells for managed state. C API:mino_atom(),mino_atom_deref(),mino_atom_reset(),mino_is_atom(). Primitives:atom,deref,reset!,atom?. Reader macro:@formexpands to(deref form). swap!: stdlib function.(swap! a f x y)sets atom to(f @a x y).defn: stdlib macro.(defn name (params) body)expands to(def name (fn (params) body)). Single-arity.spit: I/O primitive.(spit "path" content)writes to file. Strings write raw bytes; other values write their printed form.
Changed
- stdlib.mino: the standard library is now a standalone
.minofile compiled into the binary at build time (was an inline C string). - Stdlib migration:
not,not=,identity,list,empty?,>,<=,>=, and all ten type predicates (nil?,cons?,string?,number?,keyword?,symbol?,vector?,map?,fn?,set?) moved from C to mino. C primitive count reduced from 72 to 57.
[0.12.0] — Release candidate (alpha)
Quality, polish, and documentation pass. No new language features.
Changed
- Error messages in
letandloopnow include source file and line when available (promoted toset_error_at). - "Unsupported collection" errors now name the type that was actually passed (e.g.,
count: expected a collection, got int). - "Not a function" errors now report the received type (e.g.,
not a function (got string)). - Internal
type_tag_strhelper added for diagnostic formatting.
Added
- Embedding cookbook (
cookbook/): six worked examples demonstrating real-world embedding patterns — config loader, rules engine, REPL-on-socket, plugin host, data pipeline, and game scripting console. - Fuzz harness (
fuzz/): libFuzzer-compatible reader target plus a 57-case adversarial crash test suite (make fuzz-crash). - Map and sequence benchmarks (
bench/map_bench.c,bench/seq_bench.c): HAMT get/assoc scaling, and map/filter/reduce/sort throughput. Invoke viamake bench-mapandmake bench-seq.
Verified
- 258/258 smoke tests pass in all four modes (O0, O0+GC\_STRESS, O2, O2+GC\_STRESS).
- 57/57 adversarial reader inputs handled without crashes.
- All six cookbook examples compile warning-free and produce correct output.
- Benchmark results show expected O(log32 n) scaling for vectors and maps, consistent sequence throughput.
- API review: all 40+ public symbols consistently named, no orphaned declarations, UNSTABLE marker retained (alpha).
- LOC: mino.c ~6,672, mino.h ~352 (within 15k–25k budget).
[0.11.0] — Sequences & remainder of stdlib
Sets, sequence transformations, string operations, and utility functions
round out the core standard library. Strict (non-lazy) semantics
throughout — every sequence operation returns a concrete list or
collection.
Added
- Sets (
MINO_SET): persistent HAMT-backed set type. Reader literal#{...}, printer#{...}, value-based equality, hashing. -hash-set,set?,contains?,disj,geton sets,conjon sets. - Sequence operations (strict, return lists): -
map,filter,reduce(2- and 3-arg),take,drop,range(1/2/3-arg),repeat,concat,into,apply(2+-arg with spread),reverse,sort(natural ordering via merge sort). - All sequence ops work uniformly over lists, vectors, maps (yielding[key value]vectors), sets, and strings (yielding 1-char strings). - String operations:
subs,split,join(1- and 2-arg),starts-with?,ends-with?,includes?,upper-case,lower-case,trim. - Utility primitives:
not,not=,empty?,some,every?,identity. - Stdlib (mino-defined):
comp,partial,complement.
Changed
mino_install_coredocstring updated to list all new bindings.conjextended to support sets.getextended to support sets (returns the element itself if present).countextended to support sets.mino.hgainsMINO_SETin the type enum andmino_set()constructor.- Existing
apropostest updated for expanded binding set.
Notes
- LOC: mino.c ~6,583, mino.h ~352 (well within the 15k–25k budget).
- 258 smoke tests, all passing under normal and
MINO_GC_STRESS=1modes at both-O0and-O2. - Lazy sequences were evaluated and deferred: strict semantics are simpler, more predictable, and a better fit for the embeddable runtime identity. The deviation from the host language is documented.
[0.10.0] — Interactive development
The printer is now cycle-safe, def/defmacro record metadata for
introspection, and a new in-process REPL handle lets a host drive
read-eval-print one line at a time with no thread required.
Added
- Cycle-safe printing:
mino_print_totracks recursion depth and emits#<...>when the depth exceeds 128, preventing stack overflow on deeply nested structures. doc:(doc 'name)returns the docstring attached to adef/defmacrobinding, ornilif none was provided.source:(source 'name)returns the original source form of adef/defmacrobinding.apropos:(apropos "substring")returns a list of symbols whose names contain the given substring, searched across the current environment chain.- Docstring support in
defanddefmacro: An optional string literal between the name and the value/params is recorded as the binding's docstring:(def name "docstring" value),(defmacro name "docstring" (params) body). mino_repl_t— in-process REPL handle:mino_repl_new(env)creates a handle;mino_repl_feed(repl, line, &out)accumulates input and evaluates when a form is complete. ReturnsMINO_REPL_OK,MINO_REPL_MORE, orMINO_REPL_ERROR.mino_repl_freereleases the handle. No thread required — the host controls the call cadence.- Var redefinition with live reference update: Closures that reference root-level vars see updated values after
defredefines them (already the case due to env-chain lookup, now tested explicitly).
Changed
mino_install_coredocstring updated to listdoc,source, andaproposunder reflection primitives.examples/embed.cupdated to demonstrate the REPL handle API.
Notes
- LOC: mino.c ~5,210, mino.h ~338 (within the 15k–25k budget).
- 170 smoke tests, all passing under normal and
MINO_GC_STRESS=1modes at both-O0and-O2.
[0.9.0] — Sandbox, modules, diagnostics
Runtime errors now carry source locations and call-stack traces. Script
code gains try/catch/throw for recoverable exceptions. The core
environment is sandboxed by default — I/O primitives are installed
separately via mino_install_io. A host-supplied module resolver
enables require for file-based modules.
Added
- Source locations: The reader tracks file name and line number; cons cells produced by reading carry
file/lineannotations. Eval errors include afile:line:prefix, and function call errors append a stack trace showing the active call chain. try/catch/throw:tryis a special form:(try body (catch e handler...)).throwraises a script-level exception caught by the nearest enclosingtry; an unhandledthrowbecomes a fatal runtime error. Usessetjmp/longjmpinternally.mino_install_io(env): Installsprintln,prn, andslurp.mino_install_coreno longer installs any I/O primitives — the host opts in by callingmino_install_io.mino_new()installs both for convenience.slurp:(slurp path)reads a file's contents as a string. Only available whenmino_install_iohas been called.require:(require "name")loads a module by name using a host-supplied resolver. Results are cached so subsequent requires of the same name return instantly.mino_set_resolver(fn, ctx): Registers the host resolver callback forrequire.run_errtest helper insmoke.shfor testing error messages.
Changed
- Error buffer enlarged from 256 to 2048 bytes to accommodate stack traces.
mino_install_coreno longer installsprintlnorprn. Existing embedders usingmino_new()are unaffected (it calls bothmino_install_coreandmino_install_io). Embedders callingmino_install_coredirectly must addmino_install_ioto restore the prior behaviour.- REPL (
main.c) callsmino_install_ioaftermino_install_coreand preserves inter-form whitespace so the reader's line counter stays accurate across forms.
Notes
- Stack traces are appended to the error message returned by
mino_last_error(). A future version may expose structured trace access. try/catchcatches only values raised bythrow. Fatal runtime errors (NULLreturns frommino_eval) propagate to the host unmodified.- The module cache and resolver are global (not per-env). Thread safety is not a goal pre-v1.0.
[0.8.0] — Host C API
First draft of the embedding API. An external C program can now create a
runtime, register host functions, evaluate source, call mino functions,
and extract results — all in under 50 lines of glue code. The surface
language gains type predicates, str, and basic I/O. All new symbols are
mino_*-prefixed; the header remains marked UNSTABLE until v1.0.
Added
mino_new()convenience: allocates an env and installs core bindings in one call.mino_eval_string(src, env)reads and evaluates all forms in a C string, returning the last result.mino_load_file(path, env)reads a file from disk and evaluates all forms within it.mino_register_fn(env, name, fn)shorthand for binding a C function as a primitive.mino_call(fn, args, env)applies a callable value (fn, prim, macro) to an argument list from C; returns the result orNULLon error.mino_pcall(fn, args, env, &out)protected variant that returns 0 on success and -1 on error, writing the result through an out-parameter.MINO_HANDLEvalue type for opaque host objects. A handle carries avoid *and a tag string; it self-evaluates, prints as#, compares by pointer identity, and hashes by the host pointer.mino_handle(ptr, tag),mino_handle_ptr(v),mino_handle_tag(v), andmino_is_handle(v)form the C interface.- Type-safe C extraction:
mino_to_int,mino_to_float,mino_to_string,mino_to_bool. Each returns 1 on success and writes through an out-parameter;mino_to_booluses truthiness semantics. mino_set_limit(kind, value)withMINO_LIMIT_STEPS(per-eval step cap) andMINO_LIMIT_HEAP(soft cap on GC-managed bytes). When exceeded the current eval returnsNULLwith a descriptive error. Pass 0 to disable a limit.- Type-predicate primitives in the surface language:
string?,number?,keyword?,symbol?,vector?,map?,fn?. typeprimitive returns the type of its argument as a keyword (:int,:string,:list,:vector,:map,:fn,:keyword,:symbol,:nil,:bool,:float,:macro,:handle).strprimitive concatenates its arguments into a string. String arguments contribute raw content (no quotes); other types use their printer representation; nil contributes nothing.printlnprints its arguments asstrdoes, appends a newline, and returns nil.prnprints each argument in its printer form separated by spaces, appends a newline, and returns nil.examples/embed.c: a 50-line standalone C program demonstrating the full embed lifecycle — create runtime, register a host function, evaluate source, extract a float result.make exampletarget builds and runs the embedding example.- 31 additional smoke-test cases covering type predicates,
type,str,println, andprn(148 cases total).
Notes
The header remains /* UNSTABLE until v1.0.0 */. API additions are
possible through the 0.x series; the v1.0 release freezes the ABI.
Execution limits are global rather than per-env; this simplifies the
implementation while a single-threaded model is the only supported
configuration. The mino_load_file function is the first place the
runtime performs host I/O on behalf of the caller; v0.9 will gate this
behind the capability model.
[0.7.0] — Tracing garbage collection
Replaces the per-allocation malloc/free discipline with a stop-the-world
mark-and-sweep collector. Every heap object the runtime produces — values,
environments, persistent-collection internals, and scratch arrays — is now
tracked by a single registry and reclaimed automatically once it becomes
unreachable. The surface language is unchanged.
Added
gc_hdr_t-prefixed universal allocation path. Every internal allocation (values, vec/HAMT nodes, HAMT entries, env frames, env binding arrays, name strings, reader scratch buffers) carries a header with a type tag, mark bit, size, and registry link.gc_alloc_typedis the single entry; no path creates an unmanaged mino object.- Mark phase traces objects according to their type tag, following every owned pointer the walker knows about. Vector trie leaves versus branches are distinguished by a
is_leafbit on each node so the walker knows what its slots hold. HAMT nodes drive their own walk viabitmap,subnode_mask, andcollision_count. Scratch ptr-arrays (reader buffers, eval temps, prim_vector/hash-map temps) are walked as arrays of gc-managed pointers so partial fills survive allocation mid-loop. - Conservative stack scan, driven by a sorted index of allocation bounds built at the start of each collection.
setjmpflushes callee-saved registers into the collector's frame; the scan walks every aligned machine word between the saved host frame and the collector's own frame, marking any word that lands inside a managed payload (interior pointers supported). Public API entry points —mino_env_new,mino_eval,mino_read,mino_install_core— each record their local stack address so the scan's upper bound always dominates the full host-to-mino call chain even when control re-enters from a shallower frame. - Root set: all
mino_env_treturned bymino_env_new(tracked in a dedicated registry untilmino_env_free), plus the symbol and keyword intern tables, plus the conservative stack scan. - Adaptive collection trigger. The threshold starts at 1 MiB and grows to 2× live-bytes after each sweep, so steady-state programs see amortized constant-factor collection work.
MINO_GC_STRESS=1env var forces collection on every allocation. This is how we validate that no caller holds unrooted pointers across any allocation site.make test-gc-stressruns the full smoke suite under this mode.- 4 new smoke cases exercise long-tail recursion, vector churn, map churn, and closure churn — each allocates orders of magnitude more transient values than any single collection's threshold, so the collector is invoked repeatedly and the live set must survive intact (117 cases total). All pass under stress mode across
-O0,-O1,-O2, and-O3.
Changed
mino_env_freeno longer frees memory synchronously. It unregisters the env from the root set; the next collection reclaims the frame and every value that was reachable only through it. Header docstring updated to reflect the new ownership model.- Every internal
free()call on mino-owned memory has been removed. The collector is the sole path to reclamation. Plainmalloc/freeremain for host-owned scratch (main.c's line buffer, the root-env list node, the collector's own range-index cache). mino_vec_node_tgains a one-byteis_leafflag set by the constructors so the mark walker can interpret slot contents without external context.
Notes
The collector is non-incremental and non-generational; the entire heap
is scanned on each cycle. For the sizes this runtime is meant to embed
at, linear scan over a sorted range index is a good fit, and the 2×
live-bytes threshold keeps mean pause time bounded. The v0.12 release
candidate will profile realistic workloads and decide whether to layer
on an incremental pass.
[0.6.0] — Macros
Lifts the surface language above its primitives. defmacro, quasiquote,
and a small set of in-language threading and short-circuit forms mean
that new control shapes can land without growing the C evaluator.
Added
MINO_MACROvalue type. Shares the closure layout (params, body, captured env) so the same bind/apply path serves both. Printer emits#; equality is identity.defmacrospecial form binds a macro in the root frame. When the evaluator encounters a call whose head resolves to a macro, it applies the macro body to the *unevaluated* arguments and then evaluates the returned form in the caller's environment.- Reader gains `
`,~,~@as shorthands for(quasiquote x),(unquote x),(unquote-splicing x)`. Both backtick and tilde are treated as word breaks so symbols no longer absorb them. quasiquotespecial form walks its template. Vectors and maps are recursed into;(unquote x)evaluatesxand uses the value;(unquote-splicing x)evaluatesx(expected to yield a list) and inlines the elements into the enclosing list.- Variadic parameter lists: a trailing
& restbindsrestto the list of remaining arguments (possibly empty). Works forfn,defmacro, andloop. macroexpand-1(single step) andmacroexpand(to fixed point) primitives expose the expander for inspection.gensymprimitive with an optional string prefix (defaultG__) and a monotonic counter. Macro authors use this to introduce temporaries that won't capture caller-visible names — the 0.x hygiene convention.cons?andnil?predicates. The threading macros usecons?to tell whether a step is a bare symbol or a call form.- In-language stdlib macros defined in mino itself, read + eval'd at core install:
when,cond,and,or,->,->>. Each ships as mino source embedded in the runtime; they are bindings in the root env, not special forms. - 15 additional smoke cases covering
defmacro, quasiquote splicing, variadic params,macroexpand-1,gensymfreshness, and every stdlib macro (113 cases total).
Notes
0.x makes no automatic hygiene promise; macro writers should reach
for gensym when they need an identifier that can't capture anything
the caller introduced. The decision whether to keep gensym-only or
add full hygiene lands in v1.0 triage.
[0.5.0] — Persistent maps
Replaces the map layout with a 32-wide hash array mapped trie. get,
assoc, and update are now sub-linear; maps can be used as map keys,
equality between maps no longer scales quadratically, and lookup no
longer depends on key arity.
Changed
- Map representation is now a HAMT plus a companion insertion-order key vector. Lookup walks the trie for O(log₃₂ n)
get; iteration walks the key vector sokeys,vals, and the printer emit entries in the order they were first inserted — a rebind leaves the slot's position alone. Iteration order is part of the contract. - Equality between maps is O(n log₃₂ n): walk one map's keys and look each up in the other.
mino.hexposes{ root, key_order, len }withmino_hamt_node_tas an opaque forward declaration; header still UNSTABLE until v1.0.
Added
- Hash function compatible with
=. Integral floats hash as the equivalent int so(= 1 1.0)stays consistent between equality and the hash table. Strings, symbols, and keywords carry distinct type tags so byte-equal values of different types hash apart. Vectors hash element-wise; maps XOR-fold entry hashes for order-independent structural hashing. Non-hashable values (primitives, closures) fall back to pointer identity. - Collision handling: when two distinct keys hit the same 32-bit hash, a collision bucket holds them as a linear list at the depth where trie descent can no longer discriminate. Inserting a key whose hash doesn't match the bucket promotes the bucket into a bitmap node that routes the two subtrees separately.
- 5 additional smoke cases locking down map iteration order across literals, rebinds, new-key assoc, printing, and a 200-entry map that crosses several levels of the trie (98 cases total).
Notes
The v0.5 HAMT is the last structural replacement before the GC work
in v0.7; from here the layout stays but the allocator underneath
changes. Semantics remain the contract.
[0.4.0] — Persistent vectors
Replaces the vector layout with a persistent 32-way trie without
changing the surface language. Every vector primitive from v0.3 behaves
identically; the work lives entirely behind the API.
Changed
- Vector representation is now a 32-way persistent trie with a tail buffer. Leaves hold exactly 32 elements; the tail holds the trailing 1..32 so tail appends are O(1) amortized.
conjandassocpath-copy only the walked spine, so successor vectors share structure with their source.nthwalks at most log₃₂ n internal nodes. mino.hexposes the new vector shape as{ root, tail, tail_len, shift, len }withmino_vec_node_tas an opaque forward declaration; the header is still marked UNSTABLE until v1.0.- Element access across all collection primitives —
nth,first,rest,get,count,assoc,conj,update, vector self-eval, structural equality, printer — routes through internalvec_nth/vec_conj1/vec_assoc1helpers. No caller sees the trie layout.
Added
vec_from_array: a bulk build path that freezes the last 1..32 elements as the tail, packs the rest into full leaves, and folds layers 32-to-1 up the spine in a single O(n) pass. Nodes are mutated freely during construction and only become visible as part of the persistent vector when the build completes — the internal "transient" path with no public API.bench/vector_bench.c: a standalone measurement program for bulk build,nth, andassocat sizes 32, 1024, 32768, and 2^20. Wired asmake bench; not run by CI.- 2 additional smoke cases that cross the 32- and 1024-element boundaries and demonstrate structural sharing on a 2000-element
assoc(93 cases total).
Notes
The naïve map layout from v0.3 is still in place. v0.5 replaces it
with a HAMT, again without changing the surface API. The semantics
are the contract, not the layout.
[0.3.0] — Literal vectors, maps, and keywords
Brings the value-oriented data model to the surface language. Programs
can now express structured data literally and manipulate it through
immutable collection primitives.
Added
MINO_KEYWORDvalue type. Reader parses:fooas a keyword (self-evaluating, prints as:foo). Symbols and keywords are interned through process-wide tables so that repeated reads of the same name share storage; equality still falls through to length + byte compare so externally-constructed values compare equal too.MINO_VECTORvalue type with an array-backed representation. Reader parses[a b c]; printer round-trips the same shape. A vector literal is a form, not a datum: the evaluator walks it in order and produces a fresh vector of evaluated elements.MINO_MAPvalue type with parallel (keys, vals) flat arrays. Reader parses{k1 v1 k2 v2}; commas are whitespace. Odd-form contents are a parse error. Map literals self-evaluate keys and values in read order; the constructor resolves duplicate keys by last-write-wins. Equality is structural and order-insensitive.- Collection primitives:
count,nth,first,rest,vector,hash-map,assoc,get,conj,update,keys,vals.first,rest, andcountare polymorphic across cons, vector, map, string, and nil where meaningful.assocworks on both maps and vectors (vector indices may extend one past the end to append).conjprepends to lists, appends to vectors, and accepts[k v]vectors when the target is a map. apply_callablefactored out of the evaluator so primitives (starting withupdate) can call back into user-defined functions with the same trampoline semantics as direct application.- 43 additional smoke-test cases covering keywords, vector and map literals, self-evaluation, and every collection primitive across the shapes they support (91 cases total).
Notes
The v0.3 representations (flat arrays for vectors and maps, linear
scan for map lookup) are intentionally naïve. The public contract is
the primitive signatures and semantics; v0.4 replaces the vector
layout with a persistent 32-way trie and v0.5 replaces the map with a
HAMT, both without changes to the surface API.
[0.2.0] — Core special forms and closures
Locks in lexical scope, first-class functions, and bounded-stack tail
recursion. The evaluator is now expressive enough to define factorial
and fib iteratively and to build and apply higher-order functions.
Added
- Chained environments: each frame carries a parent pointer, lookups walk the chain, and bindings write to the current frame so that
letandfnparameters shadow outer names without mutating them.defalways binds in the root frame regardless of where the form is evaluated from. - Conditional and sequencing:
ifwith an optional else branch (defaulting tonil) that dispatches on truthiness (onlynilandfalseare falsey;0,"", and the empty list are truthy), anddowhich evaluates its forms left to right and returns the last. letwith a flat pair-list binding form ((let (x 1 y 2) body)), sequential evaluation so later bindings may reference earlier ones, and an implicit-do body.fnspecial form and a newMINO_FNvalue type. Closures capture the environment at definition time; applying one binds parameters in a fresh child frame of the captured environment. Arity mismatches produce a clear error. Function values print as#and compare by identity.loopandrecurwith a tail-call trampoline:recuryields a sentinel value that the enclosingfnorloopintercepts to rebind and re-enter its body, so tail recursion is bounded on the C stack (tested to 100k+ iterations). Non-tailrecuris rejected with a clear error.- Chained
<=,>, and>=comparison primitives alongside the existing<and=. - 25 additional smoke-test cases covering the new forms, closures, factorial, fib, and deep tail recursion (48 cases total).
[0.1.0] — Walking skeleton
The first published milestone. Establishes the single-file build, the
public C header, and an end-to-end read-eval-print pipeline.
Added
- Tagged value representation (
mino_val_t) covering nil, boolean, integer, float, string, symbol, cons cell, and primitive function. Singletons for nil/true/false; per-allocation construction for the rest. - Recursive-descent reader for atoms, lists, strings, line comments, numeric literals (integer and floating-point), and the
'quote shorthand. Parse errors are reported viamino_last_error(). - Printer that round-trips the reader's accepted subset, re-escapes string literals, and always emits a decimal point for floats so that printed forms re-read as the same value.
- Tree-walking evaluator with
quoteanddefspecial forms and primitive bindings for+,-,*,/,=,<,car,cdr,cons, andlist. Numeric coercion promotes int to float when any argument is a float;=compares int and float by value. - Single global environment stored as a flat
(name, value)array;defreplaces in place when the name already exists. - Standalone
minobinary providing an interactive REPL with multi-line input support. Prompts and diagnostics are written to stderr so that piped output on stdout remains clean and machine-consumable. tests/smoke.shcovering 23 end-to-end cases through the binary.- GitHub Actions matrix build for
ubuntu-latestandmacos-latest. - MIT license, README, and
.gitignore.