feat(lang): L4 type checker between parse and emit

selfhost/check/ walks every function, the entry, tests, globals' initializers and
@On listeners with real scopes, and refuses mixed number kinds, text and numbers,
two record types, mismatched slices and fn types, wrong argument counts, wrong
returns and wrong push elements - every mix-up at once, each at its line.
LUDIC_CHECK_REPORT=1 lists them by category. pointer stays untyped (L7's).

What it found is fixed: render3d's HDR scan calling the float-bits extern f_lt
with floats; ludic.shooter's right-stick aim overflowing past half a push;
prof.ludic storing longs in []int; extern arguments now coerced to their
parameters. Text-returning runtime functions say string; Assets.ready says bool.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-24 01:34:49 +03:00
parent 0677aee93f
commit 57b66bdf47
52 changed files with 27005 additions and 11844 deletions

View file

@ -398,7 +398,7 @@ function assets_font(name: pointer) -> int {
function assets_total() -> int { atlas_init(); return at_q_n }
function assets_loaded() -> int { atlas_init(); return at_q_pos }
function assets_ready() -> int { atlas_init(); if at_q_pos >= at_q_n { return 1 }; return 0 }
function assets_ready() -> bool { atlas_init(); return at_q_pos >= at_q_n }
# loading progress as a whole-number percent (0..100); an empty queue is 100.
function assets_progress() -> int {

View file

@ -62,7 +62,7 @@ function base64_decode(src: pointer) -> pointer {
}
# encode the bytes of NUL-terminated `src` as standard base64 (with '=' padding).
function base64_encode(src: pointer) -> pointer {
function base64_encode(src: pointer) -> string {
let tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
let n = len(src)
var out = ""

View file

@ -279,7 +279,7 @@ function bigint_to_int(a: BigNum) -> int {
}
# a limb (< 1e9) as exactly nine digits, left-padded with zeros
function bn_pad9(x: int) -> pointer {
function bn_pad9(x: int) -> string {
var s = string(x)
var pad = 9 - len(s)
var out = ""
@ -287,7 +287,7 @@ function bn_pad9(x: int) -> pointer {
return out + s
}
function bigint_str(a: BigNum) -> pointer {
function bigint_str(a: BigNum) -> string {
if a.sign == 0 { return "0" }
var out = ""
if a.sign < 0 { out = "-" }
@ -364,7 +364,7 @@ function decimal_rescale(d: Dec, places: int) -> Dec {
return dec_make(m, places)
}
function decimal_str(d: Dec) -> pointer {
function decimal_str(d: Dec) -> string {
var neg = ""
var m = d.m
if m.sign < 0 { neg = "-"; m = bigint_neg(m) }

View file

@ -946,7 +946,7 @@ extern function gl_get_doublev(pname: int, params: pointer) = "lgl_GetDoublev"
extern function gl_get_error() -> int = "lgl_GetError"
extern function gl_get_floatv(pname: int, params: pointer) = "lgl_GetFloatv"
extern function gl_get_integerv(pname: int, params: pointer) = "lgl_GetIntegerv"
extern function gl_get_string(name: int) -> pointer = "lgl_GetString"
extern function gl_get_string(name: int) -> string = "lgl_GetString"
extern function gl_get_tex_image(target: int, level: int, format: int, type: int, pixels: pointer) = "lgl_GetTexImage"
extern function gl_get_tex_parameterfv(target: int, pname: int, params: pointer) = "lgl_GetTexParameterfv"
extern function gl_get_tex_parameteriv(target: int, pname: int, params: pointer) = "lgl_GetTexParameteriv"
@ -1165,7 +1165,7 @@ extern function gl_clear_bufferiv(buffer: int, drawbuffer: int, value: pointer)
extern function gl_clear_bufferuiv(buffer: int, drawbuffer: int, value: pointer) = "lgl_ClearBufferuiv"
extern function gl_clear_bufferfv(buffer: int, drawbuffer: int, value: pointer) = "lgl_ClearBufferfv"
extern function gl_clear_bufferfi(buffer: int, drawbuffer: int, depth: fixed, stencil: int) = "lgl_ClearBufferfi"
extern function gl_get_stringi(name: int, index: int) -> pointer = "lgl_GetStringi"
extern function gl_get_stringi(name: int, index: int) -> string = "lgl_GetStringi"
extern function gl_draw_arrays_instanced(mode: int, first: int, count: int, instancecount: int) = "lgl_DrawArraysInstanced"
extern function gl_draw_elements_instanced(mode: int, count: int, type: int, indices: pointer, instancecount: int) = "lgl_DrawElementsInstanced"
extern function gl_tex_buffer(target: int, internalformat: int, buffer: int) = "lgl_TexBuffer"

View file

@ -235,7 +235,7 @@ function http_ok(h: int) -> bool {
return (st >= 200) and (st < 300)
}
function http_text(h: int) -> pointer {
function http_text(h: int) -> string {
if not http_valid(h) { return null }
return h_body[h - 1]
}
@ -245,7 +245,7 @@ function http_body_len(h: int) -> int {
return h_blen[h - 1]
}
function http_header_of(h: int, name: pointer) -> pointer {
function http_header_of(h: int, name: pointer) -> string {
if not http_valid(h) { return null }
let s = h - 1
if h_native[s] == 1 { return hs_header(s, name) }
@ -332,7 +332,7 @@ function http_parse(resp: pointer, len: int) -> int {
# Look up a header value in a raw header block (case-insensitive), returning a
# fresh NUL-terminated copy, or null if absent. Lines are "Name: value\r\n".
function http_find_header(hdr: pointer, name: pointer) -> pointer {
function http_find_header(hdr: pointer, name: pointer) -> string {
if hdr == null { return null }
let nlen = http_cstr_len(name)
let hlen = http_cstr_len(hdr)

View file

@ -150,7 +150,7 @@ function regex_end(m: Match, n: int) -> int {
if m == null or n < 0 or n > m.ng { return -1 }
return m.caps[2 * n + 1]
}
function regex_group(m: Match, n: int) -> pointer {
function regex_group(m: Match, n: int) -> string {
if m == null or n < 0 or n > m.ng { return "" }
let a = m.caps[2 * n]
let b = m.caps[2 * n + 1]
@ -169,7 +169,7 @@ function rx_append(out: IVec, s: pointer, a: int, b: int) -> void {
var i = a
while i < b { iv_push(out, s[i] & 255); i += 1 }
}
function regex_replace(str: pointer, pattern: pointer, repl: pointer) -> pointer {
function regex_replace(str: pointer, pattern: pointer, repl: pointer) -> string {
let p = regex_compile(pattern)
if p == null { return str }
let n = rx_slen(str)

View file

@ -647,7 +647,7 @@ function tmap_resolve(m: Tmap, gid: int) -> GidInfo {
function tmap_width(m: Tmap) -> int { return m.w }
function tmap_height(m: Tmap) -> int { return m.h }
function tmap_layer_count(m: Tmap) -> int { return len(m.layers) }
function tmap_layer_name(m: Tmap, i: int) -> pointer {
function tmap_layer_name(m: Tmap, i: int) -> string {
if i < 0 or i >= len(m.layers) { return "" }
if m.layers[i].name == null { return "" }
return m.layers[i].name
@ -855,7 +855,7 @@ function tmap_tileset_from_value(tv: Val) -> TmTileset {
return ts
}
function tiled_join(a: pointer, b: pointer) -> pointer { return Path.normalize(Path.join(a, b)) }
function tiled_join(a: pointer, b: pointer) -> string { return Path.normalize(Path.join(a, b)) }
# build the runtime map from an intermediate tree, resolving external tilesets
# and loading tileset images relative to `basedir`.
@ -1105,7 +1105,7 @@ function tmap_object(m: Tmap, layer: int, i: int) -> Val {
# the shape of an object: "tile" (a gid object), "point", "ellipse", "polygon",
# "polyline", "text", or "rectangle" (the default).
function tiled_object_shape(obj: Val) -> pointer {
function tiled_object_shape(obj: Val) -> string {
if value_has(obj, "gid") == 1 { return "tile" }
if value_has(obj, "point") == 1 { return "point" }
if value_has(obj, "ellipse") == 1 { return "ellipse" }
@ -1132,7 +1132,7 @@ function tiled_prop_node(container: Val, name: pointer) -> Val {
# a property's raw string value, with a fallback to the object's custom-type
# default (objecttypes.xml, via the type table). "" when absent everywhere.
function tiled_prop_str(container: Val, name: pointer) -> pointer {
function tiled_prop_str(container: Val, name: pointer) -> string {
let p = tiled_prop_node(container, name)
if value_kind(p) == 6 { return value_as_str(value_get(p, "value")) }
# fall back to the container's class default
@ -1153,7 +1153,7 @@ function tiled_prop_int(container: Val, name: pointer) -> int {
return xml_atoi(s)
}
function tiled_prop_type(container: Val, name: pointer) -> pointer {
function tiled_prop_type(container: Val, name: pointer) -> string {
let p = tiled_prop_node(container, name)
if value_kind(p) == 6 { return value_as_str(value_get(p, "type")) }
let cls = value_as_str(value_get(container, "type"))
@ -1353,13 +1353,13 @@ function tmap_layer_kind(m: Tmap, layer: int) -> int {
if layer < 0 or layer >= len(m.layers) { return -1 }
return m.layers[layer].kind
}
function tmap_layer_opacity(m: Tmap, layer: int) -> pointer {
function tmap_layer_opacity(m: Tmap, layer: int) -> string {
if layer < 0 or layer >= len(m.layers) { return "" }
let op = value_get(m.layers[layer].data, "opacity")
if value_kind(op) == 0 { return "1" }
return value_as_str(op)
}
function tmap_layer_tint(m: Tmap, layer: int) -> pointer {
function tmap_layer_tint(m: Tmap, layer: int) -> string {
if layer < 0 or layer >= len(m.layers) { return "" }
return value_as_str(value_get(m.layers[layer].data, "tintcolor"))
}

View file

@ -54,12 +54,12 @@ function value_put(obj: Val, key: pointer, item: Val) -> Val {
function value_kind(v: Val) -> int { return v.tag }
function value_count(v: Val) -> int { return len(v.kids) }
function value_as_int(v: Val) -> int { return v.num }
function value_as_str(v: Val) -> pointer { if v.txt == null { return "" }; return v.txt }
function value_as_str(v: Val) -> string { if v.txt == null { return "" }; return v.txt }
function value_at(list: Val, i: int) -> Val {
if i < 0 or i >= len(list.kids) { return value_null() }
return list.kids[i]
}
function value_key_at(obj: Val, i: int) -> pointer {
function value_key_at(obj: Val, i: int) -> string {
if i < 0 or i >= len(obj.keys) { return "" }
return obj.keys[i]
}
@ -76,7 +76,7 @@ function value_has(obj: Val, key: pointer) -> int {
# --- JSON encode ------------------------------------------------------------
# escape a string's `"` `\` and newline, wrapping it in quotes.
function json_quote(s: pointer) -> pointer {
function json_quote(s: pointer) -> string {
var out = "\""
var i = 0
let n = len(s)
@ -96,7 +96,7 @@ function json_quote(s: pointer) -> pointer {
# remainder hits zero. This is the exact inverse of jp_number's fixed decode, so
# a value_fixed round-trips through JSON bit-for-bit; short values (0.5, 2.0) stay
# short. Each step keeps frac < 655360, well within i32.
function json_fixed_str(raw: int) -> pointer {
function json_fixed_str(raw: int) -> string {
var a = raw
var sign = ""
if a < 0 { sign = "-"; a = -a }
@ -112,7 +112,7 @@ function json_fixed_str(raw: int) -> pointer {
return sign + string(ip) + "." + fs
}
function json_encode(v: Val) -> pointer {
function json_encode(v: Val) -> string {
if v.tag == 0 { return "null" }
if v.tag == 1 { return string(v.num) }
if v.tag == 2 { return json_fixed_str(v.num) }
@ -172,7 +172,7 @@ function jp_value(p: JP) -> Val {
}
# read a quoted string (cursor on the opening quote) -> the unescaped contents.
function jp_string(p: JP) -> pointer {
function jp_string(p: JP) -> string {
p.i += 1 # skip opening quote
var out = ""
while p.i < p.n {

View file

@ -34,8 +34,8 @@ function xml_new(tag: pointer) -> Xml {
}
# --- accessors --------------------------------------------------------------
function xml_tag(n: Xml) -> pointer { if n.tag == null { return "" }; return n.tag }
function xml_text(n: Xml) -> pointer { if n.text == null { return "" }; return n.text }
function xml_tag(n: Xml) -> string { if n.tag == null { return "" }; return n.tag }
function xml_text(n: Xml) -> string { if n.text == null { return "" }; return n.text }
function xml_child_count(n: Xml) -> int { return len(n.kids) }
function xml_child(n: Xml, i: int) -> Xml {
if i < 0 or i >= len(n.kids) { return xml_new("") }
@ -47,7 +47,7 @@ function xml_has(n: Xml, key: pointer) -> int {
while i < len(n.akeys) { if n.akeys[i] == key { return 1 }; i += 1 }
return 0
}
function xml_attr(n: Xml, key: pointer) -> pointer {
function xml_attr(n: Xml, key: pointer) -> string {
var i = 0
while i < len(n.akeys) { if n.akeys[i] == key { return n.avals[i] }; i += 1 }
return ""
@ -92,7 +92,7 @@ function xml_atoi(s: pointer) -> int {
# raw run. Only bytes 0..255 of a character reference are emitted (Ludic strings
# are byte strings); a code point above that is written as its low byte, which is
# ample for the ASCII/Latin-1 text Tiled attributes carry.
function xml_unescape(s: pointer) -> pointer {
function xml_unescape(s: pointer) -> string {
# fast path: no '&' means nothing to expand
var k = 0
let m = len(s)
@ -153,7 +153,7 @@ function xml_hexval(c: int) -> int {
# a one-byte string holding byte value `b` (1..255); "" for 0 (a NUL can't sit in
# a Ludic string). Built by slicing a 256-byte table of every byte value.
var xml_bytetab: pointer = null
function xml_byte(b: int) -> pointer {
function xml_byte(b: int) -> string {
if b <= 0 { return "" }
if xml_bytetab == null {
let t = bytes(257)
@ -202,7 +202,7 @@ function xp_skip_misc(p: XP) -> bool {
}
# read a name (element or attribute): letters, digits, '_', '-', ':', '.'
function xp_name(p: XP) -> pointer {
function xp_name(p: XP) -> string {
let start = p.i
while p.i < p.n {
let c = p.s[p.i]