feat(lang,stdlib): 64-bit long type + 64-bit Hash variants (issue #17)
Some checks are pending
docs / build-and-deploy (push) Waiting to run
Some checks are pending
docs / build-and-deploy (push) Waiting to run
Add `long`, a 64-bit signed integer primitive (i64), threaded through codegen: llty; int<->long coercion (coerce_code/to_long) at let/assign/ return/call-args; i64 arithmetic + comparison promotion in emit_bin; unary negate/~; print via %lld and str()/interpolation via @fn_long_str. Editor vocabulary (syntax header, TextMate grammar, formatter, LSP) synced; check-vocabulary green. Numeric literals stay i32 — build large values by widening (documented on the type page). Complete the Hash.* namespace (issue #17) with both 32- and 64-bit algorithms: Hash.of/fnv1a/crc32/mix/combine (32-bit) and Hash.of64/fnv1a_64/mix64 (64-bit, returning long). Deterministic and C-free; CRC-32 (poly 0xEDB88320) and FNV vectors verified against reference implementations. Tests: selfhost/tests/{hash,long}.ludic. Docs: docs/language/hash/*, type-long.md. Seed reseeded; C-free bootstrap fixpoint holds; 23 selfhost + 45 regression + 29 tooling checks green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
a38195128f
commit
2002e977d9
27 changed files with 14007 additions and 12105 deletions
9
docs/language/hash/_section.md
Normal file
9
docs/language/hash/_section.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
id: hash
|
||||
title: Hash
|
||||
order: 6
|
||||
---
|
||||
|
||||
Fast, non-cryptographic hashing for everyday game needs: turning string IDs into integer handles, mixing a few numbers into one deterministic seed, and checksumming data to catch corruption. Every function is plain 32-bit integer arithmetic with a defined byte order and fixed constants, so a given input hashes to exactly the same value on every platform and every run — the guarantee that makes it safe for procedural generation and lockstep networking. Results are 32-bit and print as signed integers. Arguments are positional.
|
||||
|
||||
Not for security. These hashes are fast and reversible — never use them for passwords, tokens, or tamper-proofing. For secure hashing reach for the cryptography library instead.
|
||||
30
docs/language/hash/hash-combine.md
Normal file
30
docs/language/hash/hash-combine.md
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
id: hash-combine
|
||||
name: Hash.combine
|
||||
category: hash
|
||||
kind: namespace-method
|
||||
tokens: Hash.combine
|
||||
sig: Hash.combine(...) -> int
|
||||
tip: Fold several ints into one deterministic seed.
|
||||
order: 5
|
||||
ns: Hash
|
||||
member: combine
|
||||
---
|
||||
|
||||
Mixes any number of integers into a single 32-bit value. It folds the arguments left to right with the classic hash-combine step (each value is spread with the golden-ratio constant <code>0x9e3779b9</code> and stirred into the running seed), so the result depends on every input and on their order — <code>Hash.combine(1, 2, 3)</code> and <code>Hash.combine(3, 2, 1)</code> differ. The whole point is a per-thing deterministic seed: hash a world seed with a chunk's coordinates to drive procedural generation, so the same chunk always regenerates identically. Pair it with the noise and random libraries, which take a seed.
|
||||
|
||||
Parameters:
|
||||
- `...` — the integers to combine (one or more)
|
||||
|
||||
```ludic
|
||||
program ChunkSeed {
|
||||
entry {
|
||||
let world_seed = 1337
|
||||
let cx = 4
|
||||
let cy = -2
|
||||
let seed = Hash.combine(world_seed, cx, cy) # this chunk's stable seed
|
||||
print(seed)
|
||||
print(Hash.combine(world_seed, cx, cy) == seed) # 1 — reproducible
|
||||
}
|
||||
}
|
||||
```
|
||||
28
docs/language/hash/hash-crc32.md
Normal file
28
docs/language/hash/hash-crc32.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
id: hash-crc32
|
||||
name: Hash.crc32
|
||||
category: hash
|
||||
kind: namespace-method
|
||||
tokens: Hash.crc32
|
||||
sig: Hash.crc32(s) -> int
|
||||
tip: CRC-32 checksum for corruption detection.
|
||||
order: 3
|
||||
ns: Hash
|
||||
member: crc32
|
||||
---
|
||||
|
||||
The <a href="https://en.wikipedia.org/wiki/Cyclic_redundancy_check">CRC-32</a> checksum of a string's bytes — the standard IEEE 802.3 variant (reflected polynomial <code>0xEDB88320</code>), the same one used by zip, gzip and PNG. Its well-known check value: <code>Hash.crc32("123456789")</code> is <code>0xCBF43926</code>. Use it to catch accidental corruption in a save file, downloaded asset, or config blob: store the checksum alongside the data and compare on load. It detects damage, not tampering — an attacker can trivially forge a matching CRC, so it is not a security check (see the section note).
|
||||
|
||||
Parameters:
|
||||
- `s` — the string (bytes) to checksum
|
||||
|
||||
```ludic
|
||||
program Checksum {
|
||||
entry {
|
||||
let data = "save:level=3;coins=120"
|
||||
let ck = Hash.crc32(data) # store this next to the save; recompute on load
|
||||
print(ck)
|
||||
print(Hash.crc32(data) == ck) # 1 — unchanged data checks out
|
||||
}
|
||||
}
|
||||
```
|
||||
26
docs/language/hash/hash-fnv1a.md
Normal file
26
docs/language/hash/hash-fnv1a.md
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
id: hash-fnv1a
|
||||
name: Hash.fnv1a
|
||||
category: hash
|
||||
kind: namespace-method
|
||||
tokens: Hash.fnv1a
|
||||
sig: Hash.fnv1a(s) -> int
|
||||
tip: FNV-1a 32-bit, named explicitly.
|
||||
order: 2
|
||||
ns: Hash
|
||||
member: fnv1a
|
||||
---
|
||||
|
||||
The <a href="https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function">FNV-1a</a> hash of a string's bytes, as an explicit 32-bit algorithm. It starts from the standard offset basis <code>0x811c9dc5</code> and, for each byte, XORs the byte in and multiplies by the FNV prime <code>0x01000193</code>. Small, allocation-free, and well-distributed for short strings — ideal for map keys and content ids. This is the same function <a href="hash-of"><code>Hash.of</code></a> currently delegates to; call <code>Hash.fnv1a</code> when you want to pin the algorithm by name so the value stays fixed even if the default changes.
|
||||
|
||||
Parameters:
|
||||
- `s` — the string to hash
|
||||
|
||||
```ludic
|
||||
program Fnv {
|
||||
entry {
|
||||
print(Hash.fnv1a("")) # the empty string -> the offset basis
|
||||
print(Hash.fnv1a("grass_tile"))
|
||||
}
|
||||
}
|
||||
```
|
||||
26
docs/language/hash/hash-fnv1a_64.md
Normal file
26
docs/language/hash/hash-fnv1a_64.md
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
id: hash-fnv1a_64
|
||||
name: Hash.fnv1a_64
|
||||
category: hash
|
||||
kind: namespace-method
|
||||
tokens: Hash.fnv1a_64
|
||||
sig: Hash.fnv1a_64(s) -> long
|
||||
tip: FNV-1a 64-bit, named explicitly.
|
||||
order: 7
|
||||
ns: Hash
|
||||
member: fnv1a_64
|
||||
---
|
||||
|
||||
The 64-bit <a href="https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function">FNV-1a</a> hash of a string's bytes, returned as a <code>long</code>. It uses the standard 64-bit offset basis <code>0xcbf29ce484222325</code> and prime <code>0x100000001b3</code>: for each byte, XOR it in and multiply by the prime. Its wider output makes accidental collisions vanishingly unlikely for the id and registry use cases games hit. This is the function <a href="hash-of64"><code>Hash.of64</code></a> delegates to; call <code>Hash.fnv1a_64</code> when you want the algorithm pinned by name.
|
||||
|
||||
Parameters:
|
||||
- `s` — the string to hash
|
||||
|
||||
```ludic
|
||||
program Fnv64 {
|
||||
entry {
|
||||
print(Hash.fnv1a_64("")) # the empty string -> the 64-bit offset basis
|
||||
print(Hash.fnv1a_64("grass_tile"))
|
||||
}
|
||||
}
|
||||
```
|
||||
29
docs/language/hash/hash-mix.md
Normal file
29
docs/language/hash/hash-mix.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
id: hash-mix
|
||||
name: Hash.mix
|
||||
category: hash
|
||||
kind: namespace-method
|
||||
tokens: Hash.mix
|
||||
sig: Hash.mix(x) -> int
|
||||
tip: Avalanche one integer into a well-scrambled value.
|
||||
order: 4
|
||||
ns: Hash
|
||||
member: mix
|
||||
---
|
||||
|
||||
Scrambles a single integer so its bits are thoroughly mixed — the <a href="https://en.wikipedia.org/wiki/MurmurHash">MurmurHash3</a> <code>fmix32</code> finalizer (shift/multiply/shift). Nearby inputs like <code>0</code>, <code>1</code>, <code>2</code> map to values that look unrelated, which is exactly what you want when turning a counter, entity id, or frame number into a random-looking seed. It is deterministic and self-inverse-free: the same input always gives the same output. <code>Hash.mix(0)</code> is <code>0</code>. For folding several numbers together use <a href="hash-combine"><code>Hash.combine</code></a>.
|
||||
|
||||
Parameters:
|
||||
- `x` — the integer to avalanche
|
||||
|
||||
```ludic
|
||||
program Seeds {
|
||||
entry {
|
||||
var i = 0
|
||||
while i < 4 {
|
||||
print(Hash.mix(i)) # consecutive counters -> unrelated-looking seeds
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
27
docs/language/hash/hash-mix64.md
Normal file
27
docs/language/hash/hash-mix64.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
id: hash-mix64
|
||||
name: Hash.mix64
|
||||
category: hash
|
||||
kind: namespace-method
|
||||
tokens: Hash.mix64
|
||||
sig: Hash.mix64(x) -> long
|
||||
tip: Avalanche one 64-bit integer into a well-scrambled value.
|
||||
order: 8
|
||||
ns: Hash
|
||||
member: mix64
|
||||
---
|
||||
|
||||
The 64-bit counterpart of <a href="hash-mix"><code>Hash.mix</code></a>: the <a href="https://en.wikipedia.org/wiki/MurmurHash">MurmurHash3</a> <code>fmix64</code> finalizer (shift by 33, multiply, repeat). It thoroughly scrambles a <code>long</code>, so consecutive or structured inputs map to unrelated-looking outputs — ideal for turning a large counter, packed coordinate, or 64-bit id into a random-looking seed. An <code>int</code> argument widens to 64 bits first. Deterministic; <code>Hash.mix64(0)</code> is <code>0</code>.
|
||||
|
||||
Parameters:
|
||||
- `x` — the integer to avalanche (widened to `long`)
|
||||
|
||||
```ludic
|
||||
program Seeds64 {
|
||||
entry {
|
||||
let base: long = Hash.of64("chunk")
|
||||
print(Hash.mix64(base))
|
||||
print(Hash.mix64(base + 1)) # a neighbouring input -> an unrelated seed
|
||||
}
|
||||
}
|
||||
```
|
||||
28
docs/language/hash/hash-of.md
Normal file
28
docs/language/hash/hash-of.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
id: hash-of
|
||||
name: Hash.of
|
||||
category: hash
|
||||
kind: namespace-method
|
||||
tokens: Hash.of
|
||||
sig: Hash.of(s) -> int
|
||||
tip: The fast default string hash.
|
||||
order: 1
|
||||
ns: Hash
|
||||
member: of
|
||||
---
|
||||
|
||||
The go-to hash for a string: turns text like <code>"grass_tile"</code> into a fast, stable 32-bit integer handle. <code>Hash.of</code> is the default when you don't care which algorithm is used — currently it is <a href="hash-fnv1a"><code>Hash.fnv1a</code></a>, FNV-1a over the bytes. Reach for it to key a lookup table by name, give content a compact id, or detect that a config string changed. The result is deterministic: the same string always produces the same value, on every platform and run. It is not for security — see the section note.
|
||||
|
||||
Parameters:
|
||||
- `s` — the string to hash
|
||||
|
||||
```ludic
|
||||
program TileIds {
|
||||
entry {
|
||||
let grass = Hash.of("grass_tile") # a stable integer id for this tile name
|
||||
let water = Hash.of("water_tile")
|
||||
print(grass)
|
||||
print(water)
|
||||
}
|
||||
}
|
||||
```
|
||||
28
docs/language/hash/hash-of64.md
Normal file
28
docs/language/hash/hash-of64.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
id: hash-of64
|
||||
name: Hash.of64
|
||||
category: hash
|
||||
kind: namespace-method
|
||||
tokens: Hash.of64
|
||||
sig: Hash.of64(s) -> long
|
||||
tip: The fast default 64-bit string hash.
|
||||
order: 6
|
||||
ns: Hash
|
||||
member: of64
|
||||
---
|
||||
|
||||
The 64-bit counterpart of <a href="hash-of"><code>Hash.of</code></a>: hashes a string to a <code>long</code> instead of a 32-bit <code>int</code>. Use it when you want far fewer collisions than 32 bits can give — large content-id spaces, asset registries, or network identifiers where two different strings sharing a hash would be a real risk. Currently it is <a href="hash-fnv1a_64"><code>Hash.fnv1a_64</code></a>, FNV-1a over the bytes. Deterministic like every hash here: the same string always gives the same 64-bit value.
|
||||
|
||||
Parameters:
|
||||
- `s` — the string to hash
|
||||
|
||||
```ludic
|
||||
program Ids64 {
|
||||
entry {
|
||||
let grass: long = Hash.of64("grass_tile")
|
||||
let water: long = Hash.of64("water_tile")
|
||||
print(grass)
|
||||
print(water)
|
||||
}
|
||||
}
|
||||
```
|
||||
21
docs/language/types/type-long.md
Normal file
21
docs/language/types/type-long.md
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
id: type-long
|
||||
name: long
|
||||
category: types
|
||||
kind: type
|
||||
tokens: long
|
||||
sig: long
|
||||
tip: A 64-bit signed integer for values that overflow a 32-bit int.
|
||||
order: 1
|
||||
---
|
||||
|
||||
`long` is a 64-bit signed integer — reach for it when a value would overflow the 32-bit `int`: a 64-bit hash or content id, an accumulated playtime in milliseconds, a large score or currency total, packed bit flags. Arithmetic (`+ - * / %`), comparison, and the bitwise operators all work on it, and mixing an `int` with a `long` promotes the `int` to 64 bits automatically — so `big * count` where `big` is a `long` computes in 64 bits and does not overflow. `print` and `str` render all its digits.
|
||||
|
||||
One limit to know: a numeric *literal* is still parsed as a 32-bit `int`, so build large values by widening — assign a smaller literal into a `long` and compute from there (`let m: long = 1000000` then `m * m`) rather than writing a 10-digit literal directly.
|
||||
|
||||
```ludic
|
||||
let big: long = 1000000
|
||||
var area: long = big * big # 10^12, computed in 64 bits
|
||||
var total: long = area + 1
|
||||
var doubled: long = area * 2
|
||||
```
|
||||
|
|
@ -24,6 +24,8 @@ var g_uses_strslice: bool = false # `s[a..b]` was emitted -> emit the substring
|
|||
var g_uses_mathrt: bool = false # Math.sqrt/sin/cos/tan was emitted -> emit the math runtime prelude
|
||||
var g_uses_textrt: bool = false # Text.upper/lower/trim/repeat/pad was emitted -> emit the text builders
|
||||
var g_uses_textrt2: bool = false # Text.split/join/replace was emitted -> emit the string/slice builders
|
||||
var g_uses_hashrt: bool = false # Hash.of/fnv1a/crc32 was emitted -> emit the byte-stream hashers
|
||||
var g_uses_longstr: bool = false # str(long) / interpolating a long was emitted -> emit fn_long_str
|
||||
|
||||
# loop targets for break/continue (innermost last)
|
||||
var brk_lbl: []ptr
|
||||
|
|
@ -70,6 +72,7 @@ fn lbl(pfx: ptr) -> ptr { let r = (pfx + itoa(ll_lbl)); ll_lbl = ll_lbl + 1; r
|
|||
# slice) is a pointer; void is void.
|
||||
fn llty(t: ptr) -> ptr {
|
||||
if (t == "int") or (t == "bool") or (t == "fixed") or (t == "entity") { return "i32" } # entity = an i32 handle (self())
|
||||
if (t == "long") { return "i64" } # a 64-bit signed integer
|
||||
if (t == "byte") { return "i8" } # a single byte (p[i] on a raw ptr)
|
||||
if (t == "words") or (t == "fixeds") or (t == "ptrs") { return "ptr" } # typed buffers
|
||||
if (t == "void") { return "void" }
|
||||
|
|
|
|||
|
|
@ -98,10 +98,12 @@ fn emit_program() -> void {
|
|||
if g_uses_loopback { emit_loopback() } # built-in transport, after all net_send/net_poll uses are seen
|
||||
if g_uses_str { emit_str_prelude() } # @fn_str_eq / @fn_str_concat, after all uses are seen
|
||||
if g_uses_intstr { emit_int_str() } # @fn_int_str, for str(int) in interpolation
|
||||
if g_uses_longstr { emit_long_str() } # @fn_long_str, for str(long) / long interpolation
|
||||
if g_uses_strslice { emit_str_slice() } # @fn_str_slice, for s[a..b]
|
||||
if g_uses_mathrt { emit_math_prelude() } # @fn_fx_sqrt / @fn_fx_sin + the sine table
|
||||
if g_uses_textrt { emit_text_prelude() } # @fn_str_upper/lower/trim/repeat/pad builders
|
||||
if g_uses_textrt2 { emit_text2_prelude() } # @fn_str_replace/join/split builders
|
||||
if g_uses_hashrt { emit_hash_prelude() } # @fn_hash_fnv1a / @fn_hash_crc32 byte hashers
|
||||
}
|
||||
|
||||
# Flush the emitted IR. With a null path it goes to stdout (the pipe the shell
|
||||
|
|
|
|||
|
|
@ -56,6 +56,24 @@ fn to_fixed(v: Val) -> ptr {
|
|||
return emit_bind(`shl i32 {v.code}, 16`)
|
||||
}
|
||||
|
||||
# coerce a value's code to the LLVM type of `target`, for the only cross-width
|
||||
# pair the language has: int (i32) <-> long (i64). int widens with sext, long
|
||||
# narrows with trunc; everything else (same width, or ptr) passes through.
|
||||
fn coerce_code(v: Val, target: ptr) -> ptr {
|
||||
let lt = llty(target)
|
||||
let vt = llty(v.ty)
|
||||
if (lt == vt) { return v.code }
|
||||
if (lt == "i64") and (vt == "i32") { return emit_bind(`sext i32 {v.code} to i64`) }
|
||||
if (lt == "i32") and (vt == "i64") { return emit_bind(`trunc i64 {v.code} to i32`) }
|
||||
return v.code
|
||||
}
|
||||
|
||||
# widen an int value to i64 (a long passes through) — the long analogue of to_fixed
|
||||
fn to_long(v: Val) -> ptr {
|
||||
if (llty(v.ty) == "i64") { return v.code }
|
||||
return emit_bind(`sext i32 {v.code} to i64`)
|
||||
}
|
||||
|
||||
# string operators: `a + b` concatenates, `a == b` / `a != b` compare by content.
|
||||
# Both call the @fn_str_* prelude (emitted once per program that uses them).
|
||||
fn emit_str_op(op: ptr, a: Val, b: Val) -> Val {
|
||||
|
|
@ -85,14 +103,22 @@ fn emit_bin(e: Node) -> Val {
|
|||
if ((e.s == ("==")) or (e.s == ("!="))) and not isnull { return emit_str_op(e.s, a, b) }
|
||||
}
|
||||
let fx = (a.ty == "fixed") or (b.ty == "fixed")
|
||||
# a 64-bit operand (and no fixed/ptr involved) promotes the whole expression to
|
||||
# i64: the other side widens with sext, and the result stays `long`.
|
||||
let lng = ((llty(a.ty) == "i64") or (llty(b.ty) == "i64")) and not fx and not ptrish
|
||||
if is_cmp(e.s) {
|
||||
var ac = a.code; var bc = b.code
|
||||
var ct = "i32"
|
||||
if fx { ac = to_fixed(a); bc = to_fixed(b) }
|
||||
else { if (llty(a.ty) == "ptr") or (llty(b.ty) == "ptr") { ct = "ptr" } } # `p == null`, str/record identity
|
||||
else { if lng { ct = "i64"; ac = to_long(a); bc = to_long(b) }
|
||||
else { if (llty(a.ty) == "ptr") or (llty(b.ty) == "ptr") { ct = "ptr" } } } # `p == null`, str/record identity
|
||||
let c = emit_bind(`icmp {cmp_code(e.s)} {ct} {ac}, {bc}`)
|
||||
return val(emit_bind(`zext i1 {c} to i32`), "bool")
|
||||
}
|
||||
if lng {
|
||||
let al = to_long(a); let bl = to_long(b)
|
||||
return val(emit_bind(`{arith_code(e.s)} i64 {al}, {bl}`), "long")
|
||||
}
|
||||
if fx {
|
||||
let af = to_fixed(a); let bf = to_fixed(b)
|
||||
if (e.s == ("*")) {
|
||||
|
|
@ -154,6 +180,13 @@ fn param_labels(fn: Node) -> []ptr {
|
|||
return out
|
||||
}
|
||||
|
||||
fn param_types(fn: Node) -> []ptr {
|
||||
let out = new []ptr
|
||||
var i = 0
|
||||
while i < len(fn.kids) { if fn.kids[i].kind == N_PARAM { push(out, fn.kids[i].ty) }; i = i + 1 }
|
||||
return out
|
||||
}
|
||||
|
||||
# ---- namespaced builtins: Screen.* / Random.* / Input.* --------------------
|
||||
# The game-facing API reads as `subject.action(...)`. Each method maps to a bare
|
||||
# runtime builtin plus the parameter labels callers may use as named arguments;
|
||||
|
|
@ -194,6 +227,10 @@ fn emit_ns_call(ns: ptr, meth: ptr, e: Node) -> Val {
|
|||
if is_time_ns(meth) { return emit_time_ns(meth, e) }
|
||||
perr(`unknown builtin Time.{meth}`)
|
||||
}
|
||||
if (ns == "Hash") {
|
||||
if is_hash_ns(meth) { return emit_hash_ns(meth, e) }
|
||||
perr(`unknown builtin Hash.{meth}`)
|
||||
}
|
||||
var bare: ptr = null
|
||||
let labels = new []ptr
|
||||
if (ns == "Screen") {
|
||||
|
|
@ -374,16 +411,18 @@ fn emit_call(e: Node) -> Val {
|
|||
}
|
||||
if (name == "len") { return emit_len(e) }
|
||||
if (name == "push") { return emit_push(e) }
|
||||
if (name == "str") { # str(x): int/bool/fixed -> text, a string passes through
|
||||
if (name == "str") { # str(x): int/bool/fixed/long -> text, a string passes through
|
||||
let a = emit_expr(e.kids[0])
|
||||
if (llty(a.ty) == "ptr") { return a }
|
||||
if (llty(a.ty) == "i64") { g_uses_longstr = true; return val(emit_bind(`call ptr @fn_long_str(i64 {a.code})`), "str") }
|
||||
g_uses_intstr = true
|
||||
return val(emit_bind(`call ptr @fn_int_str(i32 {a.code})`), "str")
|
||||
}
|
||||
if (name == "print") { # print(x): a value + newline (int or string)
|
||||
if (name == "print") { # print(x): a value + newline (string, long, or int)
|
||||
let a = emit_expr(e.kids[0])
|
||||
if (llty(a.ty) == "ptr") { emit(" call i32 (ptr, ...) @printf(ptr @.fmt_line, ptr " + `{a.code})\n`) }
|
||||
else { emit(" call i32 (ptr, ...) @printf(ptr @.fmt_int, i32 " + `{a.code})\n`) }
|
||||
else { if (llty(a.ty) == "i64") { emit(" call i32 (ptr, ...) @printf(ptr @.fmt_long, i64 " + `{a.code})\n`) }
|
||||
else { emit(" call i32 (ptr, ...) @printf(ptr @.fmt_int, i32 " + `{a.code})\n`) } }
|
||||
return val("0", "void")
|
||||
}
|
||||
if (name == "bytes") { # bytes(n): allocate n bytes -> a byte buffer
|
||||
|
|
@ -459,11 +498,18 @@ fn emit_call(e: Node) -> Val {
|
|||
cname = rtname
|
||||
}
|
||||
reorder_named(e, param_labels(fn2))
|
||||
# evaluate args first (their IR is emitted before the call instruction)
|
||||
# evaluate args first (their IR is emitted before the call instruction), coercing
|
||||
# each to the parameter's declared type so an int passed for a `long` widens.
|
||||
let ptys = param_types(fn2)
|
||||
let args = new []ptr
|
||||
let atys = new []ptr
|
||||
var i = 0
|
||||
while i < len(e.kids) { let v = emit_expr(e.kids[i]); push(args, v.code); push(atys, v.ty); i = i + 1 }
|
||||
while i < len(e.kids) {
|
||||
let v = emit_expr(e.kids[i])
|
||||
var pty = v.ty
|
||||
if (i < len(ptys)) { pty = ptys[i] }
|
||||
push(args, coerce_code(v, pty)); push(atys, pty); i = i + 1
|
||||
}
|
||||
let rl = llty(fn2.ty)
|
||||
emit(" ")
|
||||
var rreg = "0"
|
||||
|
|
@ -540,6 +586,10 @@ fn emit_expr(e: Node) -> Val {
|
|||
if e.kind == E_BIN { return emit_bin(e) }
|
||||
if e.kind == E_UN {
|
||||
let a = emit_expr(e.a)
|
||||
if (llty(a.ty) == "i64") { # negate / bit-flip a long, staying 64-bit
|
||||
if (e.s == ("-")) { return val(emit_bind(`sub i64 0, {a.code}`), "long") }
|
||||
if (e.s == "~") { return val(emit_bind(`xor i64 {a.code}, -1`), "long") }
|
||||
}
|
||||
if (e.s == ("-")) { return val(emit_bind(`sub i32 0, {a.code}`), "int") }
|
||||
if (e.s == "~") { return val(emit_bind(`xor i32 {a.code}, -1`), "int") }
|
||||
let c = emit_bind(`icmp eq i32 {a.code}, 0`)
|
||||
|
|
|
|||
195
selfhost/emit_hash.ludic
Normal file
195
selfhost/emit_hash.ludic
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
# emit_hash.ludic — the Hash.* namespace: fast, non-cryptographic hashing for
|
||||
# map keys, content IDs, deterministic seeds and checksums. Everything is plain
|
||||
# 32-bit integer IR with defined constants and byte order, so a given input
|
||||
# hashes to the same value on every platform and every run — which is what makes
|
||||
# it safe for procedural generation and lockstep networking. NOT for passwords
|
||||
# or tamper-proofing; point users at the Crypto library for that.
|
||||
#
|
||||
# Hash.of(s) fast default string hash (currently FNV-1a 32)
|
||||
# Hash.fnv1a(s) FNV-1a 32-bit, named explicitly
|
||||
# Hash.crc32(s) CRC-32 (IEEE 802.3) checksum, for corruption detection
|
||||
# Hash.mix(x) fmix32 avalanche of a single int (turn a counter into a seed)
|
||||
# Hash.combine(...) fold several ints into one (e.g. world_seed, cx, cy)
|
||||
|
||||
fn is_hash_ns(meth: ptr) -> bool {
|
||||
if (meth == "of") or (meth == "fnv1a") or (meth == "crc32") { return true }
|
||||
if (meth == "mix") or (meth == "combine") { return true }
|
||||
if (meth == "of64") or (meth == "fnv1a_64") or (meth == "mix64") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
# fmix32 (MurmurHash3 finalizer) of a single i32 -> code of an i32. A strong
|
||||
# avalanche: flips ~half the output bits for any one input bit. Used on its own
|
||||
# (Hash.mix) and nowhere else — combine has its own mixing step.
|
||||
fn hash_mix_code(x: ptr) -> ptr {
|
||||
let a = emit_bind(`lshr i32 {x}, 16`)
|
||||
let b = emit_bind(`xor i32 {x}, {a}`)
|
||||
let c = emit_bind(`mul i32 {b}, -2048144789`) # * 0x85ebca6b
|
||||
let d = emit_bind(`lshr i32 {c}, 13`)
|
||||
let e = emit_bind(`xor i32 {c}, {d}`)
|
||||
let f = emit_bind(`mul i32 {e}, -1028477387`) # * 0xc2b2ae35
|
||||
let g = emit_bind(`lshr i32 {f}, 16`)
|
||||
return emit_bind(`xor i32 {f}, {g}`)
|
||||
}
|
||||
|
||||
# fmix64 (MurmurHash3 64-bit finalizer) of a single i64 -> code of an i64. The
|
||||
# 64-bit twin of hash_mix_code: shift by 33 and multiply by the two 64-bit
|
||||
# constants. Backs Hash.mix64.
|
||||
fn hash_mix64_code(x: ptr) -> ptr {
|
||||
let a = emit_bind(`lshr i64 {x}, 33`)
|
||||
let b = emit_bind(`xor i64 {x}, {a}`)
|
||||
let c = emit_bind(`mul i64 {b}, -49064778989728563`) # * 0xff51afd7ed558ccd
|
||||
let d = emit_bind(`lshr i64 {c}, 33`)
|
||||
let e = emit_bind(`xor i64 {c}, {d}`)
|
||||
let f = emit_bind(`mul i64 {e}, -4265267296055464877`) # * 0xc4ceb9fe1a85ec53
|
||||
let g = emit_bind(`lshr i64 {f}, 33`)
|
||||
return emit_bind(`xor i64 {f}, {g}`)
|
||||
}
|
||||
|
||||
fn emit_hash_ns(meth: ptr, e: Node) -> Val {
|
||||
if (meth == "of") or (meth == "fnv1a") { # FNV-1a 32-bit over the bytes
|
||||
g_uses_hashrt = true
|
||||
let s = emit_expr(e.kids[0])
|
||||
return val(emit_bind(`call i32 @fn_hash_fnv1a(ptr {s.code})`), "int")
|
||||
}
|
||||
if (meth == "of64") or (meth == "fnv1a_64") { # FNV-1a 64-bit -> a `long`
|
||||
g_uses_hashrt = true
|
||||
let s = emit_expr(e.kids[0])
|
||||
return val(emit_bind(`call i64 @fn_hash_fnv1a_64(ptr {s.code})`), "long")
|
||||
}
|
||||
if (meth == "mix64") { # fmix64 avalanche of one long
|
||||
let x = emit_expr(e.kids[0])
|
||||
return val(hash_mix64_code(to_long(x)), "long")
|
||||
}
|
||||
if (meth == "crc32") { # CRC-32 (IEEE) checksum
|
||||
g_uses_hashrt = true
|
||||
let s = emit_expr(e.kids[0])
|
||||
return val(emit_bind(`call i32 @fn_hash_crc32(ptr {s.code})`), "int")
|
||||
}
|
||||
if (meth == "mix") { # fmix32 avalanche of one int
|
||||
let x = emit_expr(e.kids[0])
|
||||
return val(hash_mix_code(x.code), "int")
|
||||
}
|
||||
# combine(a, b, ...) -> fold the boost hash_combine step over every argument:
|
||||
# seed = seed ^ (v + 0x9e3779b9 + (seed << 6) + (seed >> 2))
|
||||
# order-sensitive and deterministic; seed starts at 0 so a single argument is
|
||||
# still well-mixed with the golden-ratio constant.
|
||||
var seed: ptr = "0"
|
||||
var i = 0
|
||||
while i < len(e.kids) {
|
||||
let v = emit_expr(e.kids[i])
|
||||
let g = emit_bind(`add i32 {v.code}, -1640531527`) # + 0x9e3779b9
|
||||
let sl = emit_bind(`shl i32 {seed}, 6`)
|
||||
let sr = emit_bind(`lshr i32 {seed}, 2`)
|
||||
let t1 = emit_bind(`add i32 {g}, {sl}`)
|
||||
let t2 = emit_bind(`add i32 {t1}, {sr}`)
|
||||
seed = emit_bind(`xor i32 {seed}, {t2}`)
|
||||
i = i + 1
|
||||
}
|
||||
return val(seed, "int")
|
||||
}
|
||||
|
||||
# emit_hash_prelude — the byte-stream hashers, emitted once per program that uses
|
||||
# Hash.of/fnv1a/crc32 (g_uses_hashrt). Both walk a null-terminated string a byte
|
||||
# at a time with pure integer IR: FNV-1a with the standard 32-bit offset basis /
|
||||
# prime, and a bitwise CRC-32 with the reflected poly 0xEDB88320. No libc, no
|
||||
# allocation, bit-identical on every target.
|
||||
fn emit_hash_prelude() -> void {
|
||||
emith("define i32 @fn_hash_fnv1a(ptr %s) {\n")
|
||||
emith("entry:\n")
|
||||
emith(" %hp = alloca i32\n")
|
||||
emith(" store i32 -2128831035, ptr %hp\n") # 0x811c9dc5 offset basis
|
||||
emith(" %ip = alloca i64\n")
|
||||
emith(" store i64 0, ptr %ip\n")
|
||||
emith(" br label %cond\n")
|
||||
emith("cond:\n")
|
||||
emith(" %i = load i64, ptr %ip\n")
|
||||
emith(" %p = getelementptr i8, ptr %s, i64 %i\n")
|
||||
emith(" %c = load i8, ptr %p\n")
|
||||
emith(" %z = icmp eq i8 %c, 0\n")
|
||||
emith(" br i1 %z, label %done, label %body\n")
|
||||
emith("body:\n")
|
||||
emith(" %ce = zext i8 %c to i32\n")
|
||||
emith(" %h = load i32, ptr %hp\n")
|
||||
emith(" %x = xor i32 %h, %ce\n")
|
||||
emith(" %m = mul i32 %x, 16777619\n") # * 0x01000193 prime
|
||||
emith(" store i32 %m, ptr %hp\n")
|
||||
emith(" %i1 = add i64 %i, 1\n")
|
||||
emith(" store i64 %i1, ptr %ip\n")
|
||||
emith(" br label %cond\n")
|
||||
emith("done:\n")
|
||||
emith(" %hr = load i32, ptr %hp\n")
|
||||
emith(" ret i32 %hr\n")
|
||||
emith("}\n")
|
||||
emith("define i64 @fn_hash_fnv1a_64(ptr %s) {\n") # 64-bit FNV-1a, same shape, i64
|
||||
emith("entry:\n")
|
||||
emith(" %hp = alloca i64\n")
|
||||
emith(" store i64 -3750763034362895579, ptr %hp\n") # 0xcbf29ce484222325 offset basis
|
||||
emith(" %ip = alloca i64\n")
|
||||
emith(" store i64 0, ptr %ip\n")
|
||||
emith(" br label %cond\n")
|
||||
emith("cond:\n")
|
||||
emith(" %i = load i64, ptr %ip\n")
|
||||
emith(" %p = getelementptr i8, ptr %s, i64 %i\n")
|
||||
emith(" %c = load i8, ptr %p\n")
|
||||
emith(" %z = icmp eq i8 %c, 0\n")
|
||||
emith(" br i1 %z, label %done, label %body\n")
|
||||
emith("body:\n")
|
||||
emith(" %ce = zext i8 %c to i64\n")
|
||||
emith(" %h = load i64, ptr %hp\n")
|
||||
emith(" %x = xor i64 %h, %ce\n")
|
||||
emith(" %m = mul i64 %x, 1099511628211\n") # * 0x100000001b3 prime
|
||||
emith(" store i64 %m, ptr %hp\n")
|
||||
emith(" %i1 = add i64 %i, 1\n")
|
||||
emith(" store i64 %i1, ptr %ip\n")
|
||||
emith(" br label %cond\n")
|
||||
emith("done:\n")
|
||||
emith(" %hr = load i64, ptr %hp\n")
|
||||
emith(" ret i64 %hr\n")
|
||||
emith("}\n")
|
||||
emith("define i32 @fn_hash_crc32(ptr %s) {\n")
|
||||
emith("entry:\n")
|
||||
emith(" %cp = alloca i32\n")
|
||||
emith(" store i32 -1, ptr %cp\n") # init 0xFFFFFFFF
|
||||
emith(" %ip = alloca i64\n")
|
||||
emith(" store i64 0, ptr %ip\n")
|
||||
emith(" %kp = alloca i32\n")
|
||||
emith(" br label %cond\n")
|
||||
emith("cond:\n")
|
||||
emith(" %i = load i64, ptr %ip\n")
|
||||
emith(" %p = getelementptr i8, ptr %s, i64 %i\n")
|
||||
emith(" %ch = load i8, ptr %p\n")
|
||||
emith(" %z = icmp eq i8 %ch, 0\n")
|
||||
emith(" br i1 %z, label %done, label %body\n")
|
||||
emith("body:\n")
|
||||
emith(" %ce = zext i8 %ch to i32\n")
|
||||
emith(" %c0 = load i32, ptr %cp\n")
|
||||
emith(" %cx = xor i32 %c0, %ce\n") # fold byte into low 8 bits
|
||||
emith(" store i32 %cx, ptr %cp\n")
|
||||
emith(" store i32 0, ptr %kp\n")
|
||||
emith(" br label %bit\n")
|
||||
emith("bit:\n")
|
||||
emith(" %k = load i32, ptr %kp\n")
|
||||
emith(" %kd = icmp slt i32 %k, 8\n")
|
||||
emith(" br i1 %kd, label %bitbody, label %bitdone\n")
|
||||
emith("bitbody:\n")
|
||||
emith(" %c1 = load i32, ptr %cp\n")
|
||||
emith(" %lb = and i32 %c1, 1\n")
|
||||
emith(" %ls = lshr i32 %c1, 1\n")
|
||||
emith(" %ism = icmp eq i32 %lb, 1\n")
|
||||
emith(" %px = select i1 %ism, i32 -306674912, i32 0\n") # ^ 0xEDB88320 when LSB set
|
||||
emith(" %c2 = xor i32 %ls, %px\n")
|
||||
emith(" store i32 %c2, ptr %cp\n")
|
||||
emith(" %k1 = add i32 %k, 1\n")
|
||||
emith(" store i32 %k1, ptr %kp\n")
|
||||
emith(" br label %bit\n")
|
||||
emith("bitdone:\n")
|
||||
emith(" %i1 = add i64 %i, 1\n")
|
||||
emith(" store i64 %i1, ptr %ip\n")
|
||||
emith(" br label %cond\n")
|
||||
emith("done:\n")
|
||||
emith(" %cf = load i32, ptr %cp\n")
|
||||
emith(" %r = xor i32 %cf, -1\n") # final XOR 0xFFFFFFFF
|
||||
emith(" ret i32 %r\n")
|
||||
emith("}\n")
|
||||
}
|
||||
|
|
@ -66,6 +66,7 @@ fn emit_header() -> void {
|
|||
emith("@__stderrp = external global ptr\n")
|
||||
emith("@__stdoutp = external global ptr\n")
|
||||
emith("@.fmt_int = private unnamed_addr constant [4 x i8] c\"%d\\0A\\00\"\n")
|
||||
emith("@.fmt_long = private unnamed_addr constant [6 x i8] c\"%lld\\0A\\00\"\n")
|
||||
emith("@.fmt_line = private unnamed_addr constant [4 x i8] c\"%s\\0A\\00\"\n")
|
||||
emith("@L_argc = internal global i32 0\n")
|
||||
emith("@L_argv = internal global ptr null\n")
|
||||
|
|
@ -192,6 +193,29 @@ fn emit_int_str() -> void {
|
|||
emith(" %rpos = add i32 %fpos, 1\n %res = getelementptr inbounds i8, ptr %buf, i32 %rpos\n ret ptr %res\n}\n")
|
||||
}
|
||||
|
||||
# the i64 twin of fn_int_str: a signed 64-bit integer -> decimal text. Emitted
|
||||
# once per program that stringifies a `long` (g_uses_longstr). A 64-bit value is
|
||||
# at most 20 digits plus sign and NUL, so the 24-byte scratch buffer still fits.
|
||||
fn emit_long_str() -> void {
|
||||
emith("define ptr @fn_long_str(i64 %n0) {\n")
|
||||
emith("entry:\n %buf = call ptr @malloc(i64 24)\n")
|
||||
emith(" %isneg = icmp slt i64 %n0, 0\n %neg = sub i64 0, %n0\n")
|
||||
emith(" %n = select i1 %isneg, i64 %neg, i64 %n0\n")
|
||||
emith(" %term = getelementptr inbounds i8, ptr %buf, i32 23\n store i8 0, ptr %term\n")
|
||||
emith(" %iszero = icmp eq i64 %n0, 0\n br i1 %iszero, label %zc, label %dl\n")
|
||||
emith("zc:\n %zp = getelementptr inbounds i8, ptr %buf, i32 22\n store i8 48, ptr %zp\n ret ptr %zp\n")
|
||||
emith("dl:\n br label %dloop\n")
|
||||
emith("dloop:\n %pos = phi i32 [ 22, %dl ], [ %pos2, %dbody ]\n %cur = phi i64 [ %n, %dl ], [ %cur2, %dbody ]\n")
|
||||
emith(" %done = icmp eq i64 %cur, 0\n br i1 %done, label %sign, label %dbody\n")
|
||||
emith("dbody:\n %d = urem i64 %cur, 10\n %d32 = trunc i64 %d to i32\n %ch = add i32 %d32, 48\n %ch8 = trunc i32 %ch to i8\n")
|
||||
emith(" %pp = getelementptr inbounds i8, ptr %buf, i32 %pos\n store i8 %ch8, ptr %pp\n")
|
||||
emith(" %cur2 = udiv i64 %cur, 10\n %pos2 = sub i32 %pos, 1\n br label %dloop\n")
|
||||
emith("sign:\n br i1 %isneg, label %addneg, label %fin\n")
|
||||
emith("addneg:\n %sp = getelementptr inbounds i8, ptr %buf, i32 %pos\n store i8 45, ptr %sp\n %posn = sub i32 %pos, 1\n br label %fin\n")
|
||||
emith("fin:\n %fpos = phi i32 [ %pos, %sign ], [ %posn, %addneg ]\n")
|
||||
emith(" %rpos = add i32 %fpos, 1\n %res = getelementptr inbounds i8, ptr %buf, i32 %rpos\n ret ptr %res\n}\n")
|
||||
}
|
||||
|
||||
# s[a..b] -> a fresh NUL-terminated copy of the bytes [a, b), emitted (once) into
|
||||
# any program that slices a string. Mallocs (b-a)+1, copies, terminates.
|
||||
fn emit_str_slice() -> void {
|
||||
|
|
|
|||
|
|
@ -40,16 +40,16 @@ fn emit_assign(st: Node) -> void {
|
|||
}
|
||||
let lt = llty(ty)
|
||||
let rv = emit_expr(st.b)
|
||||
var v = rv.code
|
||||
var v = coerce_code(rv, ty)
|
||||
if (lt == "i8") { v = emit_bind(`trunc i32 {v} to i8`) } # narrow to a byte for p[i] = v
|
||||
if not (st.s == "=") {
|
||||
let cur = emit_bind(`load {lt}, ptr {addr}`)
|
||||
var opc = "add"
|
||||
if (st.s == ("-=")) { opc = "sub" }
|
||||
if (st.s == ("*=")) { opc = "mul" }
|
||||
if (st.s == ("/=")) { opc = "sdiv" }
|
||||
v = emit_bind(`{opc} i32 {cur}, {v}`)
|
||||
v = emit_bind(`{opc} {lt} {cur}, {v}`)
|
||||
}
|
||||
if (lt == "i8") { v = emit_bind(`trunc i32 {v} to i8`) } # narrow to a byte for p[i] = v
|
||||
store_at(lt, v, addr)
|
||||
}
|
||||
|
||||
|
|
@ -115,7 +115,7 @@ fn emit_for(st: Node) -> void {
|
|||
fn emit_return(st: Node) -> void {
|
||||
if (st.a != null) {
|
||||
let v = emit_expr(st.a)
|
||||
store_at(llty(ret_ty), v.code, "%retval")
|
||||
store_at(llty(ret_ty), coerce_code(v, ret_ty), "%retval")
|
||||
}
|
||||
emit(" br label %ret\n")
|
||||
g_term = true
|
||||
|
|
@ -231,7 +231,7 @@ fn emit_stmt(st: Node) -> void {
|
|||
var z = "0"
|
||||
if (llty(ty) == "ptr") { z = "null" }
|
||||
store_at(llty(ty), z, slot)
|
||||
} else { let v = emit_expr(st.a); store_at(llty(ty), v.code, slot) }
|
||||
} else { let v = emit_expr(st.a); store_at(llty(ty), coerce_code(v, ty), slot) }
|
||||
loc_push(st.s, slot, ty)
|
||||
loc_set_mut(st.ival)
|
||||
return
|
||||
|
|
|
|||
25479
selfhost/ludicc.seed.ll
25479
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
35
selfhost/tests/hash.ludic
Normal file
35
selfhost/tests/hash.ludic
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
program T {
|
||||
entry {
|
||||
# ---- Hash.* — deterministic non-cryptographic hashing ----
|
||||
# FNV-1a 32-bit (printed as a signed i32).
|
||||
print(Hash.fnv1a("")) # 0x811c9dc5 -> -2128831035
|
||||
print(Hash.fnv1a("a")) # -468965076
|
||||
print(Hash.fnv1a("grass_tile")) # 114400290
|
||||
print(Hash.of("grass_tile")) # Hash.of is the FNV-1a default -> same
|
||||
|
||||
# CRC-32 (IEEE) — known-answer check vectors.
|
||||
print(Hash.crc32("")) # 0
|
||||
print(Hash.crc32("123456789")) # 0xCBF43926 -> -873187034
|
||||
print(Hash.crc32("The quick brown fox jumps over the lazy dog")) # 1095738169
|
||||
|
||||
# fmix32 avalanche of a single int.
|
||||
print(Hash.mix(0)) # 0
|
||||
print(Hash.mix(1)) # 1364076727
|
||||
print(Hash.mix(-1)) # -2114883783
|
||||
|
||||
# combine folds several ints into one seed: deterministic + order-sensitive.
|
||||
print(Hash.combine(123, 456)) # -845898438
|
||||
print(Hash.combine(123, 456)) # same input -> same hash
|
||||
print(Hash.combine(1, 2, 3)) # -78065325
|
||||
print(Hash.combine(3, 2, 1)) # -78057399 (order matters)
|
||||
|
||||
# ---- 64-bit variants (return `long`) ----
|
||||
print(Hash.fnv1a_64("")) # 0xcbf29ce484222325 -> -3750763034362895579
|
||||
print(Hash.fnv1a_64("a")) # 0xaf63dc4c8601ec8c -> -5808556873153909620
|
||||
print(Hash.fnv1a_64("grass_tile")) # -4100651535478758590
|
||||
print(Hash.of64("grass_tile")) # Hash.of64 is the 64-bit default -> same
|
||||
print(Hash.mix64(0)) # 0
|
||||
print(Hash.mix64(1)) # -5451962507482445012
|
||||
print(Hash.mix64(-1)) # 7256831767414464289
|
||||
}
|
||||
}
|
||||
23
selfhost/tests/long.ludic
Normal file
23
selfhost/tests/long.ludic
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
program T {
|
||||
fn wide(a: long, b: int) -> long { return a * b } # int arg widens to i64
|
||||
|
||||
entry {
|
||||
let big: long = 1000000 # int literal widens to a long
|
||||
let sq = big * big # i64 multiply -> 10^12, no overflow
|
||||
print(sq) # 1000000000000
|
||||
print(sq + 1) # 1000000000001
|
||||
var acc: long = 0
|
||||
acc += sq
|
||||
acc += 5 # compound assign in i64
|
||||
print(acc) # 1000000000005
|
||||
print(wide(big, 3000000)) # 3000000000000 (> i32 max)
|
||||
let x: long = big * 3
|
||||
let y = big * 2 # inferred long
|
||||
print(x > y) # 1
|
||||
print(y < x) # 1
|
||||
print(x == x) # 1
|
||||
print(0 - sq) # -1000000000000 (unary negate stays 64-bit)
|
||||
print(big) # 1000000
|
||||
print(Text.length(str(sq))) # 13 — str(long) renders all 13 digits
|
||||
}
|
||||
}
|
||||
|
|
@ -52,7 +52,7 @@ object LudicVocabulary {
|
|||
"enable", "disable", "match", "machine", "state", "become", "where",
|
||||
"and", "or", "not", "break", "continue", "new", "emit", "cancel"
|
||||
)
|
||||
val PRIMITIVES = setOf("int", "fixed", "bool", "entity", "str", "ptr", "byte", "words", "fixeds", "ptrs", "void")
|
||||
val PRIMITIVES = setOf("int", "long", "fixed", "bool", "entity", "str", "ptr", "byte", "words", "fixeds", "ptrs", "void")
|
||||
val PHASES = setOf("Start", "Input", "FixedUpdate", "Update", "LateUpdate", "Render")
|
||||
val WIDGETS = setOf("panel", "col", "row", "label", "button", "image", "spacer")
|
||||
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@
|
|||
{ "name": "keyword.other.clause.ludic", "match": "\\b(phase|query|on)\\b" },
|
||||
{ "name": "keyword.other.ludic", "match": "\\b(import|extern)\\b" },
|
||||
{ "name": "storage.type.ludic", "match": "\\b(program|property|model|enum|ui|const|var|let|fn|handler|entry|state)\\b" },
|
||||
{ "name": "support.type.primitive.ludic", "match": "\\b(int|fixed|bool|entity|str|ptr|byte|words|fixeds|ptrs|void)\\b" },
|
||||
{ "name": "support.type.primitive.ludic", "match": "\\b(int|long|fixed|bool|entity|str|ptr|byte|words|fixeds|ptrs|void)\\b" },
|
||||
{ "name": "constant.language.boolean.ludic", "match": "\\b(true|false|null)\\b" },
|
||||
{ "name": "constant.language.phase.ludic", "match": "\\b(Start|Input|FixedUpdate|Update|LateUpdate|Render)\\b" }
|
||||
]
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@
|
|||
{ "name": "keyword.other.clause.ludic", "match": "\\b(phase|query|on)\\b" },
|
||||
{ "name": "keyword.other.ludic", "match": "\\b(import|extern)\\b" },
|
||||
{ "name": "storage.type.ludic", "match": "\\b(program|property|model|enum|ui|const|var|let|fn|handler|entry|state)\\b" },
|
||||
{ "name": "support.type.primitive.ludic", "match": "\\b(int|fixed|bool|entity|str|ptr|byte|words|fixeds|ptrs|void)\\b" },
|
||||
{ "name": "support.type.primitive.ludic", "match": "\\b(int|long|fixed|bool|entity|str|ptr|byte|words|fixeds|ptrs|void)\\b" },
|
||||
{ "name": "constant.language.boolean.ludic", "match": "\\b(true|false|null)\\b" },
|
||||
{ "name": "constant.language.phase.ludic", "match": "\\b(Start|Input|FixedUpdate|Update|LateUpdate|Render)\\b" }
|
||||
]
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ program LudicFmt {
|
|||
|
||||
# ---- vocabulary classifiers ----
|
||||
fn is_type_word(w: ptr) -> bool {
|
||||
return (w == "int") or (w == "fixed") or (w == "bool") or (w == "entity") or (w == "str") or (w == "ptr") or (w == "byte") or (w == "words") or (w == "fixeds") or (w == "ptrs") or (w == "void")
|
||||
return (w == "int") or (w == "long") or (w == "fixed") or (w == "bool") or (w == "entity") or (w == "str") or (w == "ptr") or (w == "byte") or (w == "words") or (w == "fixeds") or (w == "ptrs") or (w == "void")
|
||||
}
|
||||
fn is_phase_word(w: ptr) -> bool {
|
||||
return (w == "Start") or (w == "Input") or (w == "FixedUpdate") or (w == "Update") or (w == "LateUpdate") or (w == "Render")
|
||||
|
|
|
|||
|
|
@ -204,7 +204,7 @@ program LudicLsp {
|
|||
return c == 43 or c == 45 or c == 42 or c == 47 or c == 37 or c == 60 or c == 62 or c == 61 or c == 40 or c == 41 or c == 123 or c == 125 or c == 91 or c == 93 or c == 44 or c == 58 or c == 46 or c == 33 or c == 64 or c == 59
|
||||
}
|
||||
fn is_type_word(w: ptr) -> bool {
|
||||
return (w == "int") or (w == "fixed") or (w == "bool") or (w == "entity") or (w == "str") or (w == "ptr") or (w == "byte") or (w == "words") or (w == "fixeds") or (w == "ptrs") or (w == "void")
|
||||
return (w == "int") or (w == "long") or (w == "fixed") or (w == "bool") or (w == "entity") or (w == "str") or (w == "ptr") or (w == "byte") or (w == "words") or (w == "fixeds") or (w == "ptrs") or (w == "void")
|
||||
}
|
||||
fn is_phase_word(w: ptr) -> bool {
|
||||
return (w == "Start") or (w == "Input") or (w == "FixedUpdate") or (w == "Update") or (w == "LateUpdate") or (w == "Render")
|
||||
|
|
@ -1291,6 +1291,16 @@ program LudicLsp {
|
|||
if (meth == "join") { return "Text.join(parts, sep) -> str" }
|
||||
if (meth == "replace") { return "Text.replace(s, from, to) -> str" }
|
||||
}
|
||||
if (ns == "Hash") {
|
||||
if (meth == "of") { return "Hash.of(s) -> int" }
|
||||
if (meth == "fnv1a") { return "Hash.fnv1a(s) -> int" }
|
||||
if (meth == "crc32") { return "Hash.crc32(s) -> int" }
|
||||
if (meth == "mix") { return "Hash.mix(x) -> int" }
|
||||
if (meth == "combine") { return "Hash.combine(...) -> int" }
|
||||
if (meth == "of64") { return "Hash.of64(s) -> long" }
|
||||
if (meth == "fnv1a_64") { return "Hash.fnv1a_64(s) -> long" }
|
||||
if (meth == "mix64") { return "Hash.mix64(x) -> long" }
|
||||
}
|
||||
if (ns == "List") {
|
||||
if (meth == "len") { return "List.len(s) -> int" }
|
||||
if (meth == "push") { return "List.push(s, v)" }
|
||||
|
|
@ -2276,7 +2286,7 @@ program LudicLsp {
|
|||
}
|
||||
}
|
||||
fn comp_types(o: Buf) -> void {
|
||||
comp_item(o, "int", 14, "built-in type"); comp_item(o, "fixed", 14, "built-in type"); comp_item(o, "bool", 14, "built-in type")
|
||||
comp_item(o, "int", 14, "built-in type"); comp_item(o, "long", 14, "built-in type"); comp_item(o, "fixed", 14, "built-in type"); comp_item(o, "bool", 14, "built-in type")
|
||||
comp_item(o, "entity", 14, "built-in type"); comp_item(o, "str", 14, "built-in type"); comp_item(o, "ptr", 14, "built-in type"); comp_item(o, "void", 14, "built-in type")
|
||||
}
|
||||
fn comp_builtins(o: Buf) -> void { var i = 0; while i < len(g_builtins) { comp_item(o, g_builtins[i], 3, g_bsigs[i]); i = i + 1 } }
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ static const char* LUDIC_KW_STMT[] = {
|
|||
"and","or","not","break","continue","new","emit","cancel", 0
|
||||
};
|
||||
static const char* LUDIC_TYPES[] = {
|
||||
"int","fixed","bool","entity","str","ptr","byte","words","fixeds","ptrs","void", 0
|
||||
"int","long","fixed","bool","entity","str","ptr","byte","words","fixeds","ptrs","void", 0
|
||||
};
|
||||
static const char* LUDIC_PHASES[] = {
|
||||
"Start","Input","FixedUpdate","Update","LateUpdate","Render", 0
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ fn selfhost_frags() -> []ptr {
|
|||
push(f, "selfhost/emit_intrin2.ludic")
|
||||
push(f, "selfhost/emit_math.ludic")
|
||||
push(f, "selfhost/emit_text.ludic")
|
||||
push(f, "selfhost/emit_hash.ludic")
|
||||
push(f, "selfhost/emit_list.ludic")
|
||||
push(f, "selfhost/emit_ease.ludic")
|
||||
push(f, "selfhost/emit_collide.ludic")
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ fn cmd_selfhost_test() -> int {
|
|||
sh_case("memsys", "65 66 0 99 12345 1")
|
||||
sh_case("math3", "45 90 180 -135 30 60 90")
|
||||
sh_case("textsplit", "1 1 1 3 1 1 1 1 1 1 1")
|
||||
sh_case("hash", "-2128831035 -468965076 114400290 114400290 0 -873187034 1095738169 0 1364076727 -2114883783 -845898438 -845898438 -78065325 -78057399 -3750763034362895579 -5808556873153909620 -4100651535478758590 -4100651535478758590 0 -5451962507482445012 7256831767414464289")
|
||||
sh_case("long", "1000000000000 1000000000001 1000000000005 3000000000000 1 1 1 -1000000000000 1000000 13")
|
||||
sh_case("color", "16744512 1090486336 1090486336 8355711 8355711 8355711")
|
||||
|
||||
print("== the self-host compiler compiles real games (vs golden output) ==")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue