Phase 7b: null literal + x == null (retire ptr_null/ptr_is_null)

`null` is now a real pointer literal and null-tests are comparisons, instead of
`ptr_null()` and `ptr_is_null(x)`:

  ptr_null()          -> null
  ptr_is_null(x)      -> (x == null)
  not ptr_is_null(x)  -> (x != null)

Mechanics: a new E_NULL primary (`null`, like true/false) lowers to the `null`
pointer; emit_bin's comparison path now picks `ptr` vs `i32` from operand type
(via llty), so `==`/`!=` work on any pointer/record/slice. The two intrinsics are
deleted.

Two reseeds: (A) add the literal + ptr comparison keeping the intrinsics; (B)
migrate all 182 call sites (compiler + runtime, via a balanced-paren script that
skips string-literal args and rewrites `not ptr_is_null` to `!= null`) and delete
the intrinsics. Node/Val/Buf/Tok field defaults now read `ptr = null`.

Vocabulary drops the two from LUDIC_INTRINSICS; `null` joins true/false as a
language constant (grammar + ludic_syntax.h). LANGUAGE.md notes the literal.
Reseeded (21664 lines); C-free fixpoint holds; goldens identical; 17/17; vocab +
doc-fences clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-08-28 00:50:21 +03:00
parent ddb1af3008
commit 70ab79a4e9
34 changed files with 5064 additions and 5124 deletions

View file

@ -4,7 +4,7 @@
fn emit_init_component(e: ptr, comp: ptr, rec: Node) -> void {
let me = itoa(MAX_ENT)
let c = find_comp(comp)
if ptr_is_null(c) { perr(sconcat("spawn: unknown property ", comp)) }
if (c == null) { perr(sconcat("spawn: unknown property ", comp)) }
let hp = nreg(); emit(" "); emit(hp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(comp); emit(", i32 0, i32 "); emit(e); emit("\n")
emit(" store i8 1, ptr "); emit(hp); emit("\n")
let slot = nreg()
@ -18,12 +18,12 @@ fn emit_init_component(e: ptr, comp: ptr, rec: Node) -> void {
let lt = llty(fd.ty)
var v = "0"
if streq(lt, "ptr") { v = "null" }
if not ptr_is_null(fd.a) { let dv = emit_expr(fd.a); v = dv.code }
if (fd.a != null) { let dv = emit_expr(fd.a); v = dv.code }
emit(" store "); emit(lt); emit(" "); emit(v); emit(", ptr "); emit(addr); emit("\n")
f = f + 1
}
# per-spawn overrides
if not ptr_is_null(rec) {
if (rec != null) {
var j = 0
while j < len(rec.kids) {
let fi = rec.kids[j]
@ -39,7 +39,7 @@ fn emit_init_component(e: ptr, comp: ptr, rec: Node) -> void {
}
# @OnAttach(Property) runs once the property is attached and seeded
let ab = onattach_body(comp)
if not ptr_is_null(ab) {
if (ab != null) {
let save = nloc
let vslot = emit_alloca("ptr")
emit(" store ptr "); emit(slot); emit(", ptr "); emit(vslot); emit("\n")
@ -76,14 +76,14 @@ fn emit_spawn(st: Node) -> void {
var c = 0
while c < len(arch.kids) {
let cn = arch.kids[c].s
var rec = ptr_null()
var rec = null
var i = 0
while i < len(st.kids) { if streq(st.kids[i].s, cn) { rec = st.kids[i].a }; i = i + 1 }
emit_init_component(e, cn, rec)
c = c + 1
}
let ob = onspawn_body(st.s) # @OnSpawn(Model) hook runs after init
if not ptr_is_null(ob) {
if (ob != null) {
let save = nloc
emit_bind_props(arch, e)
emit_block(ob)
@ -121,14 +121,14 @@ fn emit_despawn(st: Node) -> void {
# flag). A bare `<Model>` / `<Handler>` flips a global enabled flag.
fn emit_toggle(st: Node) -> void {
var val = "0"; if st.ival == 1 { val = "1" }
if not ptr_is_null(st.a) {
if (st.a != null) {
let ev = emit_expr(st.a)
let me = itoa(MAX_ENT)
let hp = nreg(); emit(" "); emit(hp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(st.s); emit(", i32 0, i32 "); emit(ev.code); emit("\n")
emit(" store i8 "); emit(val); emit(", ptr "); emit(hp); emit("\n")
var hb: Node = ptr_null()
var hb: Node = null
if st.ival == 1 { hb = onenable_body(st.s) } else { hb = ondisable_body(st.s) }
if not ptr_is_null(hb) {
if (hb != null) {
let save = nloc
let slot = nreg(); emit(" "); emit(slot); emit(" = getelementptr inbounds ["); emit(me); emit(" x %Cmp_"); emit(st.s); emit("], ptr @S_"); emit(st.s); emit(", i32 0, i32 "); emit(ev.code); emit("\n")
let vslot = emit_alloca("ptr")