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

@ -20,10 +20,10 @@
# ============================================================================
# ---- state ----------------------------------------------------------------
var rt_fb: ptr = ptr_null() # framebuffer, one i32 (0x00RRGGBB) per pixel
var rt_fb: ptr = null # framebuffer, one i32 (0x00RRGGBB) per pixel
var rt_fbw: int = 320
var rt_fbh: int = 240
var rt_regs: ptr = ptr_null() # the 64 general-purpose game registers
var rt_regs: ptr = null # the 64 general-purpose game registers
var rt_rng: int = 305419896 # xorshift32 state
var rt_alive: int = 1 # platform still running?
@ -277,7 +277,7 @@ fn rt_put_int(buf: ptr, at: int, v: int) -> int {
fn rt_dump_ppm(path: str) -> void {
let f = file_open(path, "wb")
if ptr_is_null(f) { return }
if (f == null) { return }
let hdr = mem_alloc(64)
var n = rt_put_str(hdr, 0, "P6\n")
@ -310,7 +310,7 @@ import "ui.ludic"
# A character grid the game paints with map_row() and reads with tile(). Stored
# as one byte per cell in a flat buffer; out-of-bounds reads answer '#' so a
# caller can treat the edge of the world as wall without special-casing it.
var rt_map: ptr = ptr_null()
var rt_map: ptr = null
var rt_mapw: int = 0
var rt_maph: int = 0
@ -344,7 +344,7 @@ fn rt_tile(x: int, y: int) -> int {
# ---- status line ----------------------------------------------------------
# One persistent string of feedback/dialogue, copied into runtime-owned memory
# so it survives whatever the caller does with the original.
var rt_statusbuf: ptr = ptr_null()
var rt_statusbuf: ptr = null
fn rt_status(s: str) -> void {
var i = 0

View file

@ -13,12 +13,12 @@ const IMG_MAX: int = 32
const SPR_MAX: int = 160
const SPR_SZ: int = 16
var img_px: ptr = ptr_null() # IMG_MAX pointers to RGBA buffers
var img_w: ptr = ptr_null()
var img_h: ptr = ptr_null()
var img_px: ptr = null # IMG_MAX pointers to RGBA buffers
var img_w: ptr = null
var img_h: ptr = null
var img_n: int = 0
var spr_px: ptr = ptr_null() # SPR_MAX * 16 * 16 RGBA pixels, one block
var spr_px: ptr = null # SPR_MAX * 16 * 16 RGBA pixels, one block
var spr_n: int = 0
fn rt_image_init() -> void {
@ -44,11 +44,11 @@ fn png_tag(b: ptr, at: int, a: int, c: int, d: int, e: int) -> bool {
fn rt_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)
if n <= 0 { file_close(f); return ptr_null() }
if n <= 0 { file_close(f); return null }
let buf = mem_alloc(n + 8)
file_read(f, buf, n)
file_close(f)
@ -60,14 +60,14 @@ var rt_file_len: int = 0
# decoded image is left in these; -1 width means failure
var png_w: int = 0
var png_h: int = 0
var png_px: ptr = ptr_null()
var png_px: ptr = null
fn rt_decode_png(path: str) -> bool {
png_w = 0
png_h = 0
png_px = ptr_null()
png_px = null
let d = rt_read_file(path)
if ptr_is_null(d) { return false }
if (d == null) { return false }
let size = rt_file_len
if size < 8 { mem_free(d); return false }
if peek8(d, 0) != 137 { mem_free(d); return false }
@ -334,7 +334,7 @@ fn rt_png_load(path: str) -> int {
}
}
mem_free(png_px)
png_px = ptr_null()
png_px = null
return id
}
@ -377,7 +377,7 @@ fn rt_hexval(c: int) -> int {
fn rt_sprites_load(path: str) -> void {
let d = rt_read_file(path)
if ptr_is_null(d) { spr_n = 0; return }
if (d == null) { spr_n = 0; return }
let size = rt_file_len
let pal = mem_alloc(128 * 4)
mem_set(pal, 0, 128 * 4)

View file

@ -13,7 +13,7 @@
# ============================================================================
# ---- bit reader (DEFLATE packs bits least-significant-first) ---------------
var z_src: ptr = ptr_null()
var z_src: ptr = null
var z_len: int = 0
var z_pos: int = 0
var z_bitbuf: int = 0

View file

@ -18,50 +18,50 @@ const TT_EDGES: int = 16384 # line segments in one glyph
const TT_GC: int = 512 # glyph cache entries
# ---- per-font tables (parallel arrays; index = font id) -------------------
var tt_data: ptr = ptr_null()
var tt_size: ptr = ptr_null()
var tt_upem: ptr = ptr_null()
var tt_nglyf: ptr = ptr_null()
var tt_locfm: ptr = ptr_null()
var tt_nhm: ptr = ptr_null()
var tt_asc: ptr = ptr_null()
var tt_desc: ptr = ptr_null()
var tt_gap: ptr = ptr_null()
var tt_glyf: ptr = ptr_null()
var tt_loca: ptr = ptr_null()
var tt_hmtx: ptr = ptr_null()
var tt_cmap: ptr = ptr_null()
var tt_cfmt: ptr = ptr_null()
var tt_data: ptr = null
var tt_size: ptr = null
var tt_upem: ptr = null
var tt_nglyf: ptr = null
var tt_locfm: ptr = null
var tt_nhm: ptr = null
var tt_asc: ptr = null
var tt_desc: ptr = null
var tt_gap: ptr = null
var tt_glyf: ptr = null
var tt_loca: ptr = null
var tt_hmtx: ptr = null
var tt_cmap: ptr = null
var tt_cfmt: ptr = null
var tt_n: int = 0
# ---- outline + edge scratch (reused for every glyph) ----------------------
var ol_x: ptr = ptr_null()
var ol_y: ptr = ptr_null()
var ol_on: ptr = ptr_null()
var ol_ends: ptr = ptr_null()
var ol_x: ptr = null
var ol_y: ptr = null
var ol_on: ptr = null
var ol_ends: ptr = null
var ol_n: int = 0
var ol_ne: int = 0
var ed_x0: ptr = ptr_null()
var ed_y0: ptr = ptr_null()
var ed_x1: ptr = ptr_null()
var ed_y1: ptr = ptr_null()
var ed_x0: ptr = null
var ed_y0: ptr = null
var ed_x1: ptr = null
var ed_y1: ptr = null
var ed_n: int = 0
var sc_x: ptr = ptr_null() # scanline crossings
var sc_d: ptr = ptr_null()
var sc_x: ptr = null # scanline crossings
var sc_d: ptr = null
# ---- glyph cache ----------------------------------------------------------
var gc_used: ptr = ptr_null()
var gc_font: ptr = ptr_null()
var gc_cp: ptr = ptr_null()
var gc_px: ptr = ptr_null()
var gc_w: ptr = ptr_null()
var gc_h: ptr = ptr_null()
var gc_ox: ptr = ptr_null()
var gc_oy: ptr = ptr_null()
var gc_adv: ptr = ptr_null()
var gc_bmp: ptr = ptr_null()
var gc_used: ptr = null
var gc_font: ptr = null
var gc_cp: ptr = null
var gc_px: ptr = null
var gc_w: ptr = null
var gc_h: ptr = null
var gc_ox: ptr = null
var gc_oy: ptr = null
var gc_adv: ptr = null
var gc_bmp: ptr = null
fn rt_tt_init() -> void {
tt_data = mem_alloc(TT_MAX * 8)
@ -169,7 +169,7 @@ fn tt_pick_cmap(id: int, d: ptr, co: int) -> void {
fn rt_font_load(path: str) -> int {
if tt_n >= TT_MAX { return 0 - 1 }
let d = rt_read_file(path)
if ptr_is_null(d) { return 0 - 1 }
if (d == null) { return 0 - 1 }
let size = rt_file_len
var base = 0
# a .ttc collection points at its first font

View file

@ -61,7 +61,7 @@ fn tt_raster(id: int, gid: int, px: int) -> ptr {
ol_ne = 0
ed_n = 0
tt_load_outline(id, gid, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0)
if ol_n == 0 { return ptr_null() }
if ol_n == 0 { return null }
var minx = 999999.0
var miny = 999999.0
@ -83,10 +83,10 @@ fn tt_raster(id: int, gid: int, px: int) -> ptr {
if maxy == fx(flr(maxy)) { y1 = flr(maxy) }
let W = x1 - x0
let H = y1 - y0
if W <= 0 { return ptr_null() }
if H <= 0 { return ptr_null() }
if W > 1024 { return ptr_null() }
if H > 1024 { return ptr_null() }
if W <= 0 { return null }
if H <= 0 { return null }
if W > 1024 { return null }
if H > 1024 { return null }
gr_w = W
gr_h = H
gr_ox = x0
@ -250,7 +250,7 @@ fn tt_glyph_get(font: int, cp: int, px: int) -> int {
let s = h
if peek32(gc_used, s) == 1 {
let old = peekp(gc_bmp, s)
if ptr_is_null(old) == false { mem_free(old) }
if (old == null) == false { mem_free(old) }
}
let bmp = tt_raster(font, tt_glyph_index(font, cp), px)
pokep(gc_bmp, s, bmp)
@ -299,7 +299,7 @@ fn tt_utf8(s: str, at: int) -> int {
# ---- drawing --------------------------------------------------------------
fn tt_blit(bmp: ptr, w: int, h: int, dx: int, dy: int, colour: int) -> void {
if ptr_is_null(bmp) { return }
if (bmp == null) { return }
let cr = ((colour >> 16) & 255)
let cg = ((colour >> 8) & 255)
let cb = (colour & 255)

View file

@ -45,35 +45,35 @@ const K_VISIBLE: int = 18
const K_IMG: int = 19
const K_SKIN: int = 20
var ui_type: ptr = ptr_null()
var ui_parent: ptr = ptr_null()
var ui_w: ptr = ptr_null()
var ui_h: ptr = ptr_null()
var ui_x: ptr = ptr_null()
var ui_y: ptr = ptr_null()
var ui_haspos: ptr = ptr_null()
var ui_pad: ptr = ptr_null()
var ui_gap: ptr = ptr_null()
var ui_bg: ptr = ptr_null()
var ui_fg: ptr = ptr_null()
var ui_border: ptr = ptr_null()
var ui_align: ptr = ptr_null()
var ui_grow: ptr = ptr_null()
var ui_font: ptr = ptr_null()
var ui_size: ptr = ptr_null()
var ui_skin: ptr = ptr_null()
var ui_inset: ptr = ptr_null()
var ui_img: ptr = ptr_null()
var ui_focusable: ptr = ptr_null()
var ui_rx: ptr = ptr_null()
var ui_ry: ptr = ptr_null()
var ui_rw: ptr = ptr_null()
var ui_rh: ptr = ptr_null()
var ui_visible: ptr = ptr_null()
var ui_fired: ptr = ptr_null()
var ui_hasdyn: ptr = ptr_null()
var ui_dyn: ptr = ptr_null() # UI_MAX * 96 bytes
var ui_text: ptr = ptr_null() # UI_MAX static string pointers
var ui_type: ptr = null
var ui_parent: ptr = null
var ui_w: ptr = null
var ui_h: ptr = null
var ui_x: ptr = null
var ui_y: ptr = null
var ui_haspos: ptr = null
var ui_pad: ptr = null
var ui_gap: ptr = null
var ui_bg: ptr = null
var ui_fg: ptr = null
var ui_border: ptr = null
var ui_align: ptr = null
var ui_grow: ptr = null
var ui_font: ptr = null
var ui_size: ptr = null
var ui_skin: ptr = null
var ui_inset: ptr = null
var ui_img: ptr = null
var ui_focusable: ptr = null
var ui_rx: ptr = null
var ui_ry: ptr = null
var ui_rw: ptr = null
var ui_rh: ptr = null
var ui_visible: ptr = null
var ui_fired: ptr = null
var ui_hasdyn: ptr = null
var ui_dyn: ptr = null # UI_MAX * 96 bytes
var ui_text: ptr = null # UI_MAX static string pointers
var ui_n: int = 0
var ui_active: int = -1
@ -138,7 +138,7 @@ fn rt_ui_reset(n: int) -> void {
poke32(ui_visible, i, 1)
poke32(ui_fired, i, 0)
poke32(ui_hasdyn, i, 0)
pokep(ui_text, i, ptr_null())
pokep(ui_text, i, null)
}
}
@ -178,7 +178,7 @@ fn rt_ui_static_text(i: int, s: str) -> void {
fn ui_str(i: int) -> ptr {
if peek32(ui_hasdyn, i) == 1 { return ptr_add(ui_dyn, i * 96) }
let t = peekp(ui_text, i)
if ptr_is_null(t) { return ptr_add(ui_dyn, i * 96) }
if (t == null) { return ptr_add(ui_dyn, i * 96) }
return t
}

View file

@ -146,10 +146,10 @@ fn ui_draw_node(i: int) -> void {
}
# ---- focus and activation -------------------------------------------------
var ui_fl: ptr = ptr_null()
var ui_fl: ptr = null
fn ui_focusables() -> int {
if ptr_is_null(ui_fl) { ui_fl = mem_alloc(UI_MAX * 4) }
if (ui_fl == null) { ui_fl = mem_alloc(UI_MAX * 4) }
var n = 0
for i in 0 .. ui_n {
if peek32(ui_focusable, i) == 1 {