feat(types): option (some/none) safety type (#53)
Types phase 3 — the option/result safety pair. result/ok/err/try shipped in #46; this adds its companion option: - some(v) -> option (present value; any i32-width scalar) - none() -> option (empty; no magic -1 sentinel) - is_some / is_none -> bool - unwrap_or(o, fallback) -> int A heap %Option = { i32 present, i32 value }, bare builtins guarded by find_fn (a user fn of the same name still wins), gated by g_uses_option so unused programs compile byte-identically — same idiom as result. Wired: emit_call codegen + %Option decl (emit_decl) + g_uses_option (emit_core), reseeded seed, vocabulary sync (header/JetBrains/TextMate), builtin docs + inventory, and a self-asserting example (examples/library/optionresult.ludic + feat_case). All suites green incl. golden renders byte-identical and the bootstrap fixpoint. Tagged-union enums (variant payloads + binding match + exhaustiveness) are the deep type-system feature, split out to #56. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
5dc8394f22
commit
790eda6f73
17 changed files with 5526 additions and 4905 deletions
|
|
@ -760,6 +760,55 @@ function emit_call(e: Node) -> Val {
|
|||
let c = emit_bind(`icmp eq i32 {okv}, 0`)
|
||||
return val(emit_bind(`zext i1 {c} to i32`), "bool")
|
||||
}
|
||||
# An `option` is a heap `%Option = { i32 present, i32 value }` — the "maybe a
|
||||
# value" companion to `result` (issue #53). `some(v)` wraps a present payload
|
||||
# (any i32-width scalar: int/fixed/bool/entity), `none()` is the absent case
|
||||
# (no magic -1 sentinel); `is_some`/`is_none` test presence and `unwrap_or`
|
||||
# reads the payload with a fallback. Each is guarded by a find_fn check so a
|
||||
# user function of the same name still wins.
|
||||
if (name == "some") and (find_fn("some") == null) {
|
||||
g_uses_option = true
|
||||
let v = emit_expr(e.kids[0])
|
||||
let p = emit_bind("call ptr @malloc(i64 8)")
|
||||
let pp = emit_bind(`getelementptr inbounds %Option, ptr {p}, i32 0, i32 0`)
|
||||
emit(` store i32 1, ptr {pp}\n`)
|
||||
let vp = emit_bind(`getelementptr inbounds %Option, ptr {p}, i32 0, i32 1`)
|
||||
emit(` store i32 {coerce_code(v, "int")}, ptr {vp}\n`)
|
||||
return val(p, "option")
|
||||
}
|
||||
if (name == "none") and (find_fn("none") == null) {
|
||||
g_uses_option = true
|
||||
let p = emit_bind("call ptr @malloc(i64 8)")
|
||||
let pp = emit_bind(`getelementptr inbounds %Option, ptr {p}, i32 0, i32 0`)
|
||||
emit(` store i32 0, ptr {pp}\n`)
|
||||
let vp = emit_bind(`getelementptr inbounds %Option, ptr {p}, i32 0, i32 1`)
|
||||
emit(` store i32 0, ptr {vp}\n`)
|
||||
return val(p, "option")
|
||||
}
|
||||
if (name == "is_some") and (find_fn("is_some") == null) {
|
||||
let o = emit_expr(e.kids[0])
|
||||
let pp = emit_bind(`getelementptr inbounds %Option, ptr {o.code}, i32 0, i32 0`)
|
||||
let pv = emit_bind(`load i32, ptr {pp}`)
|
||||
let c = emit_bind(`icmp ne i32 {pv}, 0`)
|
||||
return val(emit_bind(`zext i1 {c} to i32`), "bool")
|
||||
}
|
||||
if (name == "is_none") and (find_fn("is_none") == null) {
|
||||
let o = emit_expr(e.kids[0])
|
||||
let pp = emit_bind(`getelementptr inbounds %Option, ptr {o.code}, i32 0, i32 0`)
|
||||
let pv = emit_bind(`load i32, ptr {pp}`)
|
||||
let c = emit_bind(`icmp eq i32 {pv}, 0`)
|
||||
return val(emit_bind(`zext i1 {c} to i32`), "bool")
|
||||
}
|
||||
if (name == "unwrap_or") and (find_fn("unwrap_or") == null) {
|
||||
let o = emit_expr(e.kids[0])
|
||||
let fb = emit_expr(e.kids[1])
|
||||
let pp = emit_bind(`getelementptr inbounds %Option, ptr {o.code}, i32 0, i32 0`)
|
||||
let pv = emit_bind(`load i32, ptr {pp}`)
|
||||
let vp = emit_bind(`getelementptr inbounds %Option, ptr {o.code}, i32 0, i32 1`)
|
||||
let vv = emit_bind(`load i32, ptr {vp}`)
|
||||
let present = emit_bind(`icmp ne i32 {pv}, 0`)
|
||||
return val(emit_bind(`select i1 {present}, i32 {vv}, i32 {coerce_code(fb, "int")}`), "int")
|
||||
}
|
||||
# The EV2 reflection ABI (the world table), exposed to Ludic so a Ludic mod can
|
||||
# introspect the world by name — the same functions a foreign mod binds. Emitted
|
||||
# only for a modding program (ECS + events), so a plain game is unchanged.
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ var g_uses_datert: bool = false # Date.*/DateTime.* was emitted -> emit the civ
|
|||
var g_uses_expect: bool = false # expect/expect_eq/expect_near was emitted -> emit the test-assert globals
|
||||
var g_uses_panic: bool = false # panic/assert was emitted -> declare @fprintf + the panic format
|
||||
var g_uses_result: bool = false # ok()/err()/try was emitted -> define the %Result value type (issue #46)
|
||||
var g_uses_option: bool = false # some()/none() was emitted -> define the %Option value type (issue #53)
|
||||
var g_tests: []Node # test "name" { ... } blocks collected by the parser
|
||||
var g_src_name: pointer = "?" # base name of the source file, for panic/expect file:line messages
|
||||
var g_uses_longstr: bool = false # string(long) / interpolating a long was emitted -> emit fn_long_str
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@ function emit_program() -> void {
|
|||
g_uses_expect = false
|
||||
g_uses_panic = false
|
||||
g_uses_result = false
|
||||
g_uses_option = false
|
||||
g_cov_lines = new []int
|
||||
g_cov_active = true
|
||||
loc_name = new []pointer; loc_reg = new []pointer; loc_ty = new []pointer; loc_mut = new []int
|
||||
|
|
@ -205,6 +206,7 @@ function emit_program() -> void {
|
|||
emith("@.fmt_panic = private unnamed_addr constant [6 x i8] c\"%s%s\\0A\\00\"\n")
|
||||
}
|
||||
if g_uses_result { emith("%Result = type { i32, i32, ptr }\n") } # issue #46: ok/err/try value
|
||||
if g_uses_option { emith("%Option = type { i32, i32 }\n") } # issue #53: some/none value
|
||||
emit_cov_runtime() # issue #45: --coverage tables + exit dump
|
||||
}
|
||||
|
||||
|
|
|
|||
10187
selfhost/ludicc.seed.ll
10187
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue