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:
parent
ddb1af3008
commit
70ab79a4e9
34 changed files with 5064 additions and 5124 deletions
|
|
@ -49,12 +49,13 @@ const E_NEW: int = 39
|
|||
const E_FLOAT: int = 40
|
||||
const E_REC: int = 41
|
||||
const E_FINIT: int = 42
|
||||
const E_NULL: int = 43 # the `null` pointer literal
|
||||
|
||||
property Node {
|
||||
kind: int = 0
|
||||
s: ptr = ptr_null() # name / operator / string / type-of-new
|
||||
s: ptr = null # name / operator / string / type-of-new
|
||||
ival: int = 0 # int literal, bool, flags
|
||||
ty: ptr = ptr_null() # declared type (let/param/field/fn/var/const)
|
||||
ty: ptr = null # declared type (let/param/field/fn/var/const)
|
||||
a: Node # fixed children (meaning per kind)
|
||||
b: Node
|
||||
c: Node
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# module-level material (types, globals, string constants) into one buffer and
|
||||
# function bodies into another, then prints them in order.
|
||||
|
||||
property Buf { data: ptr = ptr_null(), len: int = 0, cap: int = 0 }
|
||||
property Buf { data: ptr = null, len: int = 0, cap: int = 0 }
|
||||
|
||||
fn buf_new() -> Buf {
|
||||
let b = new Buf
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ var g_addr_ty: ptr # out-param: the type at the computed address
|
|||
fn emit_member_addr(e: Node) -> ptr {
|
||||
let base = emit_expr(e.a)
|
||||
let s = layout_node(base.ty)
|
||||
if ptr_is_null(s) { perr(sconcat("member access on non-aggregate ", base.ty)) }
|
||||
if (s == null) { perr(sconcat("member access on non-aggregate ", base.ty)) }
|
||||
let fidx = field_index(s, e.s)
|
||||
if fidx < 0 { perr(sconcat("no such field ", e.s)) }
|
||||
g_addr_ty = field_type(s, e.s)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# module header. Mirrors the pieces of compiler/back/ that this subset needs.
|
||||
# structs and slices are references, so every non-scalar type lowers to `ptr`.
|
||||
|
||||
property Val { code: ptr = ptr_null(), ty: ptr = ptr_null() }
|
||||
property Val { code: ptr = null, ty: ptr = null }
|
||||
fn val(code: ptr, ty: ptr) -> Val { let v = new Val; v.code = code; v.ty = ty; return v }
|
||||
|
||||
var head: Buf # module-level: types, globals, string constants
|
||||
|
|
@ -70,12 +70,12 @@ fn slice_elem(t: ptr) -> ptr { return substr(t, 2, slen(t) - 2) }
|
|||
fn find_arch(name: ptr) -> Node {
|
||||
var i = 0
|
||||
while i < len(prog) { let d = prog[i]; if d.kind == N_ARCH and streq(d.s, name) { return d }; i = i + 1 }
|
||||
return ptr_null()
|
||||
return null
|
||||
}
|
||||
fn find_comp(name: ptr) -> Node {
|
||||
var i = 0
|
||||
while i < len(prog) { let d = prog[i]; if d.kind == N_COMP and streq(d.s, name) { return d }; i = i + 1 }
|
||||
return ptr_null()
|
||||
return null
|
||||
}
|
||||
# every record is a `property` with a %Cmp_ layout of named fields — whether it
|
||||
# is stored per-entity by the ECS or heap-allocated by `new` is a matter of use.
|
||||
|
|
@ -102,7 +102,7 @@ fn find_global(name: ptr) -> Node {
|
|||
if d.kind == N_CONST and streq(d.s, name) { return d }
|
||||
i = i + 1
|
||||
}
|
||||
return ptr_null()
|
||||
return null
|
||||
}
|
||||
|
||||
# `Enum.Variant` -> the variant's ordinal (its index), or -1 if `ename` names no
|
||||
|
|
@ -122,7 +122,7 @@ fn enum_ordinal(ename: ptr, vname: ptr) -> int {
|
|||
fn find_fn(name: ptr) -> Node {
|
||||
var i = 0
|
||||
while i < len(prog) { let d = prog[i]; if d.kind == N_FN and streq(d.s, name) { return d }; i = i + 1 }
|
||||
return ptr_null()
|
||||
return null
|
||||
}
|
||||
|
||||
# @Computed derived fields: a per-property (Prop.field -> expression) registry.
|
||||
|
|
@ -135,16 +135,16 @@ fn register_computed(prop: ptr, field: ptr, ty: ptr, e: Node) -> void {
|
|||
push(g_computed, cf)
|
||||
}
|
||||
fn computed_expr(prop: ptr, field: ptr) -> Node {
|
||||
if ptr_is_null(prop) { return ptr_null() }
|
||||
if (prop == null) { return null }
|
||||
let key = sconcat(prop, sconcat(".", field))
|
||||
var i = 0
|
||||
while i < len(g_computed) { if streq(g_computed[i].s, key) { return g_computed[i].a }; i = i + 1 }
|
||||
return ptr_null()
|
||||
return null
|
||||
}
|
||||
# best-effort static type of an expression (for computed-field lookup; emits nothing)
|
||||
fn static_type(e: Node) -> ptr {
|
||||
if e.kind == E_ID { let li = loc_find(e.s); if li >= 0 { return loc_ty[li] } }
|
||||
return ptr_null()
|
||||
return null
|
||||
}
|
||||
|
||||
# @OnSpawn(Model) hooks: a Model -> hook-body registry. Populated at parse time;
|
||||
|
|
@ -158,7 +158,7 @@ fn register_onspawn(model: ptr, body: Node) -> void {
|
|||
fn onspawn_body(model: ptr) -> Node {
|
||||
var i = 0
|
||||
while i < len(g_onspawn) { if streq(g_onspawn[i].s, model) { return g_onspawn[i].a }; i = i + 1 }
|
||||
return ptr_null()
|
||||
return null
|
||||
}
|
||||
|
||||
# @OnDespawn(Model): a Model -> hook-body registry. Despawn does not statically
|
||||
|
|
@ -173,7 +173,7 @@ fn register_ondespawn(model: ptr, body: Node) -> void {
|
|||
fn ondespawn_body(model: ptr) -> Node {
|
||||
var i = 0
|
||||
while i < len(g_ondespawn) { if streq(g_ondespawn[i].s, model) { return g_ondespawn[i].a }; i = i + 1 }
|
||||
return ptr_null()
|
||||
return null
|
||||
}
|
||||
fn register_onattach(prop: ptr, body: Node) -> void {
|
||||
let n = node(N_BLOCK); n.s = prop; n.a = body; push(g_onattach, n)
|
||||
|
|
@ -181,7 +181,7 @@ fn register_onattach(prop: ptr, body: Node) -> void {
|
|||
fn onattach_body(prop: ptr) -> Node {
|
||||
var i = 0
|
||||
while i < len(g_onattach) { if streq(g_onattach[i].s, prop) { return g_onattach[i].a }; i = i + 1 }
|
||||
return ptr_null()
|
||||
return null
|
||||
}
|
||||
|
||||
# @OnEnable(Property) / @OnDisable(Property): run when a property is toggled on an
|
||||
|
|
@ -193,12 +193,12 @@ fn register_ondisable(prop: ptr, body: Node) -> void { let n = node(N_BLOCK); n.
|
|||
fn onenable_body(prop: ptr) -> Node {
|
||||
var i = 0
|
||||
while i < len(g_onenable) { if streq(g_onenable[i].s, prop) { return g_onenable[i].a }; i = i + 1 }
|
||||
return ptr_null()
|
||||
return null
|
||||
}
|
||||
fn ondisable_body(prop: ptr) -> Node {
|
||||
var i = 0
|
||||
while i < len(g_ondisable) { if streq(g_ondisable[i].s, prop) { return g_ondisable[i].a }; i = i + 1 }
|
||||
return ptr_null()
|
||||
return null
|
||||
}
|
||||
|
||||
# is `name` a model (archetype)? — chooses model-vs-handler for a bare enable/disable
|
||||
|
|
|
|||
|
|
@ -90,13 +90,13 @@ fn emit_program() -> void {
|
|||
fn ir_flush(path: ptr) -> bool {
|
||||
let h = buf_str(head)
|
||||
let c = buf_str(code)
|
||||
if ptr_is_null(path) {
|
||||
if (path == null) {
|
||||
print_str(h)
|
||||
print_str(c)
|
||||
return true
|
||||
}
|
||||
let f = file_open(path, "wb")
|
||||
if ptr_is_null(f) { return false }
|
||||
if (f == null) { return false }
|
||||
file_write(f, h, slen(h))
|
||||
file_write(f, c, slen(c))
|
||||
file_close(f)
|
||||
|
|
|
|||
|
|
@ -63,8 +63,10 @@ fn emit_bin(e: Node) -> Val {
|
|||
let fx = streq(a.ty, "fixed") or streq(b.ty, "fixed")
|
||||
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) }
|
||||
let c = emit_bind(sconcat("icmp ", sconcat(cmp_code(e.s), sconcat(" i32 ", sconcat(ac, sconcat(", ", bc))))))
|
||||
else { if streq(llty(a.ty), "ptr") or streq(llty(b.ty), "ptr") { ct = "ptr" } } # `p == null`, str/record identity
|
||||
let c = emit_bind(sconcat("icmp ", sconcat(cmp_code(e.s), sconcat(sconcat(" ", sconcat(ct, " ")), sconcat(ac, sconcat(", ", bc))))))
|
||||
return val(emit_bind(sconcat("zext i1 ", sconcat(c, " to i32"))), "bool")
|
||||
}
|
||||
if fx {
|
||||
|
|
@ -107,11 +109,11 @@ fn emit_call(e: Node) -> Val {
|
|||
if is_math_builtin(name) { return emit_math_builtin(name, e) }
|
||||
var fn2 = find_fn(name)
|
||||
var cname = name
|
||||
if ptr_is_null(fn2) {
|
||||
if (fn2 == null) {
|
||||
# a builtin like clear()/reg() is satisfied by its rt_ function
|
||||
let rtname = sconcat("rt_", name)
|
||||
fn2 = find_fn(rtname)
|
||||
if ptr_is_null(fn2) { perr(sconcat("unknown function ", name)) }
|
||||
if (fn2 == null) { perr(sconcat("unknown function ", name)) }
|
||||
cname = rtname
|
||||
}
|
||||
# evaluate args first (their IR is emitted before the call instruction)
|
||||
|
|
@ -135,10 +137,11 @@ fn emit_call(e: Node) -> Val {
|
|||
}
|
||||
|
||||
fn emit_expr(e: Node) -> Val {
|
||||
if ptr_is_null(e) { return val("0", "int") }
|
||||
if (e == null) { return val("0", "int") }
|
||||
if e.kind == E_INT { return val(itoa(e.ival), "int") }
|
||||
if e.kind == E_FLOAT { return val(itoa(e.ival), "fixed") }
|
||||
if e.kind == E_BOOL { return val(itoa(e.ival), "bool") }
|
||||
if e.kind == E_NULL { return val("null", "ptr") }
|
||||
if e.kind == E_STR { return val(emit_str_const(e.s), "str") }
|
||||
if e.kind == E_NEW {
|
||||
if is_slice_ty(e.s) { return emit_new_slice(e.s) }
|
||||
|
|
@ -148,7 +151,7 @@ fn emit_expr(e: Node) -> Val {
|
|||
let li = loc_find(e.s)
|
||||
if li >= 0 { return emit_load_at(loc_reg[li], loc_ty[li]) }
|
||||
let g = find_global(e.s)
|
||||
if not ptr_is_null(g) {
|
||||
if (g != null) {
|
||||
if g.kind == N_CONST { return val(itoa(g.a.ival), "int") }
|
||||
let r = emit_bind(sconcat("load ", sconcat(llty(g.ty), sconcat(", ptr @g_", e.s))))
|
||||
return val(r, g.ty)
|
||||
|
|
@ -163,9 +166,9 @@ fn emit_expr(e: Node) -> Val {
|
|||
if ord >= 0 { return val(itoa(ord), "int") }
|
||||
}
|
||||
let bt = static_type(e.a) # `x.field` where field is @Computed -> inline it
|
||||
if not ptr_is_null(bt) {
|
||||
if (bt != null) {
|
||||
let cx = computed_expr(bt, e.s)
|
||||
if not ptr_is_null(cx) { return emit_expr(qualify_fields(cx, e.a)) }
|
||||
if (cx != null) { return emit_expr(qualify_fields(cx, e.a)) }
|
||||
}
|
||||
let a = emit_member_addr(e); return emit_load_at(a, g_addr_ty)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,13 +71,13 @@ fn emit_game_main() -> void {
|
|||
emit("define i32 @main(i32 %argc, ptr %argv) {\nentry:\n")
|
||||
emit(" store i32 %argc, ptr @L_argc\n")
|
||||
emit(" store ptr %argv, ptr @L_argv\n")
|
||||
if not ptr_is_null(find_fn("rt_init")) { emit(" call void @fn_rt_init()\n") }
|
||||
if (find_fn("rt_init") != null) { emit(" call void @fn_rt_init()\n") }
|
||||
emit_calls_for_phase("Start")
|
||||
emit(" br label %loop\n")
|
||||
emit("loop:\n")
|
||||
let r = emit_bind("load i32, ptr @L_running")
|
||||
let rc = emit_bind(sconcat("icmp ne i32 ", sconcat(r, ", 0")))
|
||||
if not ptr_is_null(find_fn("rt_running")) {
|
||||
if (find_fn("rt_running") != null) {
|
||||
let pr = emit_bind("call i32 @fn_rt_running()")
|
||||
let pc = emit_bind(sconcat("icmp ne i32 ", sconcat(pr, ", 0")))
|
||||
let go = emit_bind(sconcat("and i1 ", sconcat(rc, sconcat(", ", pc))))
|
||||
|
|
@ -86,7 +86,7 @@ fn emit_game_main() -> void {
|
|||
emit(" br i1 "); emit(rc); emit(", label %body, label %done\n")
|
||||
}
|
||||
emit("body:\n")
|
||||
if not ptr_is_null(find_fn("rt_poll")) {
|
||||
if (find_fn("rt_poll") != null) {
|
||||
let k = emit_bind("call i32 @fn_rt_poll()")
|
||||
emit(" store i32 "); emit(k); emit(", ptr @L_key\n")
|
||||
}
|
||||
|
|
@ -98,6 +98,6 @@ fn emit_game_main() -> void {
|
|||
emit(" br label %loop\n")
|
||||
emit("done:\n")
|
||||
emit_calls_for_phase("OnQuit") # @OnQuit shutdown hooks run once, before teardown
|
||||
if not ptr_is_null(find_fn("rt_shutdown")) { emit(" call void @fn_rt_shutdown()\n") }
|
||||
if (find_fn("rt_shutdown") != null) { emit(" call void @fn_rt_shutdown()\n") }
|
||||
emit(" ret i32 0\n}\n")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ fn emit_gep_i8(base: ptr, idx: ptr) -> ptr {
|
|||
|
||||
# the constant initializer for a global var: a literal, or 0/null
|
||||
fn global_init(d: Node) -> ptr {
|
||||
if ptr_is_null(d.a) { if streq(llty(d.ty), "ptr") { return "null" }; return "0" }
|
||||
if (d.a == null) { if streq(llty(d.ty), "ptr") { return "null" }; return "0" }
|
||||
let e = d.a
|
||||
if e.kind == E_INT or e.kind == E_FLOAT or e.kind == E_BOOL { return itoa(e.ival) }
|
||||
if e.kind == E_UN and streq(e.s, "-") and e.a.kind == E_INT { return sconcat("-", itoa(e.a.ival)) }
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ var g_intrin_ok: bool = false
|
|||
# is `name` a low-level intrinsic? A pure name check, so it can gate dispatch
|
||||
# without evaluating arguments (which could clobber shared state).
|
||||
fn is_intrinsic(name: ptr) -> bool {
|
||||
if streq(name,"ptr_null") or streq(name,"mem_alloc") or streq(name,"mem_realloc") { return true }
|
||||
if streq(name,"peek8") or streq(name,"poke8") or streq(name,"ptr_is_null") { return true }
|
||||
if streq(name,"mem_alloc") or streq(name,"mem_realloc") { return true }
|
||||
if streq(name,"peek8") or streq(name,"poke8") { return true }
|
||||
if streq(name,"file_open") or streq(name,"file_read") or streq(name,"file_write") { return true }
|
||||
if streq(name,"file_seek") or streq(name,"file_tell") or streq(name,"file_close") { return true }
|
||||
if streq(name,"print_str") or streq(name,"print_int") { return true }
|
||||
|
|
@ -24,7 +24,7 @@ fn arg_code(e: Node, i: int) -> ptr { let v = emit_expr(e.kids[i]); return v.co
|
|||
|
||||
fn emit_intrinsic(name: ptr, e: Node) -> Val {
|
||||
g_intrin_ok = true
|
||||
if streq(name, "ptr_null") { return val("null", "ptr") }
|
||||
# ptr_null / ptr_is_null are the `null` literal and `x == null` now.
|
||||
if streq(name, "mem_alloc") {
|
||||
let n = arg_code(e, 0)
|
||||
let w = emit_bind(sconcat("zext i32 ", sconcat(n, " to i64")))
|
||||
|
|
@ -48,11 +48,6 @@ fn emit_intrinsic(name: ptr, e: Node) -> Val {
|
|||
emit(" store i8 "); emit(t); emit(", ptr "); emit(a); emit("\n")
|
||||
return val("0", "void")
|
||||
}
|
||||
if streq(name, "ptr_is_null") {
|
||||
let p = arg_code(e, 0)
|
||||
let c = emit_bind(sconcat("icmp eq ptr ", sconcat(p, ", null")))
|
||||
return val(emit_bind(sconcat("zext i1 ", sconcat(c, " to i32"))), "bool")
|
||||
}
|
||||
if streq(name, "file_open") {
|
||||
let p = arg_code(e, 0); let m = arg_code(e, 1)
|
||||
return val(emit_bind(sconcat("call ptr @fopen(ptr ", sconcat(p, sconcat(", ptr ", sconcat(m, ")"))))), "ptr")
|
||||
|
|
|
|||
|
|
@ -29,10 +29,10 @@ fn emit_machine(st: Node) -> void {
|
|||
fn emit_become(st: Node) -> void {
|
||||
if nmach == 0 { perr("'become' outside a machine") }
|
||||
let m = mach_stk[nmach - 1]
|
||||
var target: Node = ptr_null()
|
||||
var target: Node = null
|
||||
var i = 0
|
||||
while i < len(m.kids) { if streq(m.kids[i].s, st.s) { target = m.kids[i] }; i = i + 1 }
|
||||
if ptr_is_null(target) { perr(sconcat("become: no state ", st.s)) }
|
||||
if (target == null) { perr(sconcat("become: no state ", st.s)) }
|
||||
let regv = emit_expr(m.a)
|
||||
let sv = emit_expr(target.b)
|
||||
emit(" call void @fn_rt_set_reg(i32 "); emit(regv.code); emit(", i32 "); emit(sv.code); emit(")\n")
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ fn emit_sizeof(llt: ptr) -> ptr {
|
|||
|
||||
fn emit_new_struct(name: ptr) -> Val {
|
||||
let s = layout_node(name) # a struct or a property — same shape
|
||||
if ptr_is_null(s) { perr("unknown record type in new") }
|
||||
if (s == null) { perr("unknown record type in new") }
|
||||
let lty = layout_ty(name)
|
||||
let sz = emit_sizeof(lty)
|
||||
let obj = emit_bind(sconcat("call ptr @malloc(i64 ", sconcat(sz, ")")))
|
||||
|
|
@ -22,7 +22,7 @@ fn emit_new_struct(name: ptr) -> Val {
|
|||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ fn emit_query(st: Node) -> void {
|
|||
}
|
||||
|
||||
# optional where-clause
|
||||
if not ptr_is_null(st.b) {
|
||||
if (st.b != null) {
|
||||
let w = emit_expr(st.b)
|
||||
let wc = emit_bind(sconcat("icmp ne i32 ", sconcat(w.code, ", 0")))
|
||||
let kw = lbl("qw")
|
||||
|
|
|
|||
|
|
@ -40,8 +40,8 @@ fn emit_snapshot() -> void {
|
|||
emith("@.sav_path = private unnamed_addr constant [10 x i8] c\"ludic.sav\\00\"\n")
|
||||
emith("@.sav_wb = private unnamed_addr constant [3 x i8] c\"wb\\00\"\n")
|
||||
emith("@.sav_rb = private unnamed_addr constant [3 x i8] c\"rb\\00\"\n")
|
||||
let has_save = not ptr_is_null(find_fn("rt_save_state"))
|
||||
let has_load = not ptr_is_null(find_fn("rt_load_state"))
|
||||
let has_save = (find_fn("rt_save_state") != null)
|
||||
let has_load = (find_fn("rt_load_state") != null)
|
||||
|
||||
emit("define void @L_save() {\nentry:\n")
|
||||
emit(" %f = call ptr @fopen(ptr @.sav_path, ptr @.sav_wb)\n")
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ fn emit_assign(st: Node) -> void {
|
|||
}
|
||||
else {
|
||||
let g = find_global(t.s)
|
||||
if ptr_is_null(g) { perr(sconcat("assign to unknown ", t.s)) }
|
||||
if (g == null) { perr(sconcat("assign to unknown ", t.s)) }
|
||||
addr = sconcat("@g_", t.s); ty = g.ty
|
||||
}
|
||||
} else {
|
||||
|
|
@ -55,7 +55,7 @@ fn emit_assign(st: Node) -> void {
|
|||
fn emit_if(st: Node) -> void {
|
||||
let c = emit_expr(st.a)
|
||||
let cc = emit_bind(sconcat("icmp ne i32 ", sconcat(c.code, ", 0")))
|
||||
let has_else = not ptr_is_null(st.c)
|
||||
let has_else = (st.c != null)
|
||||
let tl = lbl("then"); let el = lbl("else"); let en = lbl("ifend")
|
||||
if has_else { emit(" br i1 "); emit(cc); emit(", label %"); emit(tl); emit(", label %"); emit(el); emit("\n") }
|
||||
else { emit(" br i1 "); emit(cc); emit(", label %"); emit(tl); emit(", label %"); emit(en); emit("\n") }
|
||||
|
|
@ -112,7 +112,7 @@ fn emit_for(st: Node) -> void {
|
|||
}
|
||||
|
||||
fn emit_return(st: Node) -> void {
|
||||
if not ptr_is_null(st.a) {
|
||||
if (st.a != null) {
|
||||
let v = emit_expr(st.a)
|
||||
store_at(llty(ret_ty), v.code, "%retval")
|
||||
}
|
||||
|
|
@ -129,7 +129,7 @@ fn arm_is_default(arm: Node) -> bool {
|
|||
fn emit_match(st: Node) -> void {
|
||||
let sv = emit_expr(st.a)
|
||||
let endl = lbl("mend")
|
||||
var deflt: Node = ptr_null()
|
||||
var deflt: Node = null
|
||||
var i = 0
|
||||
while i < len(st.kids) {
|
||||
let arm = st.kids[i]
|
||||
|
|
@ -154,7 +154,7 @@ fn emit_match(st: Node) -> void {
|
|||
}
|
||||
i = i + 1
|
||||
}
|
||||
if not ptr_is_null(deflt) { emit_block(deflt.a) }
|
||||
if (deflt != null) { emit_block(deflt.a) }
|
||||
if not g_term { emit(" br label %"); emit(endl); emit("\n") }
|
||||
emit(endl); emit(":\n"); g_term = false
|
||||
}
|
||||
|
|
@ -162,10 +162,10 @@ fn emit_match(st: Node) -> void {
|
|||
fn emit_stmt(st: Node) -> void {
|
||||
if st.kind == S_LET {
|
||||
var ty = st.ty
|
||||
if ptr_is_null(ty) { let v0 = emit_expr(st.a); ty = v0.ty
|
||||
if (ty == null) { let v0 = emit_expr(st.a); ty = v0.ty
|
||||
let slot = emit_alloca(llty(ty)); store_at(llty(ty), v0.code, slot); loc_push(st.s, slot, ty); loc_set_mut(st.ival); return }
|
||||
let slot = emit_alloca(llty(ty))
|
||||
if ptr_is_null(st.a) {
|
||||
if (st.a == null) {
|
||||
var z = "0"
|
||||
if streq(llty(ty), "ptr") { z = "null" }
|
||||
store_at(llty(ty), z, slot)
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ fn ui_wtype(w: Node) -> int {
|
|||
fn ui_prop(w: Node, key: ptr) -> Node {
|
||||
var i = 0
|
||||
while i < len(w.b.kids) { if streq(w.b.kids[i].s, key) { return w.b.kids[i].a }; i = i + 1 }
|
||||
return ptr_null()
|
||||
return null
|
||||
}
|
||||
fn ui_flatten(w: Node, parent: int) -> void {
|
||||
let idx = len(uiw)
|
||||
|
|
@ -51,7 +51,7 @@ fn ui_index_of(nm: ptr) -> int {
|
|||
i = 0
|
||||
while i < len(uiw) {
|
||||
let idp = ui_prop(uiw[i], "id")
|
||||
if not ptr_is_null(idp) { if idp.kind == E_ID and streq(idp.s, s) { return i } }
|
||||
if (idp != null) { if idp.kind == E_ID and streq(idp.s, s) { return i } }
|
||||
i = i + 1
|
||||
}
|
||||
return 0
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
fn read_file(path: str) -> ptr {
|
||||
let f = file_open(path, "rb")
|
||||
if ptr_is_null(f) { return ptr_null() }
|
||||
if (f == null) { return null }
|
||||
file_seek(f, 0, 2)
|
||||
let n = file_tell(f)
|
||||
file_seek(f, 0, 0)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ const TK_NL: int = 4
|
|||
const TK_EOF: int = 5
|
||||
const TK_FLOAT: int = 6
|
||||
|
||||
property Tok { kind: int = 0, text: ptr = ptr_null(), ival: int = 0, line: int = 0 }
|
||||
property Tok { kind: int = 0, text: ptr = null, ival: int = 0, line: int = 0 }
|
||||
|
||||
var toks: []Tok
|
||||
|
||||
|
|
@ -43,7 +43,7 @@ fn lex(src: ptr) -> void {
|
|||
let n = slen(src)
|
||||
while i < n {
|
||||
let c = peek8(src, i)
|
||||
if c == 10 { tok_push(TK_NL, ptr_null(), 0, line); line = line + 1; i = i + 1; continue }
|
||||
if c == 10 { tok_push(TK_NL, null, 0, line); line = line + 1; i = i + 1; continue }
|
||||
if c == 32 or c == 9 or c == 13 { i = i + 1; continue }
|
||||
if c == 35 { # '#' comment to end of line
|
||||
while i < n and peek8(src, i) != 10 { i = i + 1 }
|
||||
|
|
@ -80,7 +80,7 @@ fn lex(src: ptr) -> void {
|
|||
i = i + 2
|
||||
} else { v = peek8(src, i); i = i + 1 }
|
||||
if peek8(src, i) == 39 { i = i + 1 }
|
||||
tok_push(TK_INT, ptr_null(), v, line)
|
||||
tok_push(TK_INT, null, v, line)
|
||||
continue
|
||||
}
|
||||
if char_is_digit(c) {
|
||||
|
|
@ -96,7 +96,7 @@ fn lex(src: ptr) -> void {
|
|||
v = v * 16 + d
|
||||
i = i + 1
|
||||
}
|
||||
tok_push(TK_INT, ptr_null(), v, line)
|
||||
tok_push(TK_INT, null, v, line)
|
||||
continue
|
||||
}
|
||||
var v = 0
|
||||
|
|
@ -107,10 +107,10 @@ fn lex(src: ptr) -> void {
|
|||
var fnum = 0; var fden = 1
|
||||
while i < n and char_is_digit(peek8(src, i)) { fnum = fnum * 10 + (peek8(src, i) - 48); fden = fden * 10; i = i + 1 }
|
||||
let bits = (v << 16) + (fnum << 16) / fden
|
||||
tok_push(TK_FLOAT, ptr_null(), bits, line)
|
||||
tok_push(TK_FLOAT, null, bits, line)
|
||||
continue
|
||||
}
|
||||
tok_push(TK_INT, ptr_null(), v, line)
|
||||
tok_push(TK_INT, null, v, line)
|
||||
continue
|
||||
}
|
||||
if char_is_alpha(c) {
|
||||
|
|
@ -119,7 +119,7 @@ fn lex(src: ptr) -> void {
|
|||
tok_push(TK_ID, substr(src, start, i - start), 0, line)
|
||||
continue
|
||||
}
|
||||
if c == 59 { tok_push(TK_NL, ptr_null(), 0, line); i = i + 1; continue } # ';'
|
||||
if c == 59 { tok_push(TK_NL, null, 0, line); i = i + 1; continue } # ';'
|
||||
# two-character operators
|
||||
if two_at(src, i, 45, 62) { tok_push(TK_OP, substr(src, i, 2), 0, line); i = i + 2; continue } # ->
|
||||
if two_at(src, i, 61, 62) { tok_push(TK_OP, substr(src, i, 2), 0, line); i = i + 2; continue } # =>
|
||||
|
|
@ -137,5 +137,5 @@ fn lex(src: ptr) -> void {
|
|||
if is_op1(c) { tok_push(TK_OP, substr(src, i, 1), 0, line); i = i + 1; continue }
|
||||
i = i + 1 # skip anything unrecognised
|
||||
}
|
||||
tok_push(TK_EOF, ptr_null(), 0, line)
|
||||
tok_push(TK_EOF, null, 0, line)
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -34,7 +34,7 @@ fn strip_ludic(name: ptr) -> ptr {
|
|||
# env var with a fallback when unset
|
||||
fn getenv_or(name: ptr, dflt: ptr) -> ptr {
|
||||
let v = os_getenv(name)
|
||||
if ptr_is_null(v) { return dflt }
|
||||
if (v == null) { return dflt }
|
||||
return v
|
||||
}
|
||||
|
||||
|
|
@ -52,8 +52,8 @@ fn die(msg: ptr) -> void {
|
|||
}
|
||||
|
||||
entry {
|
||||
var path = ptr_null()
|
||||
var out = ptr_null()
|
||||
var path = null
|
||||
var out = null
|
||||
var want = 0 # 0 = auto, 1 = windowed, 2 = headless
|
||||
var emit_ir = false # --emit-llvm: stop after writing IR
|
||||
var fmt = false # --fmt: lex + parse only, then exit (the doc-check gate)
|
||||
|
|
@ -84,11 +84,11 @@ entry {
|
|||
ai = ai + 1
|
||||
}
|
||||
|
||||
if ptr_is_null(path) {
|
||||
if (path == null) {
|
||||
die("usage: ludicc <file.ludic> [-o out] [--windowed|--headless] [--emit-llvm] [--save-temps]\n")
|
||||
}
|
||||
let src = read_file(path)
|
||||
if ptr_is_null(src) { die("ludicc: cannot open input\n") }
|
||||
if (src == null) { die("ludicc: cannot open input\n") }
|
||||
|
||||
cur_dir = dir_of(path)
|
||||
lex(src)
|
||||
|
|
@ -104,7 +104,7 @@ entry {
|
|||
# a game gets a window by default; a plain program stays headless. An explicit
|
||||
# flag always wins. The stdout-IR path (no target) also stays headless, which
|
||||
# is what reseed.sh compiles the compiler itself with.
|
||||
let has_target = run or (not ptr_is_null(out))
|
||||
let has_target = run or ((out != null))
|
||||
if want == 1 { g_windowed = true }
|
||||
else { if want == 2 { g_windowed = false }
|
||||
else { g_windowed = has_target and has_systems() } }
|
||||
|
|
@ -113,10 +113,10 @@ entry {
|
|||
|
||||
# --emit-llvm, or no binary target: emit IR and stop (stdout when out is null).
|
||||
if emit_ir { ir_flush(out); return }
|
||||
if not has_target { ir_flush(ptr_null()); return }
|
||||
if not has_target { ir_flush(null); return }
|
||||
|
||||
# a run with no explicit -o lands in a temp file
|
||||
if run and ptr_is_null(out) {
|
||||
if run and (out == null) {
|
||||
out = sconcat("/tmp/ludic-run-", strip_ludic(base_name(path)))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ fn p_primary() -> Node {
|
|||
if t.kind == TK_ID {
|
||||
if streq(t.text, "true") { let n = node(E_BOOL); n.ival = 1; pi = pi + 1; return n }
|
||||
if streq(t.text, "false") { let n = node(E_BOOL); n.ival = 0; pi = pi + 1; return n }
|
||||
if streq(t.text, "null") { pi = pi + 1; return node(E_NULL) }
|
||||
if streq(t.text, "new") { pi = pi + 1; let n = node(E_NEW); n.s = ptype(); return n }
|
||||
let n = node(E_ID); n.s = t.text; pi = pi + 1; return n
|
||||
}
|
||||
|
|
@ -275,13 +276,13 @@ fn already_loaded(full: ptr) -> bool {
|
|||
# `@pure`, `@deterministic`, … — one channel, not a zoo of prefix keywords.
|
||||
fn parse_one_decl() -> void {
|
||||
var is_export = false
|
||||
var qspec: Node = ptr_null()
|
||||
var onspawn_model: ptr = ptr_null()
|
||||
var ondespawn_model: ptr = ptr_null()
|
||||
var onattach_prop: ptr = ptr_null()
|
||||
var onenable_prop: ptr = ptr_null()
|
||||
var ondisable_prop: ptr = ptr_null()
|
||||
var hook_phase: ptr = ptr_null() # @OnStart / @OnQuit override the phase
|
||||
var qspec: Node = null
|
||||
var onspawn_model: ptr = null
|
||||
var ondespawn_model: ptr = null
|
||||
var onattach_prop: ptr = null
|
||||
var onenable_prop: ptr = null
|
||||
var ondisable_prop: ptr = null
|
||||
var hook_phase: ptr = null # @OnStart / @OnQuit override the phase
|
||||
while is_op("@") {
|
||||
pi = pi + 1; let a = eat_id() # collect a leading @annotation
|
||||
if streq(a, "export") { is_export = true }
|
||||
|
|
@ -309,13 +310,13 @@ fn parse_one_decl() -> void {
|
|||
if is_id("model") { push(prog, parse_archetype()); return }
|
||||
if is_id("handler") {
|
||||
let h = parse_system()
|
||||
if not ptr_is_null(onspawn_model) { register_onspawn(onspawn_model, h.a); return } # spawn hook
|
||||
if not ptr_is_null(ondespawn_model) { register_ondespawn(ondespawn_model, h.a); return } # despawn hook
|
||||
if not ptr_is_null(onattach_prop) { register_onattach(onattach_prop, h.a); return } # attach hook
|
||||
if not ptr_is_null(onenable_prop) { register_onenable(onenable_prop, h.a); return } # enable hook
|
||||
if not ptr_is_null(ondisable_prop) { register_ondisable(ondisable_prop, h.a); return } # disable hook
|
||||
if not ptr_is_null(hook_phase) { h.ty = hook_phase } # @OnStart/@OnQuit
|
||||
if not ptr_is_null(qspec) { # @Queries wraps the body in its S_QUERY
|
||||
if (onspawn_model != null) { register_onspawn(onspawn_model, h.a); return } # spawn hook
|
||||
if (ondespawn_model != null) { register_ondespawn(ondespawn_model, h.a); return } # despawn hook
|
||||
if (onattach_prop != null) { register_onattach(onattach_prop, h.a); return } # attach hook
|
||||
if (onenable_prop != null) { register_onenable(onenable_prop, h.a); return } # enable hook
|
||||
if (ondisable_prop != null) { register_ondisable(ondisable_prop, h.a); return } # disable hook
|
||||
if (hook_phase != null) { h.ty = hook_phase } # @OnStart/@OnQuit
|
||||
if (qspec != null) { # @Queries wraps the body in its S_QUERY
|
||||
qspec.a = h.a
|
||||
let wrap = node(N_BLOCK); push(wrap.kids, qspec); h.a = wrap
|
||||
}
|
||||
|
|
@ -336,7 +337,7 @@ fn do_import(rel: ptr) -> void {
|
|||
if already_loaded(full) { return }
|
||||
push(loaded_paths, full)
|
||||
let src = read_file(full)
|
||||
if ptr_is_null(src) { perr(sconcat("cannot open import ", full)) }
|
||||
if (src == null) { perr(sconcat("cannot open import ", full)) }
|
||||
let saved_toks = toks; let saved_pi = pi; let saved_dir = cur_dir
|
||||
cur_dir = dir_of(full)
|
||||
lex(src) # resets the global token stream
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ fn parse_query_for() -> Node {
|
|||
# the whole query backend (iteration, filters, binding, break/continue) is reused.
|
||||
|
||||
fn mk_and(a: Node, b: Node) -> Node {
|
||||
if ptr_is_null(a) { return b }
|
||||
if (a == null) { return b }
|
||||
let n = node(E_BIN); n.s = "and"; n.a = a; n.b = b; return n
|
||||
}
|
||||
|
||||
|
|
@ -80,7 +80,7 @@ fn mk_and(a: Node, b: Node) -> Node {
|
|||
# expansion (base is the accessed value). Non-destructive: builds a fresh tree,
|
||||
# so a stored computed expression can be expanded at many access sites.
|
||||
fn qualify_fields(e: Node, base: Node) -> Node {
|
||||
if ptr_is_null(e) { return e }
|
||||
if (e == null) { return e }
|
||||
if e.kind == E_ID {
|
||||
let m = node(E_MEMBER); m.a = base; m.s = e.s; return m
|
||||
}
|
||||
|
|
@ -99,7 +99,7 @@ fn parse_queries_anno() -> Node {
|
|||
eat_op("(")
|
||||
let qn = node(S_QUERY)
|
||||
let terms = node(N_BLOCK)
|
||||
var wh: Node = ptr_null()
|
||||
var wh: Node = null
|
||||
while not is_op(")") {
|
||||
skipnl()
|
||||
if is_op(")") { break }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue