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
|
|
@ -581,7 +581,9 @@ Operators are built-in only (no overloading). The boolean operators are spelled
|
|||
rejected with a diagnostic naming the fix (`!=` is unaffected). Bitwise operators
|
||||
are **`& | ^ << >> ~`** (`>>` is a logical/unsigned shift). `expr with { field: … }`
|
||||
is not implemented; records appear only in `spawn`. Char literals (`'w'`) are
|
||||
`int` code points; colors are hex ints (`0xff8800`).
|
||||
`int` code points; colors are hex ints (`0xff8800`). `null` is the null-pointer
|
||||
literal; test any pointer/record/slice with `x == null` / `x != null` (an unset
|
||||
`Node`/`ptr` field reads back as `null`).
|
||||
|
||||
## Builtins (the standard library / runtime surface)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -68,8 +68,8 @@ object LudicVocabulary {
|
|||
// compiler intrinsics: the floor the Ludic-written runtime stands on
|
||||
"mem_alloc", "mem_realloc", "mem_free", "mem_copy", "mem_set", "peek8", "poke8", "peek32",
|
||||
"os_argc", "os_arg", "file_stderr",
|
||||
"poke32", "peekp", "pokep", "peekf", "pokef", "ptr_add", "ptr_null",
|
||||
"ptr_is_null", "as_fixed", "as_int", "file_open", "file_read", "file_write",
|
||||
"poke32", "peekp", "pokep", "peekf", "pokef", "ptr_add",
|
||||
"as_fixed", "as_int", "file_open", "file_read", "file_write",
|
||||
"file_seek", "file_tell", "file_close", "read_byte", "write_byte", "print_str",
|
||||
"str_len", "os_exit", "os_time",
|
||||
"is_windowed", "game_title", "win_open", "win_poll", "win_present",
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@
|
|||
{ "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|void)\\b" },
|
||||
{ "name": "constant.language.boolean.ludic", "match": "\\b(true|false)\\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" }
|
||||
]
|
||||
},
|
||||
|
|
@ -198,7 +198,7 @@
|
|||
{
|
||||
"comment": "compiler intrinsics — these lower straight to libc or the OS",
|
||||
"name": "support.function.intrinsic.ludic",
|
||||
"match": "\\b(mem_alloc|mem_realloc|mem_free|mem_copy|mem_set|peek8|poke8|peek32|poke32|peekp|pokep|peekf|pokef|ptr_add|ptr_null|ptr_is_null|as_fixed|as_int|file_open|file_read|file_write|file_seek|file_tell|file_close|read_byte|write_byte|print_str|str_len|os_exit|os_time|os_argc|os_arg|file_stderr|is_windowed|game_title|win_open|win_poll|win_present|win_running|win_close)\\b(?=\\s*\\()"
|
||||
"match": "\\b(mem_alloc|mem_realloc|mem_free|mem_copy|mem_set|peek8|poke8|peek32|poke32|peekp|pokep|peekf|pokef|ptr_add|as_fixed|as_int|file_open|file_read|file_write|file_seek|file_tell|file_close|read_byte|write_byte|print_str|str_len|os_exit|os_time|os_argc|os_arg|file_stderr|is_windowed|game_title|win_open|win_poll|win_present|win_running|win_close)\\b(?=\\s*\\()"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@
|
|||
{ "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|void)\\b" },
|
||||
{ "name": "constant.language.boolean.ludic", "match": "\\b(true|false)\\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" }
|
||||
]
|
||||
},
|
||||
|
|
@ -198,7 +198,7 @@
|
|||
{
|
||||
"comment": "compiler intrinsics — these lower straight to libc or the OS",
|
||||
"name": "support.function.intrinsic.ludic",
|
||||
"match": "\\b(mem_alloc|mem_realloc|mem_free|mem_copy|mem_set|peek8|poke8|peek32|poke32|peekp|pokep|peekf|pokef|ptr_add|ptr_null|ptr_is_null|as_fixed|as_int|file_open|file_read|file_write|file_seek|file_tell|file_close|read_byte|write_byte|print_str|str_len|os_exit|os_time|os_argc|os_arg|file_stderr|is_windowed|game_title|win_open|win_poll|win_present|win_running|win_close)\\b(?=\\s*\\()"
|
||||
"match": "\\b(mem_alloc|mem_realloc|mem_free|mem_copy|mem_set|peek8|poke8|peek32|poke32|peekp|pokep|peekf|pokef|ptr_add|as_fixed|as_int|file_open|file_read|file_write|file_seek|file_tell|file_close|read_byte|write_byte|print_str|str_len|os_exit|os_time|os_argc|os_arg|file_stderr|is_windowed|game_title|win_open|win_poll|win_present|win_running|win_close)\\b(?=\\s*\\()"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -161,8 +161,6 @@ static const LBuiltin LUDIC_INTRINSICS[] = {
|
|||
{"peekf","peekf(p: ptr, off: int) -> fixed","Read a fixed-point word."},
|
||||
{"pokef","pokef(p: ptr, off: int, v: fixed)","Write a fixed-point word."},
|
||||
{"ptr_add","ptr_add(p: ptr, off: int) -> ptr","Offset a pointer by bytes."},
|
||||
{"ptr_null","ptr_null() -> ptr","The null pointer."},
|
||||
{"ptr_is_null","ptr_is_null(p: ptr) -> bool","Null test."},
|
||||
{"as_fixed","as_fixed(i: int) -> fixed","Reinterpret an int as fixed (no conversion)."},
|
||||
{"as_int","as_int(f: fixed) -> int","Reinterpret a fixed as int (no conversion)."},
|
||||
{"file_open","file_open(path: str, mode: str) -> ptr","fopen."},
|
||||
|
|
@ -259,7 +257,7 @@ static void lud_lex(LLex* L, const char* src){
|
|||
int len = i - s; char w[128];
|
||||
if (len < (int)sizeof(w)){ memcpy(w, src + s, len); w[len] = 0; } else { w[0] = 0; }
|
||||
int k = LT_ID;
|
||||
if (!strcmp(w, "true") || !strcmp(w, "false")) k = LT_BOOL;
|
||||
if (!strcmp(w, "true") || !strcmp(w, "false") || !strcmp(w, "null")) k = LT_BOOL;
|
||||
else if (lud_in(LUDIC_TYPES, w)) k = LT_TYPE;
|
||||
else if (lud_in(LUDIC_PHASES, w)) k = LT_PHASE;
|
||||
else if (lud_is_keyword(w)) k = LT_KW;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue