ludic/selfhost/emit_head.ludic
Orkuncakilkaya eac8f8f335 Phase 2: enforce statement separators (Rule B)
The parser now requires a newline or ';' between statements (block() in
selfhost/parse.ludic); two statements may no longer sit adjacent with only
spaces. Also fixed if-without-else swallowing its trailing separator.

Migration: tools/ludic-tools/migrate_separators.c inserts ';' at statement
boundaries corpus-wide (examples, runtime, 25 self-host fragments, ~1100
boundaries). Verified semantically identical — the migrated compiler compiles
itself to IR byte-identical to the pre-migration seed, and every golden game
renders identically. Reseeded to the strict compiler; C-free fixpoint holds;
test.sh 14/14.

Docs: Rule B documented in LANGUAGE.md; BOOTSTRAP.md R1 + stale fences updated;
check-docs green across all docs. Fixed a multi-line string literal in
emit_expr.ludic (byte-identical \n escape) that the C toolchain lexer mis-lexes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-27 15:41:29 +03:00

108 lines
3.8 KiB
Text

# emit_head.ludic — string constants and the module header (libc declarations,
# the slice header type, struct layouts, globals, argv, format strings).
fn hexdig(n: int) -> int { if n < 10 { return 48 + n }; return 55 + n } # 0-9 A-F
# emit `@.strN = ... c"escaped\00"` and return its name; % and non-print -> \XX
fn emit_str_const(s: ptr) -> ptr {
let name = sconcat("@.str", itoa(ll_str))
ll_str = ll_str + 1
let n = slen(s)
emith(name); emith(" = private unnamed_addr constant [")
emith(itoa(n + 1)); emith(" x i8] c\"")
let i = 0
while i < n {
let c = peek8(s, i)
if c == 34 or c == 92 or c < 32 or c > 126 {
buf_putc(head, 92) # backslash
buf_putc(head, hexdig(c / 16))
buf_putc(head, hexdig(c % 16))
} else { buf_putc(head, c) }
i = i + 1
}
emith("\\00\"\n")
return name
}
# a fresh SSA register bound to a `getelementptr`, returned as its name
fn emit_gep_i8(base: ptr, idx: ptr) -> ptr {
let r = nreg()
emit(" "); emit(r); emit(" = getelementptr inbounds i8, ptr ")
emit(base); emit(", i32 "); emit(idx); emit("\n")
return r
}
# 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" }
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)) }
if streq(llty(d.ty), "ptr") { return "null" }
return "0"
}
fn emit_header() -> void {
emith("; Ludic (self-hosted) -> LLVM IR\n")
emith("declare i32 @printf(ptr, ...)\n")
emith("declare ptr @malloc(i64)\n")
emith("declare ptr @realloc(ptr, i64)\n")
emith("declare void @free(ptr)\n")
emith("declare ptr @fopen(ptr, ptr)\n")
emith("declare i64 @fread(ptr, i64, i64, ptr)\n")
emith("declare i64 @fwrite(ptr, i64, i64, ptr)\n")
emith("declare i32 @fseek(ptr, i64, i32)\n")
emith("declare i64 @ftell(ptr)\n")
emith("declare i32 @fclose(ptr)\n")
emith("declare void @exit(i32)\n")
emith("declare i32 @system(ptr)\n")
emith("declare ptr @getenv(ptr)\n")
emith("declare ptr @memcpy(ptr, ptr, i64)\n")
emith("declare ptr @memset(ptr, i32, i64)\n")
emith("declare i64 @strlen(ptr)\n")
emith("declare i32 @getchar()\n")
emith("declare i32 @putchar(i32)\n")
emith("declare i64 @time(ptr)\n")
emith("declare void @win_open(i32, i32, i32, ptr)\n")
emith("declare i32 @win_poll()\n")
emith("declare void @win_present(ptr, i32, i32)\n")
emith("declare i32 @win_running()\n")
emith("declare void @win_close()\n")
emith("@__stderrp = external global ptr\n")
emith("@.fmt_str = private unnamed_addr constant [3 x i8] c\"%s\\00\"\n")
emith("@.fmt_int = private unnamed_addr constant [4 x i8] c\"%d\\0A\\00\"\n")
emith("@L_argc = internal global i32 0\n")
emith("@L_argv = internal global ptr null\n")
emith("@.gametitle = private unnamed_addr constant [")
emith(itoa(slen(g_game_name) + 1)); emith(" x i8] c\""); emith(g_game_name); emith("\\00\"\n")
emith("%LSlice = type { ptr, i32, i32 }\n")
# struct layouts
let i = 0
while i < len(prog) {
let d = prog[i]
if d.kind == N_STRUCT {
emith("%Str_"); emith(d.s); emith(" = type { ")
if len(d.kids) == 0 { emith("i32") }
let f = 0
while f < len(d.kids) {
if f > 0 { emith(", ") }
emith(llty(d.kids[f].ty))
f = f + 1
}
emith(" }\n")
}
i = i + 1
}
# globals (vars) — aggregates/pointers default to null, scalars to 0
i = 0
while i < len(prog) {
let d = prog[i]
if d.kind == N_VAR {
emith("@g_"); emith(d.s); emith(" = internal global ")
emith(llty(d.ty)); emith(" ")
emith(global_init(d))
emith("\n")
}
i = i + 1
}
}