soma.cell

Native handlers

[native] handlers compiled to Rust for numeric hot paths, and what they may contain. Part of the guide to the Soma programming language.

Also as plain text: /llms-native.txt.

The native vocabulary, its limits and costs. The language itself: https://soma-lang.dev/llms.txt (soma docs agent).

Performance

Interpreter is the reference semantics (linear-time map/filter/indexing; a 20k-row CSV aggregation runs in ~0.3 s interpreted). Annotate hot handlers [native] for Rust-codegen on numeric loops (the harness and its C/Rust reference implementations are in bench/); a native compile failure aborts the run (there is no silent fall-back to the interpreter). It needs cargo (rustup) on PATH; the first compile takes seconds and is cached in .soma_cache/ beside the program. Arguments are type-checked at the call like any handler; random(n) / random(lo, hi) are Ints, random() a Float; buffer indexes are Ints from 0 (no negative indexes); an Int literal beyond 64 bits is refused (build it: shl(1, 64) - 1); [loop_bound(N)] is checked; a [native] handler's recursion deeper than 20,000 calls raises stack_overflow (interpreted handlers: 512 — walk deep structures with an explicit stack). Floats print with the shortest round-trip digits; print/to_string never use exponent form (1e-7 prints 0.0000001, a Float inside a printed map or list too; format("%.3e", x) for scientific notation); to_json — HTTP bodies included — uses JSON's shortest form (1.18e21). An SSE data: line and a WebSocket push carry the value as JSON on one line (a String payload is quoted, so a client cannot forge another event). Native math: sqrt, log, exp, pow, abs, min, max, floor, ceil, round, sin, cos, tan, atan, atan2, asin, acos, pi() (asin/acos outside [-1, 1] raise kind range, as interpreted). soma check verifies the native vocabulary (every handler, every error at once). Same semantics as the interpreter: 7 / 2 is 3.5, a division by zero is an ordinary try-catchable error, an Int overflow promotes to BigInt, and an Int slot refuses a non-exact / instead of truncating. One difference: native code is statically typed: a quotient returned or stored as an Int is the exact Int (return a / b gives -7 for -7 / 1; an exact quotient past 2^53 raises natively — use idiv(a, b) there), but inside an expression that needs its type — to_string(a / b) — it is a Float ("-7.0") — soma check warns; write idiv(a, b) for an integer quotient. A native local keeps the type of its let (assigning a String to a number local is a check error).

What a [native] handler may use — nothing else:

  • parameters and locals: Int, Float, Bool, String (a List cannot cross the boundary in either direction: parse the input into buffers inside the handler, return a String)

  • arithmetic, comparisons, if/while/for i in range(a, b), return, sqrt log exp pow abs min max floor ceil round sin cos random, idiv gcd pow_mod sqrt_int, band bor bxor bnot shl shr bit_len bit_test bit_set bit_clr bit_next, to_int to_float to_string, str_len str_at str_eq, sibling [native] handlers

  • arrays: buffer(n) -> Buf (n Ints, zeroed), buf_get(b, i) -> Int, buf_set(b, i, v); buffer_f(n) / buf_get_f / buf_set_f for Floats

  • maps: hashmap() -> HMap (Int → Int), hm_get(m, k), hm_set(m, k, v), hm_inc(m, k), hm_len(m), hm_has(m, k)

  • strings: strbuf() -> SBuf, sb_push(b, s), sb_push_int(b, n), sb_push_char(b, c), sb_len(b), sb_finish(b) -> String; regex_count / regex_replace / regex_match; read_stdin write_str (write_str flushes, and is written once even when an Int overflow re-runs the handler in BigInt mode); read_file is interpreted only (its failure value is a Map): read in an interpreted handler, pass the text

  • a Buf/HMap/SBuf lives in ONE variable of ONE handler: it cannot be re-bound (let t = u u = v — copy elements instead), returned, or passed to a sibling (pack results into a String with strbuf). A Buf holds 64-bit Ints: a value that overflows i64 while stored into it is a range error (scalars promote to BigInt; mask the value or keep it scalar). for takes range(a, b) only (a step → while). A literal / 0 does not compile. soma check reports all of these.

  • one FFI call costs ~2 µs: make the LOOP native, not just the per-element kernel (10^7 sibling calls inside a native loop: 20 ms; 2·10^6 calls from an interpreted loop: 3.5 s). Not available natively: maps/lists/records, think(), memory slots, try, match, string interpolation — call an interpreted handler for those. The buffer / hashmap / strbuf primitives exist ONLY in [native] handlers (soma check refuses them elsewhere); everything else in the list, sin, regex_*, read_stdin included, works in interpreted handlers too.