feat(types): BigInt + Decimal exact economy numbers (#52)
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:
parent
2c9f9ac549
commit
bd6b12d2ea
35 changed files with 23274 additions and 20791 deletions
13
changes/types-bignum-decimal.md
Normal file
13
changes/types-bignum-decimal.md
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
bump: minor
|
||||
type: feat
|
||||
**Exact numbers for game economies — `BigInt` + `Decimal` (#52, types phase 2).**
|
||||
The thing a tycoon or idle game must never round wrong. `BigInt.*` is an
|
||||
arbitrary-precision integer (sign-magnitude, base-1e9 limbs) with `from`/`parse`,
|
||||
`add`/`sub`/`mul`/`pow`, integer `div`/`mod`, `cmp`/`eq`/`is_zero`, `to_int` and
|
||||
`str` — for idle counters and exact huge currencies that overflow a 32/64-bit
|
||||
int. `Decimal.*` is an exact base-10 fixed-point number (a `BigInt` mantissa
|
||||
plus a decimal scale) with `from`/`parse`, exact `add`/`sub`/`mul`, `cmp`/`eq`,
|
||||
`scale`/`rescale` and `str` — so money like `0.10 + 0.20` is exactly `0.30`, no
|
||||
binary rounding. Both are exact and therefore deterministic; no `f32`/`f64`. The
|
||||
engine (runtime/native/bignum.ludic) is spliced in on demand, so it costs
|
||||
nothing when unused and works in plain tools as well as games.
|
||||
7
docs/language/bigint/_section.md
Normal file
7
docs/language/bigint/_section.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
id: bigint
|
||||
title: BigInt
|
||||
order: 10
|
||||
---
|
||||
|
||||
Arbitrary-precision integers with no upper bound, for idle/incremental counters, exact huge currencies, and score arithmetic that a 32- or 64-bit <code>int</code> would overflow. A <code>BigInt</code> is a sign-magnitude number stored as base-1e9 limbs, so every operation is <strong>exact</strong> and therefore deterministic — bit-identical on every platform, with no binary floating point. Build one with <code>BigInt.from</code> (an <code>int</code>) or <code>BigInt.parse</code> (decimal text), combine with <code>add</code> / <code>sub</code> / <code>mul</code> / <code>pow</code> / <code>div</code> / <code>mod</code>, compare with <code>cmp</code> / <code>eq</code> / <code>is_zero</code>, and render with <code>str</code>. Arguments are positional. The runtime is spliced in only when a program mentions <code>BigInt.*</code>.
|
||||
22
docs/language/bigint/bigint-add.md
Normal file
22
docs/language/bigint/bigint-add.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: bigint-add
|
||||
name: BigInt.add
|
||||
category: bigint
|
||||
kind: namespace-method
|
||||
tokens: BigInt.add
|
||||
sig: BigInt.add(a, b) -> BigInt
|
||||
tip: Exact sum of two big integers.
|
||||
order: 2
|
||||
ns: BigInt
|
||||
member: add
|
||||
---
|
||||
|
||||
Returns the exact sum <code>a + b</code>. There is no overflow — the result grows as many digits as it needs.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let total = BigInt.add(score, reward)
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/bigint/bigint-cmp.md
Normal file
22
docs/language/bigint/bigint-cmp.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: bigint-cmp
|
||||
name: BigInt.cmp
|
||||
category: bigint
|
||||
kind: namespace-method
|
||||
tokens: BigInt.cmp
|
||||
sig: BigInt.cmp(a, b) -> int
|
||||
tip: Compare two big integers: -1, 0, or 1.
|
||||
order: 9
|
||||
ns: BigInt
|
||||
member: cmp
|
||||
---
|
||||
|
||||
Compares two big integers, returning <code>-1</code>, <code>0</code>, or <code>1</code> as <code>a</code> is less than, equal to, or greater than <code>b</code>.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
if BigInt.cmp(score, best) > 0 { best = score }
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/bigint/bigint-div.md
Normal file
22
docs/language/bigint/bigint-div.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: bigint-div
|
||||
name: BigInt.div
|
||||
category: bigint
|
||||
kind: namespace-method
|
||||
tokens: BigInt.div
|
||||
sig: BigInt.div(a, d) -> BigInt
|
||||
tip: Divide a big integer by an int (toward zero).
|
||||
order: 7
|
||||
ns: BigInt
|
||||
member: div
|
||||
---
|
||||
|
||||
Divides a <code>BigInt</code> by an <code>int</code> divisor, truncating toward zero. Handy for splitting an exact total into equal shares.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let each = BigInt.div(pot, players)
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/bigint/bigint-eq.md
Normal file
22
docs/language/bigint/bigint-eq.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: bigint-eq
|
||||
name: BigInt.eq
|
||||
category: bigint
|
||||
kind: namespace-method
|
||||
tokens: BigInt.eq
|
||||
sig: BigInt.eq(a, b) -> bool
|
||||
tip: True when two big integers are equal.
|
||||
order: 10
|
||||
ns: BigInt
|
||||
member: eq
|
||||
---
|
||||
|
||||
Returns true when two big integers have exactly the same value.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
if BigInt.eq(score, target) { win() }
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/bigint/bigint-from.md
Normal file
22
docs/language/bigint/bigint-from.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: bigint-from
|
||||
name: BigInt.from
|
||||
category: bigint
|
||||
kind: namespace-method
|
||||
tokens: BigInt.from
|
||||
sig: BigInt.from(value) -> BigInt
|
||||
tip: Turn a plain int into a BigInt.
|
||||
order: 0
|
||||
ns: BigInt
|
||||
member: from
|
||||
---
|
||||
|
||||
Lifts a plain <code>int</code> into a <code>BigInt</code>, the starting point for exact arbitrary-precision arithmetic that would overflow an ordinary integer.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let n = BigInt.from(1000000)
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/bigint/bigint-is_zero.md
Normal file
22
docs/language/bigint/bigint-is_zero.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: bigint-is_zero
|
||||
name: BigInt.is_zero
|
||||
category: bigint
|
||||
kind: namespace-method
|
||||
tokens: BigInt.is_zero
|
||||
sig: BigInt.is_zero(a) -> bool
|
||||
tip: True when a big integer is zero.
|
||||
order: 11
|
||||
ns: BigInt
|
||||
member: is_zero
|
||||
---
|
||||
|
||||
Returns true when the value is exactly zero — cheaper and clearer than comparing against <code>BigInt.from(0)</code>.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
if BigInt.is_zero(balance) { gameOver() }
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/bigint/bigint-mod.md
Normal file
22
docs/language/bigint/bigint-mod.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: bigint-mod
|
||||
name: BigInt.mod
|
||||
category: bigint
|
||||
kind: namespace-method
|
||||
tokens: BigInt.mod
|
||||
sig: BigInt.mod(a, d) -> int
|
||||
tip: Remainder of a big integer divided by an int.
|
||||
order: 8
|
||||
ns: BigInt
|
||||
member: mod
|
||||
---
|
||||
|
||||
Returns the remainder of <code>a</code> divided by an <code>int</code> divisor, carrying the sign of <code>a</code>. Pairs with <code>BigInt.div</code>.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let leftover = BigInt.mod(pot, players)
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/bigint/bigint-mul.md
Normal file
22
docs/language/bigint/bigint-mul.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: bigint-mul
|
||||
name: BigInt.mul
|
||||
category: bigint
|
||||
kind: namespace-method
|
||||
tokens: BigInt.mul
|
||||
sig: BigInt.mul(a, b) -> BigInt
|
||||
tip: Exact product of two big integers.
|
||||
order: 4
|
||||
ns: BigInt
|
||||
member: mul
|
||||
---
|
||||
|
||||
Returns the exact product <code>a * b</code>. Multiplying two large counters never loses precision — the classic idle-game growth step.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let next = BigInt.mul(count, multiplier)
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/bigint/bigint-neg.md
Normal file
22
docs/language/bigint/bigint-neg.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: bigint-neg
|
||||
name: BigInt.neg
|
||||
category: bigint
|
||||
kind: namespace-method
|
||||
tokens: BigInt.neg
|
||||
sig: BigInt.neg(a) -> BigInt
|
||||
tip: Negate a big integer.
|
||||
order: 5
|
||||
ns: BigInt
|
||||
member: neg
|
||||
---
|
||||
|
||||
Returns <code>-a</code> — the same magnitude with the opposite sign.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let debt = BigInt.neg(balance)
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/bigint/bigint-parse.md
Normal file
22
docs/language/bigint/bigint-parse.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: bigint-parse
|
||||
name: BigInt.parse
|
||||
category: bigint
|
||||
kind: namespace-method
|
||||
tokens: BigInt.parse
|
||||
sig: BigInt.parse(text) -> BigInt
|
||||
tip: Parse decimal text into a BigInt.
|
||||
order: 1
|
||||
ns: BigInt
|
||||
member: parse
|
||||
---
|
||||
|
||||
Parses decimal text (an optional leading sign then digits) into a <code>BigInt</code> — the way to enter a value too large for an <code>int</code> literal.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let huge = BigInt.parse("123456789012345678901234567890")
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/bigint/bigint-pow.md
Normal file
22
docs/language/bigint/bigint-pow.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: bigint-pow
|
||||
name: BigInt.pow
|
||||
category: bigint
|
||||
kind: namespace-method
|
||||
tokens: BigInt.pow
|
||||
sig: BigInt.pow(a, exp) -> BigInt
|
||||
tip: Raise a big integer to an int power.
|
||||
order: 6
|
||||
ns: BigInt
|
||||
member: pow
|
||||
---
|
||||
|
||||
Raises <code>a</code> to the (non-negative) integer power <code>exp</code> by binary exponentiation — an exact way to reach astronomically large magnitudes.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let googol = BigInt.pow(BigInt.from(10), 100)
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/bigint/bigint-str.md
Normal file
22
docs/language/bigint/bigint-str.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: bigint-str
|
||||
name: BigInt.str
|
||||
category: bigint
|
||||
kind: namespace-method
|
||||
tokens: BigInt.str
|
||||
sig: BigInt.str(a) -> string
|
||||
tip: Render a big integer as decimal text.
|
||||
order: 13
|
||||
ns: BigInt
|
||||
member: str
|
||||
---
|
||||
|
||||
Renders the full value as decimal text, with a leading <code>-</code> when negative. This is how you display or serialize a <code>BigInt</code>.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
Screen.draw_text(8, 8, BigInt.str(score), Color.White, 1)
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/bigint/bigint-sub.md
Normal file
22
docs/language/bigint/bigint-sub.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: bigint-sub
|
||||
name: BigInt.sub
|
||||
category: bigint
|
||||
kind: namespace-method
|
||||
tokens: BigInt.sub
|
||||
sig: BigInt.sub(a, b) -> BigInt
|
||||
tip: Exact difference of two big integers.
|
||||
order: 3
|
||||
ns: BigInt
|
||||
member: sub
|
||||
---
|
||||
|
||||
Returns the exact difference <code>a - b</code>; the result is negative when <code>b</code> is larger.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let remaining = BigInt.sub(bank, cost)
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/bigint/bigint-to_int.md
Normal file
22
docs/language/bigint/bigint-to_int.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: bigint-to_int
|
||||
name: BigInt.to_int
|
||||
category: bigint
|
||||
kind: namespace-method
|
||||
tokens: BigInt.to_int
|
||||
sig: BigInt.to_int(a) -> int
|
||||
tip: Narrow a big integer to a plain int (clamped).
|
||||
order: 12
|
||||
ns: BigInt
|
||||
member: to_int
|
||||
---
|
||||
|
||||
Returns the value as a plain <code>int</code> when it fits in 32 bits, clamping to the int min/max otherwise. Use it to feed a small big-integer back into ordinary code.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let n = BigInt.to_int(BigInt.from(2024))
|
||||
}
|
||||
}
|
||||
```
|
||||
7
docs/language/decimal/_section.md
Normal file
7
docs/language/decimal/_section.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
id: decimal
|
||||
title: Decimal
|
||||
order: 11
|
||||
---
|
||||
|
||||
Exact base-10 fixed-point numbers for game economies — prices, balances and taxes on values like <code>0.10</code> that binary floating point cannot represent, so they always add up exactly. A <code>Decimal</code> is a <code>BigInt</code> mantissa with a decimal <code>scale</code> (the number of digits after the point), giving unbounded range and exact <code>add</code> / <code>sub</code> / <code>mul</code>. Build one with <code>Decimal.from</code> (an <code>int</code>) or <code>Decimal.parse</code> (text like <code>"19.99"</code>), compare with <code>cmp</code> / <code>eq</code>, change precision with <code>rescale</code> (truncates toward zero), read the current precision with <code>scale</code>, and render with <code>str</code>. Every operation is exact and deterministic. The runtime is spliced in only when a program mentions <code>Decimal.*</code> (or <code>BigInt.*</code>).
|
||||
22
docs/language/decimal/decimal-add.md
Normal file
22
docs/language/decimal/decimal-add.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: decimal-add
|
||||
name: Decimal.add
|
||||
category: decimal
|
||||
kind: namespace-method
|
||||
tokens: Decimal.add
|
||||
sig: Decimal.add(a, b) -> Decimal
|
||||
tip: Exact sum of two decimals.
|
||||
order: 2
|
||||
ns: Decimal
|
||||
member: add
|
||||
---
|
||||
|
||||
Returns the exact sum, aligning the two scales first so nothing is rounded. <code>0.10 + 0.20</code> is exactly <code>0.30</code>.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let total = Decimal.add(subtotal, tax)
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/decimal/decimal-cmp.md
Normal file
22
docs/language/decimal/decimal-cmp.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: decimal-cmp
|
||||
name: Decimal.cmp
|
||||
category: decimal
|
||||
kind: namespace-method
|
||||
tokens: Decimal.cmp
|
||||
sig: Decimal.cmp(a, b) -> int
|
||||
tip: Compare two decimals: -1, 0, or 1.
|
||||
order: 6
|
||||
ns: Decimal
|
||||
member: cmp
|
||||
---
|
||||
|
||||
Compares two decimals regardless of their scales, returning <code>-1</code>, <code>0</code>, or <code>1</code>.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
if Decimal.cmp(balance, price) < 0 { deny() }
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/decimal/decimal-eq.md
Normal file
22
docs/language/decimal/decimal-eq.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: decimal-eq
|
||||
name: Decimal.eq
|
||||
category: decimal
|
||||
kind: namespace-method
|
||||
tokens: Decimal.eq
|
||||
sig: Decimal.eq(a, b) -> bool
|
||||
tip: True when two decimals are equal in value.
|
||||
order: 7
|
||||
ns: Decimal
|
||||
member: eq
|
||||
---
|
||||
|
||||
Returns true when two decimals are equal in value even if written at different scales — <code>1.5</code> equals <code>1.50</code>.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
if Decimal.eq(paid, price) { accept() }
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/decimal/decimal-from.md
Normal file
22
docs/language/decimal/decimal-from.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: decimal-from
|
||||
name: Decimal.from
|
||||
category: decimal
|
||||
kind: namespace-method
|
||||
tokens: Decimal.from
|
||||
sig: Decimal.from(value) -> Decimal
|
||||
tip: Turn a whole int into a Decimal.
|
||||
order: 0
|
||||
ns: Decimal
|
||||
member: from
|
||||
---
|
||||
|
||||
Lifts a whole <code>int</code> into a <code>Decimal</code> with scale zero — a starting point for exact money arithmetic.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let qty = Decimal.from(3)
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/decimal/decimal-mul.md
Normal file
22
docs/language/decimal/decimal-mul.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: decimal-mul
|
||||
name: Decimal.mul
|
||||
category: decimal
|
||||
kind: namespace-method
|
||||
tokens: Decimal.mul
|
||||
sig: Decimal.mul(a, b) -> Decimal
|
||||
tip: Exact product of two decimals.
|
||||
order: 4
|
||||
ns: Decimal
|
||||
member: mul
|
||||
---
|
||||
|
||||
Returns the exact product; the result's scale is the sum of the operands' scales, so no precision is lost.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let line = Decimal.mul(price, quantity)
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/decimal/decimal-neg.md
Normal file
22
docs/language/decimal/decimal-neg.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: decimal-neg
|
||||
name: Decimal.neg
|
||||
category: decimal
|
||||
kind: namespace-method
|
||||
tokens: Decimal.neg
|
||||
sig: Decimal.neg(a) -> Decimal
|
||||
tip: Negate a decimal.
|
||||
order: 5
|
||||
ns: Decimal
|
||||
member: neg
|
||||
---
|
||||
|
||||
Returns <code>-a</code> at the same scale — the same magnitude with the opposite sign.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let refund = Decimal.neg(charge)
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/decimal/decimal-parse.md
Normal file
22
docs/language/decimal/decimal-parse.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: decimal-parse
|
||||
name: Decimal.parse
|
||||
category: decimal
|
||||
kind: namespace-method
|
||||
tokens: Decimal.parse
|
||||
sig: Decimal.parse(text) -> Decimal
|
||||
tip: Parse decimal text like "19.99".
|
||||
order: 1
|
||||
ns: Decimal
|
||||
member: parse
|
||||
---
|
||||
|
||||
Parses text like <code>"19.99"</code> or <code>"0.10"</code> into an exact <code>Decimal</code>, remembering how many digits followed the point. This is how you enter a price the way binary floating point never could.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let price = Decimal.parse("19.99")
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/decimal/decimal-rescale.md
Normal file
22
docs/language/decimal/decimal-rescale.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: decimal-rescale
|
||||
name: Decimal.rescale
|
||||
category: decimal
|
||||
kind: namespace-method
|
||||
tokens: Decimal.rescale
|
||||
sig: Decimal.rescale(d, places) -> Decimal
|
||||
tip: Change precision (truncates toward zero).
|
||||
order: 9
|
||||
ns: Decimal
|
||||
member: rescale
|
||||
---
|
||||
|
||||
Changes the number of fractional digits. Increasing precision is exact; decreasing it truncates toward zero — how you round a computed price down to whole cents.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let cents = Decimal.rescale(raw, 2)
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/decimal/decimal-scale.md
Normal file
22
docs/language/decimal/decimal-scale.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: decimal-scale
|
||||
name: Decimal.scale
|
||||
category: decimal
|
||||
kind: namespace-method
|
||||
tokens: Decimal.scale
|
||||
sig: Decimal.scale(d) -> int
|
||||
tip: The number of digits after the point.
|
||||
order: 8
|
||||
ns: Decimal
|
||||
member: scale
|
||||
---
|
||||
|
||||
Returns the current scale — how many digits sit after the decimal point.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let places = Decimal.scale(price)
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/decimal/decimal-str.md
Normal file
22
docs/language/decimal/decimal-str.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: decimal-str
|
||||
name: Decimal.str
|
||||
category: decimal
|
||||
kind: namespace-method
|
||||
tokens: Decimal.str
|
||||
sig: Decimal.str(d) -> string
|
||||
tip: Render a decimal as text.
|
||||
order: 10
|
||||
ns: Decimal
|
||||
member: str
|
||||
---
|
||||
|
||||
Renders the value as text with its decimal point in place (a leading <code>-</code> when negative). This is how you display or serialize money.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
Screen.draw_text(8, 8, Decimal.str(balance), Color.White, 1)
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/decimal/decimal-sub.md
Normal file
22
docs/language/decimal/decimal-sub.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: decimal-sub
|
||||
name: Decimal.sub
|
||||
category: decimal
|
||||
kind: namespace-method
|
||||
tokens: Decimal.sub
|
||||
sig: Decimal.sub(a, b) -> Decimal
|
||||
tip: Exact difference of two decimals.
|
||||
order: 3
|
||||
ns: Decimal
|
||||
member: sub
|
||||
---
|
||||
|
||||
Returns the exact difference <code>a - b</code>, aligning scales so balances stay penny-accurate.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let change = Decimal.sub(paid, price)
|
||||
}
|
||||
}
|
||||
```
|
||||
30
examples/library/bignum.ludic
Normal file
30
examples/library/bignum.ludic
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# bignum.ludic — BigInt.* (arbitrary-precision integers) and Decimal.* (exact
|
||||
# base-10 money). Each assertion that holds prints its number, so a full run
|
||||
# prints:
|
||||
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
||||
# Both types are exact, so they are deterministic (see runtime/native/bignum.ludic).
|
||||
program BigNum {
|
||||
entry {
|
||||
# --- BigInt: exact integers with no upper bound ---
|
||||
let a = BigInt.parse("123456789012345678901234567890")
|
||||
let b = BigInt.from(1000000)
|
||||
if BigInt.str(BigInt.mul(a, b)) == "123456789012345678901234567890000000" { print(1) }
|
||||
if BigInt.str(BigInt.pow(BigInt.from(2), 100)) == "1267650600228229401496703205376" { print(2) }
|
||||
if BigInt.str(BigInt.add(a, a)) == "246913578024691357802469135780" { print(3) }
|
||||
if BigInt.str(BigInt.sub(BigInt.from(5), BigInt.from(12))) == "-7" { print(4) }
|
||||
if BigInt.cmp(a, b) == 1 { print(5) }
|
||||
if BigInt.eq(BigInt.from(42), BigInt.parse("42")) { print(6) }
|
||||
if BigInt.str(BigInt.div(BigInt.parse("1000000000000000000"), 7)) == "142857142857142857" { print(7) }
|
||||
if BigInt.mod(BigInt.from(17), 5) == 2 { print(8) }
|
||||
if BigInt.is_zero(BigInt.sub(a, a)) { print(9) }
|
||||
if BigInt.to_int(BigInt.from(2024)) == 2024 { print(10) }
|
||||
|
||||
# --- Decimal: money that adds up exactly (binary floats can't) ---
|
||||
if Decimal.str(Decimal.add(Decimal.parse("0.10"), Decimal.parse("0.20"))) == "0.30" { print(11) }
|
||||
if Decimal.str(Decimal.mul(Decimal.parse("19.99"), Decimal.from(3))) == "59.97" { print(12) }
|
||||
if Decimal.eq(Decimal.parse("1.5"), Decimal.parse("1.50")) { print(13) }
|
||||
if Decimal.str(Decimal.rescale(Decimal.parse("3.14159"), 2)) == "3.14" { print(14) }
|
||||
if Decimal.str(Decimal.sub(Decimal.parse("100.00"), Decimal.parse("0.01"))) == "99.99" { print(15) }
|
||||
if Decimal.cmp(Decimal.parse("2.5"), Decimal.parse("2.49")) == 1 { print(16) }
|
||||
}
|
||||
}
|
||||
380
runtime/native/bignum.ludic
Normal file
380
runtime/native/bignum.ludic
Normal file
|
|
@ -0,0 +1,380 @@
|
|||
# ============================================================================
|
||||
# bignum.ludic — arbitrary-precision integers (`BigInt.*`) and exact base-10
|
||||
# decimals (`Decimal.*`), written in Ludic.
|
||||
#
|
||||
# The thing a tycoon or idle game must never get wrong: money that adds up
|
||||
# exactly, and counters that grow past what a 32- or 64-bit int can hold. Both
|
||||
# types are EXACT, so they are deterministic — the same guarantee the rest of
|
||||
# the runtime gives, with no binary floating point anywhere.
|
||||
#
|
||||
# BigInt — a sign-magnitude integer of unbounded size. Limbs are base 1e9
|
||||
# (nine decimal digits each), little-endian, in a `words` buffer, so
|
||||
# printing is just per-limb decimal with zero-padding. Multiplication
|
||||
# accumulates in i64 (`long`) so a limb-by-limb product never
|
||||
# overflows. add / sub / mul / pow / compare / divide-by-int.
|
||||
# Decimal — a BigInt mantissa plus a decimal `scale` (digits after the point),
|
||||
# so a value is mantissa x 10^-scale. add / sub align scales and stay
|
||||
# exact; mul adds scales; `rescale` truncates toward zero. Perfect for
|
||||
# prices, balances and taxes on values like 0.10 that binary can't
|
||||
# represent.
|
||||
#
|
||||
# ludicc splices this file into any program that mentions `BigInt.*` or
|
||||
# `Decimal.*` (like the regex/value runtimes); it is a self-contained fragment
|
||||
# (only compiler intrinsics), so it works in a plain `program { entry }` tool as
|
||||
# well as a game. The namespaces (emit_call.ludic) alias each method to the
|
||||
# matching `bigint_*` / `decimal_*` function below.
|
||||
# ============================================================================
|
||||
|
||||
const BN_BASE: int = 1000000000 # 1e9 — nine decimal digits per limb
|
||||
|
||||
# a sign-magnitude big integer. sign is -1 / 0 / +1 (0 only for the value zero);
|
||||
# limbs are base-1e9, little-endian; n is the number of significant limbs.
|
||||
property BigNum { sign: int = 0, n: int = 0, limbs: words }
|
||||
|
||||
# allocate a BigNum with room for `cap` limbs (at least one), all zeroed
|
||||
function bn_alloc(cap: int) -> BigNum {
|
||||
var c = cap
|
||||
if c < 1 { c = 1 }
|
||||
let b = new BigNum
|
||||
b.sign = 0; b.n = 0; b.limbs = words(c)
|
||||
var i = 0
|
||||
while i < c { b.limbs[i] = 0; i = i + 1 }
|
||||
return b
|
||||
}
|
||||
|
||||
# drop high zero limbs; a magnitude of zero forces sign 0
|
||||
function bn_norm(b: BigNum) -> BigNum {
|
||||
var k = b.n
|
||||
while k > 0 and b.limbs[k - 1] == 0 { k = k - 1 }
|
||||
b.n = k
|
||||
if k == 0 { b.sign = 0 }
|
||||
return b
|
||||
}
|
||||
|
||||
function bigint_zero() -> BigNum { return bn_alloc(1) }
|
||||
|
||||
# an int -> BigNum (widened through i64 so INT_MIN's magnitude is representable)
|
||||
function bigint_from(value: int) -> BigNum {
|
||||
if value == 0 { return bigint_zero() }
|
||||
var sign = 1
|
||||
var v: long = value
|
||||
if v < 0 { sign = -1; v = 0 - v }
|
||||
let b = bn_alloc(3)
|
||||
var i = 0
|
||||
while v > 0 {
|
||||
let q: long = v / BN_BASE
|
||||
let lo: int = v - q * BN_BASE
|
||||
b.limbs[i] = lo
|
||||
v = q
|
||||
i = i + 1
|
||||
}
|
||||
b.n = i; b.sign = sign
|
||||
return bn_norm(b)
|
||||
}
|
||||
|
||||
# decimal text (optionally signed) -> BigNum. Assumes valid digits.
|
||||
function bigint_from_str(text: pointer) -> BigNum {
|
||||
let e = len(text)
|
||||
var i = 0
|
||||
var sign = 1
|
||||
if e > 0 and text[0] == 45 { sign = -1; i = 1 } # '-'
|
||||
if e > 0 and text[0] == 43 { i = 1 } # '+'
|
||||
while i < e - 1 and text[i] == 48 { i = i + 1 } # skip leading zeros
|
||||
let ndig = e - i
|
||||
if ndig <= 0 { return bigint_zero() }
|
||||
let nlimb = (ndig + 8) / 9
|
||||
let b = bn_alloc(nlimb)
|
||||
var li = 0
|
||||
var pos = e
|
||||
while pos > i {
|
||||
var start = pos - 9
|
||||
if start < i { start = i }
|
||||
var v = 0
|
||||
var k = start
|
||||
while k < pos { v = v * 10 + (text[k] - 48); k = k + 1 }
|
||||
b.limbs[li] = v; li = li + 1
|
||||
pos = start
|
||||
}
|
||||
b.n = nlimb; b.sign = sign
|
||||
return bn_norm(b)
|
||||
}
|
||||
|
||||
# compare magnitudes: -1 / 0 / 1
|
||||
function bn_ucmp(a: BigNum, b: BigNum) -> int {
|
||||
if a.n != b.n { if a.n > b.n { return 1 }; return -1 }
|
||||
var i = a.n - 1
|
||||
while i >= 0 {
|
||||
if a.limbs[i] != b.limbs[i] { if a.limbs[i] > b.limbs[i] { return 1 }; return -1 }
|
||||
i = i - 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
# magnitude add (ignores signs)
|
||||
function bn_uadd(a: BigNum, b: BigNum) -> BigNum {
|
||||
var m = a.n
|
||||
if b.n > m { m = b.n }
|
||||
let r = bn_alloc(m + 1)
|
||||
var carry = 0
|
||||
var i = 0
|
||||
while i < m {
|
||||
var s = carry
|
||||
if i < a.n { s = s + a.limbs[i] }
|
||||
if i < b.n { s = s + b.limbs[i] }
|
||||
if s >= BN_BASE { r.limbs[i] = s - BN_BASE; carry = 1 } else { r.limbs[i] = s; carry = 0 }
|
||||
i = i + 1
|
||||
}
|
||||
r.limbs[m] = carry
|
||||
r.n = m + 1
|
||||
return bn_norm(r)
|
||||
}
|
||||
|
||||
# magnitude subtract, assuming |a| >= |b|
|
||||
function bn_usub(a: BigNum, b: BigNum) -> BigNum {
|
||||
let r = bn_alloc(a.n)
|
||||
var borrow = 0
|
||||
var i = 0
|
||||
while i < a.n {
|
||||
var s = a.limbs[i] - borrow
|
||||
if i < b.n { s = s - b.limbs[i] }
|
||||
if s < 0 { s = s + BN_BASE; borrow = 1 } else { borrow = 0 }
|
||||
r.limbs[i] = s
|
||||
i = i + 1
|
||||
}
|
||||
r.n = a.n
|
||||
return bn_norm(r)
|
||||
}
|
||||
|
||||
function bigint_neg(a: BigNum) -> BigNum {
|
||||
let r = bn_alloc(a.n)
|
||||
var i = 0
|
||||
while i < a.n { r.limbs[i] = a.limbs[i]; i = i + 1 }
|
||||
r.n = a.n; r.sign = 0 - a.sign
|
||||
return r
|
||||
}
|
||||
|
||||
function bigint_add(a: BigNum, b: BigNum) -> BigNum {
|
||||
if a.sign == 0 { return b }
|
||||
if b.sign == 0 { return a }
|
||||
if a.sign == b.sign {
|
||||
let r = bn_uadd(a, b); r.sign = a.sign
|
||||
return bn_norm(r)
|
||||
}
|
||||
let c = bn_ucmp(a, b)
|
||||
if c == 0 { return bigint_zero() }
|
||||
if c > 0 { let r = bn_usub(a, b); r.sign = a.sign; return bn_norm(r) }
|
||||
let r = bn_usub(b, a); r.sign = b.sign
|
||||
return bn_norm(r)
|
||||
}
|
||||
|
||||
function bigint_sub(a: BigNum, b: BigNum) -> BigNum { return bigint_add(a, bigint_neg(b)) }
|
||||
|
||||
function bigint_mul(a: BigNum, b: BigNum) -> BigNum {
|
||||
if a.sign == 0 or b.sign == 0 { return bigint_zero() }
|
||||
let r = bn_alloc(a.n + b.n)
|
||||
var i = 0
|
||||
while i < a.n {
|
||||
var carry: long = 0
|
||||
let ai: long = a.limbs[i]
|
||||
var j = 0
|
||||
while j < b.n {
|
||||
let bj: long = b.limbs[j]
|
||||
let cur: long = r.limbs[i + j]
|
||||
let t: long = cur + ai * bj + carry
|
||||
let q: long = t / BN_BASE
|
||||
let lo: int = t - q * BN_BASE
|
||||
r.limbs[i + j] = lo
|
||||
carry = q
|
||||
j = j + 1
|
||||
}
|
||||
var k = i + b.n
|
||||
while carry > 0 {
|
||||
let cur2: long = r.limbs[k]
|
||||
let t2: long = cur2 + carry
|
||||
let q2: long = t2 / BN_BASE
|
||||
let lo2: int = t2 - q2 * BN_BASE
|
||||
r.limbs[k] = lo2
|
||||
carry = q2
|
||||
k = k + 1
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
r.n = a.n + b.n; r.sign = a.sign * b.sign
|
||||
return bn_norm(r)
|
||||
}
|
||||
|
||||
# a^exp for exp >= 0, by binary exponentiation
|
||||
function bigint_pow(a: BigNum, exp: int) -> BigNum {
|
||||
var result = bigint_from(1)
|
||||
var base = a
|
||||
var e = exp
|
||||
while e > 0 {
|
||||
if e - (e / 2) * 2 == 1 { result = bigint_mul(result, base) }
|
||||
base = bigint_mul(base, base)
|
||||
e = e / 2
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
# floor-toward-zero divide by a nonzero int; quotient is a BigNum
|
||||
function bigint_div_int(a: BigNum, d: int) -> BigNum {
|
||||
var dd = d
|
||||
var dsign = 1
|
||||
if d < 0 { dsign = -1; dd = 0 - d }
|
||||
let r = bn_alloc(a.n)
|
||||
var rem: long = 0
|
||||
let ddl: long = dd
|
||||
var i = a.n - 1
|
||||
while i >= 0 {
|
||||
let cur: long = rem * BN_BASE + a.limbs[i]
|
||||
let q: long = cur / ddl
|
||||
rem = cur - q * ddl
|
||||
r.limbs[i] = q
|
||||
i = i - 1
|
||||
}
|
||||
r.n = a.n; r.sign = a.sign * dsign
|
||||
return bn_norm(r)
|
||||
}
|
||||
|
||||
# remainder of dividing by a nonzero int; carries the sign of `a`
|
||||
function bigint_mod_int(a: BigNum, d: int) -> int {
|
||||
var dd = d
|
||||
if d < 0 { dd = 0 - d }
|
||||
let ddl: long = dd
|
||||
var rem: long = 0
|
||||
var i = a.n - 1
|
||||
while i >= 0 {
|
||||
let cur: long = rem * BN_BASE + a.limbs[i]
|
||||
rem = cur - (cur / ddl) * ddl
|
||||
i = i - 1
|
||||
}
|
||||
let r: int = rem
|
||||
return r * a.sign
|
||||
}
|
||||
|
||||
function bigint_cmp(a: BigNum, b: BigNum) -> int {
|
||||
if a.sign != b.sign { if a.sign > b.sign { return 1 }; return -1 }
|
||||
if a.sign == 0 { return 0 }
|
||||
let c = bn_ucmp(a, b)
|
||||
if a.sign > 0 { return c }
|
||||
return 0 - c
|
||||
}
|
||||
|
||||
function bigint_eq(a: BigNum, b: BigNum) -> bool { return bigint_cmp(a, b) == 0 }
|
||||
function bigint_is_zero(a: BigNum) -> bool { return a.sign == 0 }
|
||||
|
||||
# the value as a plain int when it fits in 32 bits, else clamped to the extreme
|
||||
function bigint_to_int(a: BigNum) -> int {
|
||||
if a.sign == 0 { return 0 }
|
||||
var v: long = 0
|
||||
var i = a.n - 1
|
||||
while i >= 0 { v = v * BN_BASE + a.limbs[i]; i = i - 1 }
|
||||
if a.sign < 0 { v = 0 - v }
|
||||
let hi: long = 2147483647
|
||||
let lo: long = 0 - hi - 1
|
||||
if v > hi { let r: int = hi; return r }
|
||||
if v < lo { return 0 - 2147483647 - 1 }
|
||||
let r: int = v
|
||||
return r
|
||||
}
|
||||
|
||||
# a limb (< 1e9) as exactly nine digits, left-padded with zeros
|
||||
function bn_pad9(x: int) -> pointer {
|
||||
var s = string(x)
|
||||
var pad = 9 - len(s)
|
||||
var out = ""
|
||||
while pad > 0 { out = out + "0"; pad = pad - 1 }
|
||||
return out + s
|
||||
}
|
||||
|
||||
function bigint_str(a: BigNum) -> pointer {
|
||||
if a.sign == 0 { return "0" }
|
||||
var out = ""
|
||||
if a.sign < 0 { out = "-" }
|
||||
out = out + string(a.limbs[a.n - 1])
|
||||
var i = a.n - 2
|
||||
while i >= 0 { out = out + bn_pad9(a.limbs[i]); i = i - 1 }
|
||||
return out
|
||||
}
|
||||
|
||||
# ---- Decimal: a BigNum mantissa scaled by 10^-scale ------------------------
|
||||
|
||||
property Dec { m: BigNum, scale: int = 0 }
|
||||
|
||||
function dec_make(m: BigNum, scale: int) -> Dec {
|
||||
let d = new Dec
|
||||
d.m = m; d.scale = scale
|
||||
return d
|
||||
}
|
||||
|
||||
function decimal_from(value: int) -> Dec { return dec_make(bigint_from(value), 0) }
|
||||
|
||||
# "[-]int[.frac]" -> Dec, scale = number of fractional digits
|
||||
function decimal_from_str(text: pointer) -> Dec {
|
||||
let e = len(text)
|
||||
var dot = 0 - 1
|
||||
var i = 0
|
||||
while i < e { if text[i] == 46 { dot = i }; i = i + 1 } # '.'
|
||||
if dot < 0 { return dec_make(bigint_from_str(text), 0) }
|
||||
let intp = text[0..dot]
|
||||
let fracp = text[dot + 1 .. e]
|
||||
let scale = len(fracp)
|
||||
return dec_make(bigint_from_str(intp + fracp), scale)
|
||||
}
|
||||
|
||||
# mantissa of `d` scaled up to `newscale` (newscale >= d.scale), exact
|
||||
function dec_scaled(d: Dec, newscale: int) -> BigNum {
|
||||
let diff = newscale - d.scale
|
||||
if diff <= 0 { return d.m }
|
||||
return bigint_mul(d.m, bigint_pow(bigint_from(10), diff))
|
||||
}
|
||||
|
||||
function decimal_add(a: Dec, b: Dec) -> Dec {
|
||||
var sc = a.scale
|
||||
if b.scale > sc { sc = b.scale }
|
||||
return dec_make(bigint_add(dec_scaled(a, sc), dec_scaled(b, sc)), sc)
|
||||
}
|
||||
|
||||
function decimal_sub(a: Dec, b: Dec) -> Dec {
|
||||
var sc = a.scale
|
||||
if b.scale > sc { sc = b.scale }
|
||||
return dec_make(bigint_sub(dec_scaled(a, sc), dec_scaled(b, sc)), sc)
|
||||
}
|
||||
|
||||
function decimal_mul(a: Dec, b: Dec) -> Dec { return dec_make(bigint_mul(a.m, b.m), a.scale + b.scale) }
|
||||
function decimal_neg(a: Dec) -> Dec { return dec_make(bigint_neg(a.m), a.scale) }
|
||||
|
||||
function decimal_cmp(a: Dec, b: Dec) -> int {
|
||||
var sc = a.scale
|
||||
if b.scale > sc { sc = b.scale }
|
||||
return bigint_cmp(dec_scaled(a, sc), dec_scaled(b, sc))
|
||||
}
|
||||
|
||||
function decimal_eq(a: Dec, b: Dec) -> bool { return decimal_cmp(a, b) == 0 }
|
||||
function decimal_scale(d: Dec) -> int { return d.scale }
|
||||
|
||||
# change the number of fractional digits: scaling up is exact, scaling down
|
||||
# truncates toward zero (drop the extra low digits)
|
||||
function decimal_rescale(d: Dec, places: int) -> Dec {
|
||||
if places >= d.scale { return dec_make(dec_scaled(d, places), places) }
|
||||
var diff = d.scale - places
|
||||
var m = d.m
|
||||
var i = 0
|
||||
while i < diff { m = bigint_div_int(m, 10); i = i + 1 }
|
||||
return dec_make(m, places)
|
||||
}
|
||||
|
||||
function decimal_str(d: Dec) -> pointer {
|
||||
var neg = ""
|
||||
var m = d.m
|
||||
if m.sign < 0 { neg = "-"; m = bigint_neg(m) }
|
||||
var digits = bigint_str(m)
|
||||
if d.scale == 0 { return neg + digits }
|
||||
var nd = len(digits)
|
||||
var pad = d.scale + 1 - nd
|
||||
while pad > 0 { digits = "0" + digits; pad = pad - 1; nd = nd + 1 }
|
||||
let cut = nd - d.scale
|
||||
let ip = digits[0..cut]
|
||||
let fp = digits[cut..nd]
|
||||
return neg + ip + "." + fp
|
||||
}
|
||||
|
|
@ -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") }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
43005
selfhost/ludicc.seed.ll
43005
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
|
|
@ -383,6 +383,35 @@
|
|||
"rect-contains",
|
||||
"rect-intersects"
|
||||
],
|
||||
"bigint": [
|
||||
"bigint-from",
|
||||
"bigint-parse",
|
||||
"bigint-add",
|
||||
"bigint-sub",
|
||||
"bigint-mul",
|
||||
"bigint-neg",
|
||||
"bigint-pow",
|
||||
"bigint-div",
|
||||
"bigint-mod",
|
||||
"bigint-cmp",
|
||||
"bigint-eq",
|
||||
"bigint-is_zero",
|
||||
"bigint-to_int",
|
||||
"bigint-str"
|
||||
],
|
||||
"decimal": [
|
||||
"decimal-from",
|
||||
"decimal-parse",
|
||||
"decimal-add",
|
||||
"decimal-sub",
|
||||
"decimal-mul",
|
||||
"decimal-neg",
|
||||
"decimal-cmp",
|
||||
"decimal-eq",
|
||||
"decimal-scale",
|
||||
"decimal-rescale",
|
||||
"decimal-str"
|
||||
],
|
||||
"duration": [
|
||||
"duration-seconds",
|
||||
"duration-minutes",
|
||||
|
|
|
|||
|
|
@ -193,6 +193,7 @@ function cmd_test() -> int {
|
|||
feat_case("library/crypto", "", "1 2 3 4 5 6 7 8 9", "crypto.ludic (Crypto SHA-256/HMAC/base64 KAT + CSPRNG shape)")
|
||||
feat_case("library/uuid", "", "1 2 3 4 5 6 7 8 9 10", "uuid.ludic (Uuid v4/v7 format, version/variant, parse/equals)")
|
||||
feat_case("library/noise", "", "1 2 3 4 5 6 7 8 9 10 11", "noise.ludic (Noise value/perlin/simplex/fbm/cellular determinism + range)")
|
||||
feat_case("library/bignum", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16", "bignum.ludic (BigInt arbitrary-precision + Decimal exact base-10 money)")
|
||||
feat_case("library/regex", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18", "regex.ludic (Regex match/find/groups/classes/quantifiers/replace + linear-time safety)")
|
||||
feat_case("library/grid", "", "1 2 3 4 5 6 7 8 9 10 11 12 13", "grid.ludic (Grid line/flood/line_of_sight + A* pathfinding over the tilemap)")
|
||||
feat_case("library/anim", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34", "anim.ludic (Anim frame/once/pingpong/cell + Tween progress/loop/yoyo/ease/number/round/point/tint)")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue