feat(types): BigInt + Decimal exact economy numbers (#52)
All checks were successful
bootstrap / cfree-fixpoint (push) Successful in 19s
ci / build-and-test (push) Successful in 1m28s
commit-lint / conventional-commits (push) Successful in 5s
docs / build-and-deploy (push) Successful in 22s

Types phase 2 — the "money problem". A splice-on-demand bignum engine
(runtime/native/bignum.ludic, self-contained, only intrinsics) exposed as
two namespaces:

- BigInt.* — arbitrary-precision integer (sign-magnitude, base-1e9 limbs):
  from/parse, add/sub/mul/pow, div/mod (by int), cmp/eq/is_zero, to_int, str.
  For idle counters and exact huge currencies that overflow a 32/64-bit int.
- Decimal.* — exact base-10 fixed point (BigInt mantissa + decimal scale):
  from/parse, exact add/sub/mul, cmp/eq, scale/rescale (truncate), str.
  So 0.10 + 0.20 is exactly 0.30 — no binary rounding.

Both exact => deterministic; no f32/f64. Wired: parser splice trigger
(g_uses_bignum), emit_call dispatch, reseeded seed, a self-asserting example
(examples/library/bignum.ludic + feat_case), and per-symbol docs + inventory.
All suites green incl. golden renders byte-identical and the bootstrap fixpoint.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-08-31 18:45:14 +03:00
parent 2c9f9ac549
commit bd6b12d2ea
35 changed files with 23274 additions and 20791 deletions

View file

@ -332,6 +332,39 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
# tile char, e.g. '#'. line/flood/a_star return []Cell slices. (Pathfinding
# lives under Grid rather than a `Path` namespace — that name is the filesystem
# paths library.)
# BigInt.* / Decimal.* -> the bignum engine (runtime/native/bignum.ludic,
# spliced on demand). These are ordinary Ludic functions, so the generic call
# path resolves them to @fn_bigint_* / @fn_decimal_* and keeps their return
# types (BigNum / Dec / int / bool / string).
if (ns == "BigInt") {
if (meth == "from") { bare = "bigint_from"; push(labels, "value") }
if (meth == "parse") { bare = "bigint_from_str"; push(labels, "text") }
if (meth == "add") { bare = "bigint_add"; push(labels, "a"); push(labels, "b") }
if (meth == "sub") { bare = "bigint_sub"; push(labels, "a"); push(labels, "b") }
if (meth == "mul") { bare = "bigint_mul"; push(labels, "a"); push(labels, "b") }
if (meth == "neg") { bare = "bigint_neg"; push(labels, "a") }
if (meth == "pow") { bare = "bigint_pow"; push(labels, "a"); push(labels, "exp") }
if (meth == "div") { bare = "bigint_div_int"; push(labels, "a"); push(labels, "d") }
if (meth == "mod") { bare = "bigint_mod_int"; push(labels, "a"); push(labels, "d") }
if (meth == "cmp") { bare = "bigint_cmp"; push(labels, "a"); push(labels, "b") }
if (meth == "eq") { bare = "bigint_eq"; push(labels, "a"); push(labels, "b") }
if (meth == "is_zero") { bare = "bigint_is_zero"; push(labels, "a") }
if (meth == "to_int") { bare = "bigint_to_int"; push(labels, "a") }
if (meth == "str") { bare = "bigint_str"; push(labels, "a") }
}
if (ns == "Decimal") {
if (meth == "from") { bare = "decimal_from"; push(labels, "value") }
if (meth == "parse") { bare = "decimal_from_str"; push(labels, "text") }
if (meth == "add") { bare = "decimal_add"; push(labels, "a"); push(labels, "b") }
if (meth == "sub") { bare = "decimal_sub"; push(labels, "a"); push(labels, "b") }
if (meth == "mul") { bare = "decimal_mul"; push(labels, "a"); push(labels, "b") }
if (meth == "neg") { bare = "decimal_neg"; push(labels, "a") }
if (meth == "cmp") { bare = "decimal_cmp"; push(labels, "a"); push(labels, "b") }
if (meth == "eq") { bare = "decimal_eq"; push(labels, "a"); push(labels, "b") }
if (meth == "scale") { bare = "decimal_scale"; push(labels, "d") }
if (meth == "rescale") { bare = "decimal_rescale"; push(labels, "d"); push(labels, "places") }
if (meth == "str") { bare = "decimal_str"; push(labels, "d") }
}
if (ns == "Grid") {
if (meth == "line") { bare = "grid_line"; push(labels, "x0"); push(labels, "y0"); push(labels, "x1"); push(labels, "y1") }
if (meth == "blocked") { bare = "grid_blocked"; push(labels, "x"); push(labels, "y"); push(labels, "wall") }

View file

@ -175,6 +175,7 @@ function p_postfix() -> Node {
while true {
if is_op(".") { pi = pi + 1; let m = node(E_MEMBER); m.a = e; m.s = eat_id(); e = m
if e.a.kind == E_ID and e.a.s == "Regex" { g_uses_regex = true } # splice the regex runtime on demand
if e.a.kind == E_ID and (e.a.s == "BigInt" or e.a.s == "Decimal") { g_uses_bignum = true } # splice the bignum runtime on demand
if e.a.kind == E_ID and e.a.s == "Query" { g_uses_query = true } # splice the ECS spatial-query runtime on demand
if e.a.kind == E_ID and e.a.s == "Reflect" { g_uses_reflect = true } # force-emit the reflection ABI (Reflect.* reads the world schema)
if e.a.kind == E_ID and e.a.s == "Light" { g_uses_light = true } # splice the 2D light-accumulation pass on demand
@ -421,6 +422,7 @@ function path_join(dir: pointer, rel: pointer) -> pointer {
var loaded_paths: []pointer
var cur_dir: pointer
var g_uses_regex: bool = false # a program mentioned Regex.* -> splice the regex runtime
var g_uses_bignum: bool = false # a program mentioned BigInt.*/Decimal.* -> splice the bignum runtime
var g_uses_query: bool = false # a program mentioned Query.* -> splice the query runtime + reflection ABI
var g_uses_reflect: bool = false # a program mentioned Reflect.* -> force-emit the reflection ABI
var g_uses_light: bool = false # a program mentioned Light.* -> splice the 2D light pass
@ -599,6 +601,13 @@ function maybe_splice_runtime() -> void {
do_import("runtime/native/regex_vm.ludic")
cur_dir = saved
}
# any program that uses BigInt.*/Decimal.* gets the bignum engine spliced in
# (self-contained — only compiler intrinsics — so a plain tool works too).
if g_uses_bignum {
cur_dir = ""
do_import("runtime/native/bignum.ludic")
cur_dir = saved
}
# any program that uses Query.* gets the ECS spatial-query helpers spliced in;
# they read entity state through the reflection ABI (emit_decl force-emits it
# for a Query program even when it declares no events).
@ -713,6 +722,7 @@ function parse_program() -> void {
g_onlisten = new []Node
g_toggled_layers = new []pointer
g_uses_regex = false
g_uses_bignum = false
g_uses_query = false
g_uses_reflect = false
g_uses_esys = false

File diff suppressed because it is too large Load diff