Phase 7c: string operators + and ==/!= (retire streq/sconcat)

Strings are values now: `a + b` concatenates and `a == b` / `a != b` compare by
content, replacing the 605 `sconcat(...)` / `streq(...)` calls that made the
compiler read like C.

  streq(name, "let")            -> name == "let"
  sconcat("load ", reg)         -> "load " + reg
  sconcat(a, sconcat(b, c))     -> a + b + c

Implementation: emit_bin gains a string path. Strings are pointer-typed, so any
`+` with a pointer operand concatenates and `==`/`!=` between pointers compares
content — except when one side is the `null` literal, which stays a pointer
identity test (the only two kinds of pointer `==` in the codebase). Both call a
small hand-written IR prelude, @fn_str_eq / @fn_str_concat, emitted once into any
program that uses string ops (so it works for tools, games and the compiler with
no runtime-splice dependency and no duplicate symbols).

Delivered as two reseeds: (A) add the operators + prelude with the full
pointer-aware dispatch, keeping streq/sconcat; (B) migrate every call site
(354 lines, via a string-literal-safe balanced-paren script that leaves the
function definitions alone) and delete streq/sconcat. examples/strings.ludic +
a test.sh smoke (prints 1 2 3 4 5) guard it.

Reseeded (21890 lines); C-free fixpoint holds; goldens byte-identical; 18/18;
vocab + doc-fences clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-08-28 01:05:36 +03:00
parent 56024f2cba
commit a021362cdb
25 changed files with 3553 additions and 3248 deletions

View file

@ -579,7 +579,12 @@ The bitwise operators bind **tighter than comparison** (Go-style), so
Operators are built-in only (no overloading). The boolean operators are spelled
**`and` / `or` / `not`**; `&&` and `||` are not Ludic operators, and a bare `!` is
rejected with a diagnostic naming the fix (`!=` is unaffected). Bitwise operators
are **`& | ^ << >> ~`** (`>>` is a logical/unsigned shift). `expr with { field: … }`
are **`& | ^ << >> ~`** (`>>` is a logical/unsigned shift).
**Strings are values.** `a + b` concatenates two strings, and `a == b` / `a != b`
compare them **by content** (not by pointer). `"go" + dir == "goleft"` works as
written. (Under the hood these call a small emitted string runtime; a `==`/`!=`
against `null` is still a pointer test.) `expr with { field: … }`
is not implemented; records appear only in `spawn`. Char literals (`'w'`) are
`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

21
examples/strings.ludic Normal file
View file

@ -0,0 +1,21 @@
# strings.ludic — strings are values: compare with ==/!=, join with +.
# Running it prints: 1 2 3 4 5
program Strings {
entry {
let greeting = "hello"
if greeting == "hello" { print_int(1) } # content comparison
if greeting != "world" { print_int(2) }
let who = "Ludic"
let msg = greeting + ", " + who + "!" # concatenation chains
if msg == "hello, Ludic!" { print_int(3) }
# build up a string in a loop
var line = ""
var i = 0
while i < 3 { line = line + "ab"; i = i + 1 }
if line == "ababab" { print_int(4) }
if (greeting + who) != greeting { print_int(5) }
}
}

View file

@ -8,9 +8,9 @@ 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 (s == null) { perr(sconcat("member access on non-aggregate ", base.ty)) }
if (s == null) { perr((("member access on non-aggregate ") + base.ty)) }
let fidx = field_index(s, e.s)
if fidx < 0 { perr(sconcat("no such field ", e.s)) }
if fidx < 0 { perr(("no such field " + e.s)) }
g_addr_ty = field_type(s, e.s)
let r = nreg()
emit(" "); emit(r); emit(" = getelementptr inbounds "); emit(layout_ty(base.ty))

View file

@ -18,6 +18,7 @@ var loc_reg: []ptr
var loc_ty: []ptr
var loc_mut: []int # 1 = mutable (var / param / loop-var), 0 = immutable (let)
var nloc: int = 0
var g_uses_str: bool = false # a `str + str` / `str == str` was emitted -> emit the prelude
# loop targets for break/continue (innermost last)
var brk_lbl: []ptr
@ -37,30 +38,20 @@ fn emith(s: ptr) -> void { buf_puts(head, s) }
# stack slots MUST live in the entry block (an alloca in a loop walks the stack
# off its end), so they go into a per-function buffer spliced in at entry.
fn emit_alloca(llt: ptr) -> ptr {
let r = sconcat("%t", itoa(ll_t)); ll_t = ll_t + 1
let r = (("%t") + itoa(ll_t)); ll_t = ll_t + 1
buf_puts(falloc, " "); buf_puts(falloc, r); buf_puts(falloc, " = alloca "); buf_puts(falloc, llt); buf_puts(falloc, "\n")
return r
}
# "%t<n>" fresh register
fn sconcat(a: ptr, b: ptr) -> ptr {
let la = slen(a); let lb = slen(b)
let out = mem_alloc(la + lb + 1)
var i = 0
while i < la { poke8(out, i, peek8(a, i)); i = i + 1 }
var j = 0
while j < lb { poke8(out, la + j, peek8(b, j)); j = j + 1 }
poke8(out, la + lb, 0)
return out
}
fn nreg() -> ptr { let r = sconcat("%t", itoa(ll_t)); ll_t = ll_t + 1; return r }
fn lbl(pfx: ptr) -> ptr { let r = sconcat(pfx, itoa(ll_lbl)); ll_lbl = ll_lbl + 1; return r }
fn nreg() -> ptr { let r = (("%t") + itoa(ll_t)); ll_t = ll_t + 1; return r }
fn lbl(pfx: ptr) -> ptr { let r = (pfx + itoa(ll_lbl)); ll_lbl = ll_lbl + 1; return r }
# Ludic type -> LLVM type. int/bool are i32; everything else (ptr/str/struct/
# slice) is a pointer; void is void.
fn llty(t: ptr) -> ptr {
if streq(t, "int") or streq(t, "bool") or streq(t, "fixed") { return "i32" }
if streq(t, "void") { return "void" }
if (t == "int") or (t == "bool") or (t == "fixed") { return "i32" }
if (t == "void") { return "void" }
return "ptr"
}
@ -69,27 +60,27 @@ 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 }
while i < len(prog) { let d = prog[i]; if d.kind == N_ARCH and (d.s == name) { return d }; i = i + 1 }
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 }
while i < len(prog) { let d = prog[i]; if d.kind == N_COMP and (d.s == name) { return d }; i = i + 1 }
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.
fn layout_node(name: ptr) -> Node { return find_comp(name) }
fn layout_ty(name: ptr) -> ptr { return sconcat("%Cmp_", name) }
fn layout_ty(name: ptr) -> ptr { return (("%Cmp_") + name) }
fn field_index(s: Node, fname: ptr) -> int {
var i = 0
while i < len(s.kids) { if streq(s.kids[i].s, fname) { return i }; i = i + 1 }
while i < len(s.kids) { if (s.kids[i].s == fname) { return i }; i = i + 1 }
return 0 - 1
}
fn field_type(s: Node, fname: ptr) -> ptr {
var i = 0
while i < len(s.kids) { if streq(s.kids[i].s, fname) { return s.kids[i].ty }; i = i + 1 }
while i < len(s.kids) { if (s.kids[i].s == fname) { return s.kids[i].ty }; i = i + 1 }
return "int"
}
@ -98,8 +89,8 @@ fn find_global(name: ptr) -> Node {
var i = 0
while i < len(prog) {
let d = prog[i]
if d.kind == N_VAR and streq(d.s, name) { return d }
if d.kind == N_CONST and streq(d.s, name) { return d }
if d.kind == N_VAR and (d.s == name) { return d }
if d.kind == N_CONST and (d.s == name) { return d }
i = i + 1
}
return null
@ -111,9 +102,9 @@ fn enum_ordinal(ename: ptr, vname: ptr) -> int {
var i = 0
while i < len(prog) {
let d = prog[i]
if d.kind == N_ENUM and streq(d.s, ename) {
if d.kind == N_ENUM and (d.s == ename) {
var j = 0
while j < len(d.kids) { if streq(d.kids[j].s, vname) { return j }; j = j + 1 }
while j < len(d.kids) { if (d.kids[j].s == vname) { return j }; j = j + 1 }
}
i = i + 1
}
@ -121,7 +112,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 }
while i < len(prog) { let d = prog[i]; if d.kind == N_FN and (d.s == name) { return d }; i = i + 1 }
return null
}
@ -131,14 +122,14 @@ fn find_fn(name: ptr) -> Node {
var g_computed: []Node # each: s = "Prop.field", ty = result type, a = expr
fn register_computed(prop: ptr, field: ptr, ty: ptr, e: Node) -> void {
let cf = node(N_FIELD); cf.s = sconcat(prop, sconcat(".", field)); cf.ty = ty; cf.a = e
let cf = node(N_FIELD); cf.s = (prop + ("." + field)); cf.ty = ty; cf.a = e
push(g_computed, cf)
}
fn computed_expr(prop: ptr, field: ptr) -> Node {
if (prop == null) { return null }
let key = sconcat(prop, sconcat(".", field))
let key = (prop + ("." + field))
var i = 0
while i < len(g_computed) { if streq(g_computed[i].s, key) { return g_computed[i].a }; i = i + 1 }
while i < len(g_computed) { if (g_computed[i].s == key) { return g_computed[i].a }; i = i + 1 }
return null
}
# best-effort static type of an expression (for computed-field lookup; emits nothing)
@ -157,7 +148,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 }
while i < len(g_onspawn) { if (g_onspawn[i].s == model) { return g_onspawn[i].a }; i = i + 1 }
return null
}
@ -172,7 +163,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 }
while i < len(g_ondespawn) { if (g_ondespawn[i].s == model) { return g_ondespawn[i].a }; i = i + 1 }
return null
}
fn register_onattach(prop: ptr, body: Node) -> void {
@ -180,7 +171,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 }
while i < len(g_onattach) { if (g_onattach[i].s == prop) { return g_onattach[i].a }; i = i + 1 }
return null
}
@ -192,12 +183,12 @@ fn register_onenable(prop: ptr, body: Node) -> void { let n = node(N_BLOCK); n.s
fn register_ondisable(prop: ptr, body: Node) -> void { let n = node(N_BLOCK); n.s = prop; n.a = body; push(g_ondisable, 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 }
while i < len(g_onenable) { if (g_onenable[i].s == prop) { return g_onenable[i].a }; i = i + 1 }
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 }
while i < len(g_ondisable) { if (g_ondisable[i].s == prop) { return g_ondisable[i].a }; i = i + 1 }
return null
}
@ -217,6 +208,6 @@ fn loc_push(name: ptr, r: ptr, ty: ptr) -> void {
fn loc_set_mut(m: int) -> void { if nloc > 0 { loc_mut[nloc - 1] = m } }
fn loc_find(name: ptr) -> int {
var i = nloc - 1
while i >= 0 { if streq(loc_name[i], name) { return i }; i = i - 1 }
while i >= 0 { if (loc_name[i] == name) { return i }; i = i - 1 }
return 0 - 1
}

View file

@ -19,7 +19,7 @@ fn emit_fn(d: Node) -> void {
let saved = code
code = fbody
let rl = llty(ret_ty)
if not streq(rl, "void") { buf_puts(falloc, " %retval = alloca "); buf_puts(falloc, rl); buf_puts(falloc, "\n") }
if not (rl == "void") { buf_puts(falloc, " %retval = alloca "); buf_puts(falloc, rl); buf_puts(falloc, "\n") }
# params: store each incoming argument into a stack slot
var i = 0
while i < len(d.kids) {
@ -32,8 +32,8 @@ fn emit_fn(d: Node) -> void {
emit_block(d.a)
if not g_term { emit(" br label %ret\n") }
emit("ret:\n")
if streq(rl, "void") { emit(" ret void\n") }
else { let r = emit_bind(sconcat("load ", sconcat(rl, ", ptr %retval"))); emit(" ret "); emit(rl); emit(" "); emit(r); emit("\n") }
if (rl == "void") { emit(" ret void\n") }
else { let r = emit_bind(("load " + (rl + (", ptr %retval")))); emit(" ret "); emit(rl); emit(" "); emit(r); emit("\n") }
code = saved
emit("define "); emit(rl); emit(" @fn_"); emit(d.s); emit("("); emit_params_sig(d); emit(") {\nentry:\n")
emit(buf_str(falloc))
@ -67,6 +67,7 @@ fn emit_main(d: Node) -> void {
fn emit_program() -> void {
head = buf_new()
code = buf_new()
g_uses_str = false
loc_name = new []ptr; loc_reg = new []ptr; loc_ty = new []ptr; loc_mut = new []int
brk_lbl = new []ptr; cnt_lbl = new []ptr
self_stk = new []ptr
@ -82,6 +83,7 @@ fn emit_program() -> void {
i = 0
while i < len(prog) { if prog[i].kind == N_MAIN { emit_main(prog[i]) }; i = i + 1 }
}
if g_uses_str { emit_str_prelude() } # @fn_str_eq / @fn_str_concat, after all uses are seen
}
# Flush the emitted IR. With a null path it goes to stdout (the pipe the shell

View file

@ -27,9 +27,9 @@ fn emit_ecs_storage() -> void {
emith("@L_key = internal global i32 0\n")
emith("@L_entc = internal global i32 0\n")
let me = itoa(MAX_ENT)
emith(sconcat("@L_alive = internal global [", sconcat(me, " x i32] zeroinitializer\n")))
emith(sconcat("@L_kind = internal global [", sconcat(me, " x i32] zeroinitializer\n")))
emith(sconcat("@L_freelist = internal global [", sconcat(me, " x i32] zeroinitializer\n")))
emith(("@L_alive = internal global [" + (me + " x i32] zeroinitializer\n")))
emith(("@L_kind = internal global [" + (me + " x i32] zeroinitializer\n")))
emith(("@L_freelist = internal global [" + (me + " x i32] zeroinitializer\n")))
emith("@L_freen = internal global i32 0\n")
var i = 0
while i < len(prog) {
@ -38,12 +38,12 @@ fn emit_ecs_storage() -> void {
# header). Every property in an ECS program is a component today; a property
# used only via `new` would not need these, but no such program mixes the two.
if c.kind == N_COMP {
emith(sconcat("@S_", sconcat(c.s, sconcat(" = internal global [", sconcat(me, sconcat(" x %Cmp_", sconcat(c.s, "] zeroinitializer\n")))))))
emith(sconcat("@H_", sconcat(c.s, sconcat(" = internal global [", sconcat(me, " x i8] zeroinitializer\n")))))
emith(("@S_" + (c.s + (" = internal global [" + (me + ((" x %Cmp_") + (c.s + "] zeroinitializer\n")))))))
emith(("@H_" + (c.s + (" = internal global [" + (me + " x i8] zeroinitializer\n")))))
}
# one enabled-flag global per model and per handler (default enabled)
if c.kind == N_ARCH { emith(sconcat("@ME_", sconcat(c.s, " = internal global i32 1\n"))) }
if c.kind == N_SYS { emith(sconcat("@HE_", sconcat(c.s, " = internal global i32 1\n"))) }
if c.kind == N_ARCH { emith(("@ME_" + (c.s + " = internal global i32 1\n"))) }
if c.kind == N_SYS { emith(("@HE_" + (c.s + " = internal global i32 1\n"))) }
i = i + 1
}
}
@ -55,7 +55,7 @@ fn emit_ecs_allocator() -> void {
var i = 0
while i < len(prog) {
if prog[i].kind == N_COMP {
let hn = sconcat("%h", itoa(i))
let hn = (("%h") + itoa(i))
emit(" "); emit(hn); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(prog[i].s); emit(", i32 0, i32 %e\n")
emit(" store i8 0, ptr "); emit(hn); emit("\n")
}

View file

@ -1,7 +1,7 @@
# emit_expr.ludic — lower an expression to IR, returning its register and type.
fn emit_load_at(addr: ptr, ty: ptr) -> Val {
let r = emit_bind(sconcat("load ", sconcat(llty(ty), sconcat(", ptr ", addr))))
let r = emit_bind(("load " + (llty(ty) + (", ptr " + addr))))
return val(r, ty)
}
@ -10,100 +10,124 @@ fn emit_load_at(addr: ptr, ty: ptr) -> Val {
fn emit_logic(e: Node) -> Val {
let slot = emit_alloca("i32")
let la = emit_expr(e.a)
let lc = emit_bind(sconcat("icmp ne i32 ", sconcat(la.code, ", 0")))
let lz = emit_bind(sconcat("zext i1 ", sconcat(lc, " to i32")))
let lc = emit_bind(("icmp ne i32 " + (la.code + ", 0")))
let lz = emit_bind(("zext i1 " + (lc + " to i32")))
emit(" store i32 "); emit(lz); emit(", ptr "); emit(slot); emit("\n")
let ev = lbl("sc"); let done = lbl("scend")
if streq(e.s, "and") { emit(" br i1 "); emit(lc); emit(", label %"); emit(ev); emit(", label %"); emit(done); emit("\n") }
if (e.s == "and") { emit(" br i1 "); emit(lc); emit(", label %"); emit(ev); emit(", label %"); emit(done); emit("\n") }
else { emit(" br i1 "); emit(lc); emit(", label %"); emit(done); emit(", label %"); emit(ev); emit("\n") }
emit(ev); emit(":\n")
let rb = emit_expr(e.b)
let rc = emit_bind(sconcat("icmp ne i32 ", sconcat(rb.code, ", 0")))
let rz = emit_bind(sconcat("zext i1 ", sconcat(rc, " to i32")))
let rc = emit_bind(("icmp ne i32 " + (rb.code + ", 0")))
let rz = emit_bind(("zext i1 " + (rc + " to i32")))
emit(" store i32 "); emit(rz); emit(", ptr "); emit(slot); emit("\n")
emit(" br label %"); emit(done); emit("\n")
emit(done); emit(":\n")
return val(emit_bind(sconcat("load i32, ptr ", slot)), "bool")
return val(emit_bind(("load i32, ptr " + slot)), "bool")
}
fn cmp_code(op: ptr) -> ptr {
if streq(op, "<") { return "slt" }
if streq(op, "<=") { return "sle" }
if streq(op, ">") { return "sgt" }
if streq(op, ">=") { return "sge" }
if streq(op, "==") { return "eq" }
if (op == ("<")) { return "slt" }
if (op == ("<=")) { return "sle" }
if (op == (">")) { return "sgt" }
if (op == (">=")) { return "sge" }
if (op == ("==")) { return "eq" }
return "ne"
}
fn is_cmp(op: ptr) -> bool {
return streq(op,"<") or streq(op,"<=") or streq(op,">") or streq(op,">=") or streq(op,"==") or streq(op,"!=")
return (op == ("<")) or (op == ("<=")) or (op == (">")) or (op == (">=")) or (op == ("==")) or (op == ("!="))
}
fn arith_code(op: ptr) -> ptr {
if streq(op, "+") { return "add" }
if streq(op, "-") { return "sub" }
if streq(op, "*") { return "mul" }
if streq(op, "/") { return "sdiv" }
if streq(op, "&") { return "and" }
if streq(op, "|") { return "or" }
if streq(op, "^") { return "xor" }
if streq(op, "<<") { return "shl" }
if streq(op, ">>") { return "lshr" } # logical shift (unsigned)
if (op == ("+")) { return "add" }
if (op == ("-")) { return "sub" }
if (op == ("*")) { return "mul" }
if (op == ("/")) { return "sdiv" }
if (op == ("&")) { return "and" }
if (op == ("|")) { return "or" }
if (op == ("^")) { return "xor" }
if (op == ("<<")) { return "shl" }
if (op == (">>")) { return "lshr" } # logical shift (unsigned)
return "srem"
}
# widen an int value to Q16.16 by shifting left 16 (a fixed value passes through)
fn to_fixed(v: Val) -> ptr {
if streq(v.ty, "fixed") { return v.code }
return emit_bind(sconcat("shl i32 ", sconcat(v.code, ", 16")))
if (v.ty == "fixed") { return v.code }
return emit_bind(("shl i32 " + (v.code + ", 16")))
}
# string operators: `a + b` concatenates, `a == b` / `a != b` compare by content.
# Both call the @fn_str_* prelude (emitted once per program that uses them).
fn emit_str_op(op: ptr, a: Val, b: Val) -> Val {
g_uses_str = true
if (op == ("+")) {
return val(emit_bind(("call ptr @fn_str_concat(ptr " + (a.code + (", ptr " + (b.code + ")"))))), "str")
}
let r = emit_bind(("call i32 @fn_str_eq(ptr " + (a.code + (", ptr " + (b.code + ")")))))
if (op == ("!=")) {
let c = emit_bind(("icmp eq i32 " + (r + ", 0")))
return val(emit_bind(("zext i1 " + (c + " to i32"))), "bool")
}
return val(r, "bool")
}
fn emit_bin(e: Node) -> Val {
if streq(e.s, "and") or streq(e.s, "or") { return emit_logic(e) }
if (e.s == "and") or (e.s == "or") { return emit_logic(e) }
let a = emit_expr(e.a)
let b = emit_expr(e.b)
let fx = streq(a.ty, "fixed") or streq(b.ty, "fixed")
# strings are pointer-typed, so any `+` with a pointer operand is concatenation,
# and `==`/`!=` between pointers is content comparison — except `x == null`,
# which is a pointer-identity test and falls through to the icmp below.
let ptrish = (llty(a.ty) == "ptr") or (llty(b.ty) == "ptr")
let isnull = e.a.kind == E_NULL or e.b.kind == E_NULL
if ptrish {
if (e.s == ("+")) { return emit_str_op("+", a, b) }
if ((e.s == ("==")) or (e.s == ("!="))) and not isnull { return emit_str_op(e.s, a, b) }
}
let fx = (a.ty == "fixed") or (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) }
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")
else { if (llty(a.ty) == "ptr") or (llty(b.ty) == "ptr") { ct = "ptr" } } # `p == null`, str/record identity
let c = emit_bind(("icmp " + (cmp_code(e.s) + ((" " + (ct + " ")) + (ac + (", " + bc))))))
return val(emit_bind(("zext i1 " + (c + " to i32"))), "bool")
}
if fx {
let af = to_fixed(a); let bf = to_fixed(b)
if streq(e.s, "*") {
let a64 = emit_bind(sconcat("sext i32 ", sconcat(af, " to i64")))
let b64 = emit_bind(sconcat("sext i32 ", sconcat(bf, " to i64")))
let m = emit_bind(sconcat("mul i64 ", sconcat(a64, sconcat(", ", b64))))
let sh = emit_bind(sconcat("ashr i64 ", sconcat(m, ", 16")))
return val(emit_bind(sconcat("trunc i64 ", sconcat(sh, " to i32"))), "fixed")
if (e.s == ("*")) {
let a64 = emit_bind(("sext i32 " + (af + " to i64")))
let b64 = emit_bind(("sext i32 " + (bf + " to i64")))
let m = emit_bind(("mul i64 " + (a64 + (", " + b64))))
let sh = emit_bind(("ashr i64 " + (m + ", 16")))
return val(emit_bind(("trunc i64 " + (sh + " to i32"))), "fixed")
}
if streq(e.s, "/") {
let a64 = emit_bind(sconcat("sext i32 ", sconcat(af, " to i64")))
let ash = emit_bind(sconcat("shl i64 ", sconcat(a64, ", 16")))
let b64 = emit_bind(sconcat("sext i32 ", sconcat(bf, " to i64")))
let dv = emit_bind(sconcat("sdiv i64 ", sconcat(ash, sconcat(", ", b64))))
return val(emit_bind(sconcat("trunc i64 ", sconcat(dv, " to i32"))), "fixed")
if (e.s == ("/")) {
let a64 = emit_bind(("sext i32 " + (af + " to i64")))
let ash = emit_bind(("shl i64 " + (a64 + ", 16")))
let b64 = emit_bind(("sext i32 " + (bf + " to i64")))
let dv = emit_bind(("sdiv i64 " + (ash + (", " + b64))))
return val(emit_bind(("trunc i64 " + (dv + " to i32"))), "fixed")
}
let r = emit_bind(sconcat(arith_code(e.s), sconcat(" i32 ", sconcat(af, sconcat(", ", bf)))))
let r = emit_bind((arith_code(e.s) + (" i32 " + (af + (", " + bf)))))
return val(r, "fixed")
}
let r = emit_bind(sconcat(arith_code(e.s), sconcat(" i32 ", sconcat(a.code, sconcat(", ", b.code)))))
let r = emit_bind((arith_code(e.s) + (" i32 " + (a.code + (", " + b.code)))))
return val(r, "int")
}
fn emit_call(e: Node) -> Val {
let name = e.a.s
if streq(name, "self") { if nself == 0 { return val("0", "entity") }; return val(emit_bind(sconcat("load i32, ptr ", self_stk[nself - 1])), "entity") }
if streq(name, "key") { return val(emit_bind("load i32, ptr @L_key"), "int") }
if streq(name, "save") { emit(" call void @L_save()\n"); return val("0", "void") }
if streq(name, "ui_build") { emit(" call void @ui_build()\n"); return val("0", "void") }
if streq(name, "load") { return val(emit_bind("call i32 @L_load()"), "bool") }
if streq(name, "quit") { emit(" store i32 0, ptr @L_running\n"); return val("0", "void") }
if streq(name, "len") { return emit_len(e) }
if streq(name, "push") { return emit_push(e) }
if streq(name, "fx") { let a = emit_expr(e.kids[0]); return val(emit_bind(sconcat("shl i32 ", sconcat(a.code, ", 16"))), "fixed") }
if streq(name, "flr") { let a = emit_expr(e.kids[0]); return val(emit_bind(sconcat("ashr i32 ", sconcat(a.code, ", 16"))), "int") }
if (name == "self") { if nself == 0 { return val("0", "entity") }; return val(emit_bind(("load i32, ptr " + self_stk[nself - 1])), "entity") }
if (name == "key") { return val(emit_bind("load i32, ptr @L_key"), "int") }
if (name == "save") { emit(" call void @L_save()\n"); return val("0", "void") }
if (name == "ui_build") { emit(" call void @ui_build()\n"); return val("0", "void") }
if (name == "load") { return val(emit_bind("call i32 @L_load()"), "bool") }
if (name == "quit") { emit(" store i32 0, ptr @L_running\n"); return val("0", "void") }
if (name == "len") { return emit_len(e) }
if (name == "push") { return emit_push(e) }
if (name == "fx") { let a = emit_expr(e.kids[0]); return val(emit_bind(("shl i32 " + (a.code + ", 16"))), "fixed") }
if (name == "flr") { let a = emit_expr(e.kids[0]); return val(emit_bind(("ashr i32 " + (a.code + ", 16"))), "int") }
if is_intrinsic(name) { return emit_intrinsic(name, e) }
if is_intrinsic2(name) { return emit_intrinsic2(name, e) }
if is_math_builtin(name) { return emit_math_builtin(name, e) }
@ -111,9 +135,9 @@ fn emit_call(e: Node) -> Val {
var cname = name
if (fn2 == null) {
# a builtin like clear()/reg() is satisfied by its rt_ function
let rtname = sconcat("rt_", name)
let rtname = ("rt_" + name)
fn2 = find_fn(rtname)
if (fn2 == null) { perr(sconcat("unknown function ", name)) }
if (fn2 == null) { perr(("unknown function " + name)) }
cname = rtname
}
# evaluate args first (their IR is emitted before the call instruction)
@ -124,7 +148,7 @@ fn emit_call(e: Node) -> Val {
let rl = llty(fn2.ty)
emit(" ")
var rreg = "0"
if not streq(rl, "void") { rreg = nreg(); emit(rreg); emit(" = ") }
if not (rl == "void") { rreg = nreg(); emit(rreg); emit(" = ") }
emit("call "); emit(rl); emit(" @fn_"); emit(cname); emit("(")
i = 0
while i < len(args) {
@ -153,12 +177,12 @@ fn emit_expr(e: Node) -> Val {
let g = find_global(e.s)
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))))
let r = emit_bind(("load " + (llty(g.ty) + (", ptr @g_" + e.s))))
return val(r, g.ty)
}
# a UI_<name> that is not a const/var resolves to its widget index
if is_ui_ident(e.s) { return val(itoa(ui_index_of(e.s)), "int") }
perr(sconcat("unknown identifier ", e.s))
perr(("unknown identifier " + e.s))
}
if e.kind == E_MEMBER {
if e.a.kind == E_ID { # `Enum.Variant` -> its ordinal, a compile-time int
@ -177,10 +201,10 @@ fn emit_expr(e: Node) -> Val {
if e.kind == E_BIN { return emit_bin(e) }
if e.kind == E_UN {
let a = emit_expr(e.a)
if streq(e.s, "-") { return val(emit_bind(sconcat("sub i32 0, ", a.code)), "int") }
if streq(e.s, "~") { return val(emit_bind(sconcat("xor i32 ", sconcat(a.code, ", -1"))), "int") }
let c = emit_bind(sconcat("icmp eq i32 ", sconcat(a.code, ", 0")))
return val(emit_bind(sconcat("zext i1 ", sconcat(c, " to i32"))), "bool")
if (e.s == ("-")) { return val(emit_bind(("sub i32 0, " + a.code)), "int") }
if (e.s == "~") { return val(emit_bind(("xor i32 " + (a.code + (", -1")))), "int") }
let c = emit_bind(("icmp eq i32 " + (a.code + ", 0")))
return val(emit_bind(("zext i1 " + (c + " to i32"))), "bool")
}
perr("cannot emit expression")
return val("0", "int")

View file

@ -24,9 +24,9 @@ fn emit_calls_for_phase(phase: ptr) -> void {
var i = 0
while i < len(prog) {
let d = prog[i]
if d.kind == N_SYS and streq(d.ty, phase) { # skip a disabled handler
let he = emit_bind(sconcat("load i32, ptr @HE_", d.s))
let hc = emit_bind(sconcat("icmp ne i32 ", sconcat(he, ", 0")))
if d.kind == N_SYS and (d.ty == phase) { # skip a disabled handler
let he = emit_bind(("load i32, ptr @HE_" + d.s))
let hc = emit_bind(("icmp ne i32 " + (he + ", 0")))
let run = lbl("hrun"); let skip = lbl("hskip")
emit(" br i1 "); emit(hc); emit(", label %"); emit(run); emit(", label %"); emit(skip); emit("\n")
emit(run); emit(":\n call void @sys_"); emit(d.s); emit("()\n")
@ -76,11 +76,11 @@ fn emit_game_main() -> void {
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")))
let rc = emit_bind(("icmp ne i32 " + (r + ", 0")))
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))))
let pc = emit_bind(("icmp ne i32 " + (pr + ", 0")))
let go = emit_bind(("and i1 " + (rc + (", " + pc))))
emit(" br i1 "); emit(go); emit(", label %body, label %done\n")
} else {
emit(" br i1 "); emit(rc); emit(", label %body, label %done\n")

View file

@ -5,7 +5,7 @@ fn hexdig(n: int) -> int { if n < 10 { return 48 + n }; return 55 + n } # 0-9
# 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))
let name = ("@.str" + itoa(ll_str))
ll_str = ll_str + 1
let n = slen(s)
emith(name); emith(" = private unnamed_addr constant [")
@ -34,11 +34,11 @@ 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 (d.a == null) { if streq(llty(d.ty), "ptr") { return "null" }; return "0" }
if (d.a == null) { if (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" }
if e.kind == E_UN and (e.s == ("-")) and e.a.kind == E_INT { return (("-") + itoa(e.a.ival)) }
if (llty(d.ty) == "ptr") { return "null" }
return "0"
}
@ -108,3 +108,46 @@ fn emit_header() -> void {
i = i + 1
}
}
# The string runtime, emitted (once) into any program that uses `+`/`==`/`!=`
# on strings. Hand-written IR over NUL-terminated byte buffers: str_eq walks both
# until a mismatch or a shared terminator; str_concat measures both, mallocs
# len+len+1, copies each half, and NUL-terminates. @malloc is always declared.
fn emit_str_prelude() -> void {
emith("define i32 @fn_str_eq(ptr %a, ptr %b) {\n")
emith("entry:\n br label %loop\n")
emith("loop:\n %i = phi i32 [ 0, %entry ], [ %i1, %cont ]\n")
emith(" %pa = getelementptr inbounds i8, ptr %a, i32 %i\n")
emith(" %pb = getelementptr inbounds i8, ptr %b, i32 %i\n")
emith(" %ca = load i8, ptr %pa\n %cb = load i8, ptr %pb\n")
emith(" %df = icmp ne i8 %ca, %cb\n br i1 %df, label %ret0, label %chk\n")
emith("chk:\n %zt = icmp eq i8 %ca, 0\n br i1 %zt, label %ret1, label %cont\n")
emith("cont:\n %i1 = add i32 %i, 1\n br label %loop\n")
emith("ret1:\n ret i32 1\nret0:\n ret i32 0\n}\n")
emith("define ptr @fn_str_concat(ptr %a, ptr %b) {\n")
emith("entry:\n br label %al\n")
emith("al:\n %ia = phi i32 [ 0, %entry ], [ %ia1, %alb ]\n")
emith(" %pa = getelementptr inbounds i8, ptr %a, i32 %ia\n %cca = load i8, ptr %pa\n")
emith(" %za = icmp eq i8 %cca, 0\n br i1 %za, label %bl0, label %alb\n")
emith("alb:\n %ia1 = add i32 %ia, 1\n br label %al\n")
emith("bl0:\n br label %bl\n")
emith("bl:\n %ib = phi i32 [ 0, %bl0 ], [ %ib1, %blb ]\n")
emith(" %pb = getelementptr inbounds i8, ptr %b, i32 %ib\n %ccb = load i8, ptr %pb\n")
emith(" %zb = icmp eq i8 %ccb, 0\n br i1 %zb, label %alloc, label %blb\n")
emith("blb:\n %ib1 = add i32 %ib, 1\n br label %bl\n")
emith("alloc:\n %sum = add i32 %ia, %ib\n %sz = add i32 %sum, 1\n")
emith(" %sz64 = sext i32 %sz to i64\n %out = call ptr @malloc(i64 %sz64)\n br label %c1\n")
emith("c1:\n %i = phi i32 [ 0, %alloc ], [ %i1, %c1b ]\n")
emith(" %d1 = icmp slt i32 %i, %ia\n br i1 %d1, label %c1b, label %c2i\n")
emith("c1b:\n %s1 = getelementptr inbounds i8, ptr %a, i32 %i\n %v1 = load i8, ptr %s1\n")
emith(" %o1 = getelementptr inbounds i8, ptr %out, i32 %i\n store i8 %v1, ptr %o1\n")
emith(" %i1 = add i32 %i, 1\n br label %c1\n")
emith("c2i:\n br label %c2\n")
emith("c2:\n %j = phi i32 [ 0, %c2i ], [ %j1, %c2b ]\n")
emith(" %d2 = icmp slt i32 %j, %ib\n br i1 %d2, label %c2b, label %fin\n")
emith("c2b:\n %s2 = getelementptr inbounds i8, ptr %b, i32 %j\n %v2 = load i8, ptr %s2\n")
emith(" %oj = add i32 %ia, %j\n %o2 = getelementptr inbounds i8, ptr %out, i32 %oj\n store i8 %v2, ptr %o2\n")
emith(" %j1 = add i32 %j, 1\n br label %c2\n")
emith("fin:\n %pe = getelementptr inbounds i8, ptr %out, i32 %sum\n store i8 0, ptr %pe\n ret ptr %out\n}\n")
}

View file

@ -7,14 +7,14 @@ 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,"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 }
if streq(name,"os_argc") or streq(name,"os_arg") or streq(name,"os_exit") or streq(name,"file_stderr") { return true }
if streq(name,"os_system") or streq(name,"os_getenv") { return true }
if streq(name,"peek32") or streq(name,"poke32") { return true }
if (name == "mem_alloc") or (name == "mem_realloc") { return true }
if (name == "peek8") or (name == "poke8") { return true }
if (name == "file_open") or (name == "file_read") or (name == "file_write") { return true }
if (name == "file_seek") or (name == "file_tell") or (name == "file_close") { return true }
if (name == "print_str") or (name == "print_int") { return true }
if (name == "os_argc") or (name == "os_arg") or (name == "os_exit") or (name == "file_stderr") { return true }
if (name == "os_system") or (name == "os_getenv") { return true }
if (name == "peek32") or (name == "poke32") { return true }
return false
}
# emit " <r> = <rest>\n" and return r
@ -25,97 +25,97 @@ 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
# ptr_null / ptr_is_null are the `null` literal and `x == null` now.
if streq(name, "mem_alloc") {
if (name == "mem_alloc") {
let n = arg_code(e, 0)
let w = emit_bind(sconcat("zext i32 ", sconcat(n, " to i64")))
return val(emit_bind(sconcat("call ptr @malloc(i64 ", sconcat(w, ")"))), "ptr")
let w = emit_bind(("zext i32 " + (n + " to i64")))
return val(emit_bind(("call ptr @malloc(i64 " + (w + ")"))), "ptr")
}
if streq(name, "mem_realloc") {
if (name == "mem_realloc") {
let p = arg_code(e, 0); let n = arg_code(e, 1)
let w = emit_bind(sconcat("zext i32 ", sconcat(n, " to i64")))
return val(emit_bind(sconcat("call ptr @realloc(ptr ", sconcat(p, sconcat(", i64 ", sconcat(w, ")"))))), "ptr")
let w = emit_bind(("zext i32 " + (n + " to i64")))
return val(emit_bind(("call ptr @realloc(ptr " + (p + (", i64 " + (w + ")"))))), "ptr")
}
if streq(name, "peek8") {
if (name == "peek8") {
let p = arg_code(e, 0); let i = arg_code(e, 1)
let a = emit_gep_i8(p, i)
let b = emit_bind(sconcat("load i8, ptr ", a))
return val(emit_bind(sconcat("zext i8 ", sconcat(b, " to i32"))), "int")
let b = emit_bind(("load i8, ptr " + a))
return val(emit_bind(("zext i8 " + (b + " to i32"))), "int")
}
if streq(name, "poke8") {
if (name == "poke8") {
let p = arg_code(e, 0); let i = arg_code(e, 1); let v = arg_code(e, 2)
let a = emit_gep_i8(p, i)
let t = emit_bind(sconcat("trunc i32 ", sconcat(v, " to i8")))
let t = emit_bind(("trunc i32 " + (v + " to i8")))
emit(" store i8 "); emit(t); emit(", ptr "); emit(a); emit("\n")
return val("0", "void")
}
if streq(name, "file_open") {
if (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")
return val(emit_bind(("call ptr @fopen(ptr " + (p + (", ptr " + (m + ")"))))), "ptr")
}
if streq(name, "file_read") or streq(name, "file_write") {
if (name == "file_read") or (name == "file_write") {
let f = arg_code(e, 0); let b = arg_code(e, 1); let n = arg_code(e, 2)
let w = emit_bind(sconcat("zext i32 ", sconcat(n, " to i64")))
let w = emit_bind(("zext i32 " + (n + " to i64")))
var fn2 = "@fread"
if streq(name, "file_write") { fn2 = "@fwrite" }
let r = emit_bind(sconcat("call i64 ", sconcat(fn2, sconcat("(ptr ", sconcat(b, sconcat(", i64 1, i64 ", sconcat(w, sconcat(", ptr ", sconcat(f, ")")))))))))
return val(emit_bind(sconcat("trunc i64 ", sconcat(r, " to i32"))), "int")
if (name == "file_write") { fn2 = "@fwrite" }
let r = emit_bind(("call i64 " + (fn2 + ("(ptr " + (b + (", i64 1, i64 " + (w + (", ptr " + (f + ")")))))))))
return val(emit_bind(("trunc i64 " + (r + " to i32"))), "int")
}
if streq(name, "file_seek") {
if (name == "file_seek") {
let f = arg_code(e, 0); let off = arg_code(e, 1); let wh = arg_code(e, 2)
let o = emit_bind(sconcat("sext i32 ", sconcat(off, " to i64")))
return val(emit_bind(sconcat("call i32 @fseek(ptr ", sconcat(f, sconcat(", i64 ", sconcat(o, sconcat(", i32 ", sconcat(wh, ")"))))))), "int")
let o = emit_bind(("sext i32 " + (off + " to i64")))
return val(emit_bind(("call i32 @fseek(ptr " + (f + (", i64 " + (o + (", i32 " + (wh + ")"))))))), "int")
}
if streq(name, "file_tell") {
if (name == "file_tell") {
let f = arg_code(e, 0)
let r = emit_bind(sconcat("call i64 @ftell(ptr ", sconcat(f, ")")))
return val(emit_bind(sconcat("trunc i64 ", sconcat(r, " to i32"))), "int")
let r = emit_bind(("call i64 @ftell(ptr " + (f + ")")))
return val(emit_bind(("trunc i64 " + (r + " to i32"))), "int")
}
if streq(name, "file_close") {
if (name == "file_close") {
let f = arg_code(e, 0)
emit(" call i32 @fclose(ptr "); emit(f); emit(")\n")
return val("0", "void")
}
if streq(name, "print_str") {
if (name == "print_str") {
let s = arg_code(e, 0)
emit(" call i32 (ptr, ...) @printf(ptr @.fmt_str, ptr "); emit(s); emit(")\n")
return val("0", "void")
}
if streq(name, "print_int") {
if (name == "print_int") {
let n = arg_code(e, 0)
emit(" call i32 (ptr, ...) @printf(ptr @.fmt_int, i32 "); emit(n); emit(")\n")
return val("0", "void")
}
if streq(name, "os_argc") { return val(emit_bind("load i32, ptr @L_argc"), "int") }
if streq(name, "os_arg") {
if (name == "os_argc") { return val(emit_bind("load i32, ptr @L_argc"), "int") }
if (name == "os_arg") {
let i = arg_code(e, 0)
let v = emit_bind("load ptr, ptr @L_argv")
let q = emit_bind(sconcat("getelementptr ptr, ptr ", sconcat(v, sconcat(", i32 ", i))))
return val(emit_bind(sconcat("load ptr, ptr ", q)), "str")
let q = emit_bind(("getelementptr ptr, ptr " + (v + (", i32 " + i))))
return val(emit_bind(("load ptr, ptr " + q)), "str")
}
if streq(name, "os_exit") {
if (name == "os_exit") {
let n = arg_code(e, 0)
emit(" call void @exit(i32 "); emit(n); emit(")\n")
emit(" unreachable\n")
g_term = true
return val("0", "void")
}
if streq(name, "file_stderr") { return val(emit_bind("load ptr, ptr @__stderrp"), "ptr") }
if streq(name, "os_system") {
if (name == "file_stderr") { return val(emit_bind("load ptr, ptr @__stderrp"), "ptr") }
if (name == "os_system") {
let c = arg_code(e, 0)
return val(emit_bind(sconcat("call i32 @system(ptr ", sconcat(c, ")"))), "int")
return val(emit_bind(("call i32 @system(ptr " + (c + ")"))), "int")
}
if streq(name, "os_getenv") {
if (name == "os_getenv") {
let n = arg_code(e, 0)
return val(emit_bind(sconcat("call ptr @getenv(ptr ", sconcat(n, ")"))), "str")
return val(emit_bind(("call ptr @getenv(ptr " + (n + ")"))), "str")
}
# bitwise ops are the operators & | ^ << >> ~ now (see emit_bin / p_mul).
if streq(name, "peek32") {
if (name == "peek32") {
let p = arg_code(e, 0); let i = arg_code(e, 1)
let g = nreg()
emit(" "); emit(g); emit(" = getelementptr inbounds i32, ptr "); emit(p); emit(", i32 "); emit(i); emit("\n")
return val(emit_bind(sconcat("load i32, ptr ", g)), "int")
return val(emit_bind(("load i32, ptr " + g)), "int")
}
if streq(name, "poke32") {
if (name == "poke32") {
let p = arg_code(e, 0); let i = arg_code(e, 1); let v = arg_code(e, 2)
let g = nreg()
emit(" "); emit(g); emit(" = getelementptr inbounds i32, ptr "); emit(p); emit(", i32 "); emit(i); emit("\n")

View file

@ -5,91 +5,91 @@
var g_windowed: bool = false # headless by default (games read stdin / dump PPM)
fn is_intrinsic2(name: ptr) -> bool {
if streq(name,"mem_free") or streq(name,"mem_copy") or streq(name,"mem_set") { return true }
if streq(name,"ptr_add") or streq(name,"read_byte") or streq(name,"write_byte") { return true }
if streq(name,"str_len") or streq(name,"os_time") { return true }
if streq(name,"peekf") or streq(name,"pokef") or streq(name,"as_fixed") or streq(name,"as_int") { return true }
if streq(name,"peekp") or streq(name,"pokep") { return true }
if streq(name,"is_windowed") or streq(name,"game_title") { return true }
if streq(name,"win_open") or streq(name,"win_poll") or streq(name,"win_present") { return true }
if streq(name,"win_running") or streq(name,"win_close") { return true }
if (name == "mem_free") or (name == "mem_copy") or (name == "mem_set") { return true }
if (name == "ptr_add") or (name == "read_byte") or (name == "write_byte") { return true }
if (name == "str_len") or (name == "os_time") { return true }
if (name == "peekf") or (name == "pokef") or (name == "as_fixed") or (name == "as_int") { return true }
if (name == "peekp") or (name == "pokep") { return true }
if (name == "is_windowed") or (name == "game_title") { return true }
if (name == "win_open") or (name == "win_poll") or (name == "win_present") { return true }
if (name == "win_running") or (name == "win_close") { return true }
return false
}
fn emit_intrinsic2(name: ptr, e: Node) -> Val {
if streq(name, "mem_free") {
if (name == "mem_free") {
let p = arg_code(e, 0); emit(" call void @free(ptr "); emit(p); emit(")\n"); return val("0", "void")
}
if streq(name, "mem_copy") {
if (name == "mem_copy") {
let d = arg_code(e, 0); let s = arg_code(e, 1); let n = arg_code(e, 2)
let w = emit_bind(sconcat("zext i32 ", sconcat(n, " to i64")))
let w = emit_bind(("zext i32 " + (n + " to i64")))
emit(" call ptr @memcpy(ptr "); emit(d); emit(", ptr "); emit(s); emit(", i64 "); emit(w); emit(")\n")
return val("0", "void")
}
if streq(name, "mem_set") {
if (name == "mem_set") {
let p = arg_code(e, 0); let v = arg_code(e, 1); let n = arg_code(e, 2)
let w = emit_bind(sconcat("zext i32 ", sconcat(n, " to i64")))
let w = emit_bind(("zext i32 " + (n + " to i64")))
emit(" call ptr @memset(ptr "); emit(p); emit(", i32 "); emit(v); emit(", i64 "); emit(w); emit(")\n")
return val("0", "void")
}
if streq(name, "ptr_add") {
if (name == "ptr_add") {
let p = arg_code(e, 0); let n = arg_code(e, 1)
let g = nreg()
emit(" "); emit(g); emit(" = getelementptr inbounds i8, ptr "); emit(p); emit(", i32 "); emit(n); emit("\n")
return val(g, "ptr")
}
if streq(name, "read_byte") { return val(emit_bind("call i32 @getchar()"), "int") }
if streq(name, "write_byte") {
if (name == "read_byte") { return val(emit_bind("call i32 @getchar()"), "int") }
if (name == "write_byte") {
let v = arg_code(e, 0); emit(" call i32 @putchar(i32 "); emit(v); emit(")\n"); return val("0", "void")
}
if streq(name, "str_len") {
if (name == "str_len") {
let s = arg_code(e, 0)
let r = emit_bind(sconcat("call i64 @strlen(ptr ", sconcat(s, ")")))
return val(emit_bind(sconcat("trunc i64 ", sconcat(r, " to i32"))), "int")
let r = emit_bind(("call i64 @strlen(ptr " + (s + ")")))
return val(emit_bind(("trunc i64 " + (r + " to i32"))), "int")
}
if streq(name, "os_time") {
if (name == "os_time") {
let r = emit_bind("call i64 @time(ptr null)")
return val(emit_bind(sconcat("trunc i64 ", sconcat(r, " to i32"))), "int")
return val(emit_bind(("trunc i64 " + (r + " to i32"))), "int")
}
if streq(name, "peekf") {
if (name == "peekf") {
let p = arg_code(e, 0); let i = arg_code(e, 1)
let g = nreg(); emit(" "); emit(g); emit(" = getelementptr inbounds i32, ptr "); emit(p); emit(", i32 "); emit(i); emit("\n")
return val(emit_bind(sconcat("load i32, ptr ", g)), "fixed")
return val(emit_bind(("load i32, ptr " + g)), "fixed")
}
if streq(name, "pokef") {
if (name == "pokef") {
let p = arg_code(e, 0); let i = arg_code(e, 1); let v = arg_code(e, 2)
let g = nreg(); emit(" "); emit(g); emit(" = getelementptr inbounds i32, ptr "); emit(p); emit(", i32 "); emit(i); emit("\n")
emit(" store i32 "); emit(v); emit(", ptr "); emit(g); emit("\n")
return val("0", "void")
}
if streq(name, "peekp") {
if (name == "peekp") {
let p = arg_code(e, 0); let i = arg_code(e, 1)
let g = nreg(); emit(" "); emit(g); emit(" = getelementptr inbounds ptr, ptr "); emit(p); emit(", i32 "); emit(i); emit("\n")
return val(emit_bind(sconcat("load ptr, ptr ", g)), "ptr")
return val(emit_bind(("load ptr, ptr " + g)), "ptr")
}
if streq(name, "pokep") {
if (name == "pokep") {
let p = arg_code(e, 0); let i = arg_code(e, 1); let v = arg_code(e, 2)
let g = nreg(); emit(" "); emit(g); emit(" = getelementptr inbounds ptr, ptr "); emit(p); emit(", i32 "); emit(i); emit("\n")
emit(" store ptr "); emit(v); emit(", ptr "); emit(g); emit("\n")
return val("0", "void")
}
if streq(name, "as_fixed") { let a = emit_expr(e.kids[0]); return val(a.code, "fixed") }
if streq(name, "as_int") { let a = emit_expr(e.kids[0]); return val(a.code, "int") }
if streq(name, "is_windowed") { if g_windowed { return val("1", "bool") }; return val("0", "bool") }
if streq(name, "game_title") { return val("@.gametitle", "str") }
if (name == "as_fixed") { let a = emit_expr(e.kids[0]); return val(a.code, "fixed") }
if (name == "as_int") { let a = emit_expr(e.kids[0]); return val(a.code, "int") }
if (name == "is_windowed") { if g_windowed { return val("1", "bool") }; return val("0", "bool") }
if (name == "game_title") { return val("@.gametitle", "str") }
# windowing hooks — declared external; only reached on the is_windowed() branch
if streq(name, "win_poll") { return val(emit_bind("call i32 @win_poll()"), "int") }
if streq(name, "win_running") { return val(emit_bind("call i32 @win_running()"), "bool") }
if streq(name, "win_open") {
if (name == "win_poll") { return val(emit_bind("call i32 @win_poll()"), "int") }
if (name == "win_running") { return val(emit_bind("call i32 @win_running()"), "bool") }
if (name == "win_open") {
let a = arg_code(e, 0); let b = arg_code(e, 1); let c = arg_code(e, 2); let d = arg_code(e, 3)
emit(" call void @win_open(i32 "); emit(a); emit(", i32 "); emit(b); emit(", i32 "); emit(c); emit(", ptr "); emit(d); emit(")\n")
return val("0", "void")
}
if streq(name, "win_present") {
if (name == "win_present") {
let a = arg_code(e, 0); let b = arg_code(e, 1); let c = arg_code(e, 2)
emit(" call void @win_present(ptr "); emit(a); emit(", i32 "); emit(b); emit(", i32 "); emit(c); emit(")\n")
return val("0", "void")
}
if streq(name, "win_close") { emit(" call void @win_close()\n"); return val("0", "void") }
if (name == "win_close") { emit(" call void @win_close()\n"); return val("0", "void") }
return val("0", "void")
}

View file

@ -4,7 +4,7 @@
fn emit_machine(st: Node) -> void {
let regv = emit_expr(st.a)
let s = emit_bind(sconcat("call i32 @fn_rt_reg(i32 ", sconcat(regv.code, ")")))
let s = emit_bind(("call i32 @fn_rt_reg(i32 " + (regv.code + ")")))
if nmach < len(mach_stk) { mach_stk[nmach] = st } else { push(mach_stk, st) }
nmach = nmach + 1
let endl = lbl("smend")
@ -12,7 +12,7 @@ fn emit_machine(st: Node) -> void {
while i < len(st.kids) {
let state = st.kids[i]
let v = emit_expr(state.b)
let c = emit_bind(sconcat("icmp eq i32 ", sconcat(s, sconcat(", ", v.code))))
let c = emit_bind(("icmp eq i32 " + (s + (", " + v.code))))
let body = lbl("sbody"); let nxt = lbl("sarm")
emit(" br i1 "); emit(c); emit(", label %"); emit(body); emit(", label %"); emit(nxt); emit("\n")
emit(body); emit(":\n"); g_term = false
@ -31,8 +31,8 @@ fn emit_become(st: Node) -> void {
let m = mach_stk[nmach - 1]
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 (target == null) { perr(sconcat("become: no state ", st.s)) }
while i < len(m.kids) { if (m.kids[i].s == st.s) { target = m.kids[i] }; i = i + 1 }
if (target == null) { perr(("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")

View file

@ -3,27 +3,27 @@
# (clear, fill_rect, reg, ...) is resolved to its rt_ function by emit_call.
fn is_math_builtin(name: ptr) -> bool {
return streq(name,"min") or streq(name,"max") or streq(name,"abs") or streq(name,"clamp")
return (name == "min") or (name == "max") or (name == "abs") or (name == "clamp")
}
fn emit_math_builtin(name: ptr, e: Node) -> Val {
if streq(name, "abs") {
if (name == "abs") {
let a = emit_expr(e.kids[0])
let c = emit_bind(sconcat("icmp slt i32 ", sconcat(a.code, ", 0")))
let n = emit_bind(sconcat("sub i32 0, ", a.code))
return val(emit_bind(sconcat("select i1 ", sconcat(c, sconcat(", i32 ", sconcat(n, sconcat(", i32 ", a.code)))))), "int")
let c = emit_bind(("icmp slt i32 " + (a.code + ", 0")))
let n = emit_bind(("sub i32 0, " + a.code))
return val(emit_bind(("select i1 " + (c + (", i32 " + (n + (", i32 " + a.code)))))), "int")
}
if streq(name, "min") or streq(name, "max") {
if (name == "min") or (name == "max") {
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1])
var op = "slt"
if streq(name, "max") { op = "sgt" }
let c = emit_bind(sconcat("icmp ", sconcat(op, sconcat(" i32 ", sconcat(a.code, sconcat(", ", b.code))))))
return val(emit_bind(sconcat("select i1 ", sconcat(c, sconcat(", i32 ", sconcat(a.code, sconcat(", i32 ", b.code)))))), "int")
if (name == "max") { op = "sgt" }
let c = emit_bind(("icmp " + (op + (" i32 " + (a.code + (", " + b.code))))))
return val(emit_bind(("select i1 " + (c + (", i32 " + (a.code + (", i32 " + b.code)))))), "int")
}
# clamp(v, lo, hi) = max(lo, min(v, hi))
let v = emit_expr(e.kids[0]); let lo = emit_expr(e.kids[1]); let hi = emit_expr(e.kids[2])
let c1 = emit_bind(sconcat("icmp slt i32 ", sconcat(v.code, sconcat(", ", hi.code))))
let t = emit_bind(sconcat("select i1 ", sconcat(c1, sconcat(", i32 ", sconcat(v.code, sconcat(", i32 ", hi.code))))))
let c2 = emit_bind(sconcat("icmp sgt i32 ", sconcat(lo.code, sconcat(", ", t))))
return val(emit_bind(sconcat("select i1 ", sconcat(c2, sconcat(", i32 ", sconcat(lo.code, sconcat(", i32 ", t)))))), "int")
let c1 = emit_bind(("icmp slt i32 " + (v.code + (", " + hi.code))))
let t = emit_bind(("select i1 " + (c1 + (", i32 " + (v.code + (", i32 " + hi.code))))))
let c2 = emit_bind(("icmp sgt i32 " + (lo.code + (", " + t))))
return val(emit_bind(("select i1 " + (c2 + (", i32 " + (lo.code + (", i32 " + t)))))), "int")
}

View file

@ -3,8 +3,8 @@
# holder points at, so growth is visible to every holder.
fn emit_sizeof(llt: ptr) -> ptr {
let p = emit_bind(sconcat("getelementptr ", sconcat(llt, ", ptr null, i32 1")))
return emit_bind(sconcat("ptrtoint ptr ", sconcat(p, " to i64")))
let p = emit_bind(("getelementptr " + (llt + ", ptr null, i32 1")))
return emit_bind(("ptrtoint ptr " + (p + " to i64")))
}
fn emit_new_struct(name: ptr) -> Val {
@ -12,7 +12,7 @@ fn emit_new_struct(name: ptr) -> Val {
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, ")")))
let obj = emit_bind(("call ptr @malloc(i64 " + (sz + ")")))
var f = 0
while f < len(s.kids) {
let fd = s.kids[f]
@ -21,7 +21,7 @@ fn emit_new_struct(name: ptr) -> Val {
emit(", ptr "); emit(obj); emit(", i32 0, i32 "); emit(itoa(f)); emit("\n")
let lt = llty(fd.ty)
var v = "0"
if streq(lt, "ptr") { v = "null" }
if (lt == "ptr") { v = "null" }
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
@ -31,7 +31,7 @@ fn emit_new_struct(name: ptr) -> Val {
fn emit_new_slice(ty: ptr) -> Val {
let sz = emit_sizeof("%LSlice")
let h = emit_bind(sconcat("call ptr @malloc(i64 ", sconcat(sz, ")")))
let h = emit_bind(("call ptr @malloc(i64 " + (sz + ")")))
let d0 = nreg(); emit(" "); emit(d0); emit(" = getelementptr inbounds %LSlice, ptr "); emit(h); emit(", i32 0, i32 0\n")
emit(" store ptr null, ptr "); emit(d0); emit("\n")
let d1 = nreg(); emit(" "); emit(d1); emit(" = getelementptr inbounds %LSlice, ptr "); emit(h); emit(", i32 0, i32 1\n")
@ -51,7 +51,7 @@ fn slice_field(h: ptr, i: int) -> ptr {
fn emit_len(e: Node) -> Val {
let s = emit_expr(e.kids[0])
let lp = slice_field(s.code, 1)
return val(emit_bind(sconcat("load i32, ptr ", lp)), "int")
return val(emit_bind(("load i32, ptr " + lp)), "int")
}
fn emit_push(e: Node) -> Val {
@ -60,30 +60,30 @@ fn emit_push(e: Node) -> Val {
let elt = llty(el)
let h = s.code
let lp = slice_field(h, 1); let cp = slice_field(h, 2); let dp = slice_field(h, 0)
let l = emit_bind(sconcat("load i32, ptr ", lp))
let c = emit_bind(sconcat("load i32, ptr ", cp))
let full = emit_bind(sconcat("icmp sge i32 ", sconcat(l, sconcat(", ", c))))
let l = emit_bind(("load i32, ptr " + lp))
let c = emit_bind(("load i32, ptr " + cp))
let full = emit_bind(("icmp sge i32 " + (l + (", " + c))))
let grow = lbl("grow"); let put = lbl("put")
emit(" br i1 "); emit(full); emit(", label %"); emit(grow); emit(", label %"); emit(put); emit("\n")
emit(grow); emit(":\n")
let dbl = emit_bind(sconcat("mul i32 ", sconcat(c, ", 2")))
let isz = emit_bind(sconcat("icmp eq i32 ", sconcat(c, ", 0")))
let nc = emit_bind(sconcat("select i1 ", sconcat(isz, sconcat(", i32 8, i32 ", dbl))))
let dbl = emit_bind(("mul i32 " + (c + ", 2")))
let isz = emit_bind(("icmp eq i32 " + (c + ", 0")))
let nc = emit_bind(("select i1 " + (isz + (", i32 8, i32 " + dbl))))
let esz = emit_sizeof(elt)
let ncw = emit_bind(sconcat("zext i32 ", sconcat(nc, " to i64")))
let bytes = emit_bind(sconcat("mul i64 ", sconcat(ncw, sconcat(", ", esz))))
let old = emit_bind(sconcat("load ptr, ptr ", dp))
let nd = emit_bind(sconcat("call ptr @realloc(ptr ", sconcat(old, sconcat(", i64 ", sconcat(bytes, ")")))))
let ncw = emit_bind(("zext i32 " + (nc + " to i64")))
let bytes = emit_bind(("mul i64 " + (ncw + (", " + esz))))
let old = emit_bind(("load ptr, ptr " + dp))
let nd = emit_bind(("call ptr @realloc(ptr " + (old + (", i64 " + (bytes + ")")))))
emit(" store ptr "); emit(nd); emit(", ptr "); emit(dp); emit("\n")
emit(" store i32 "); emit(nc); emit(", ptr "); emit(cp); emit("\n")
emit(" br label %"); emit(put); emit("\n")
emit(put); emit(":\n")
let v = emit_expr(e.kids[1])
let data = emit_bind(sconcat("load ptr, ptr ", dp))
let data = emit_bind(("load ptr, ptr " + dp))
let slot = nreg()
emit(" "); emit(slot); emit(" = getelementptr inbounds "); emit(elt); emit(", ptr "); emit(data); emit(", i32 "); emit(l); emit("\n")
emit(" store "); emit(elt); emit(" "); emit(v.code); emit(", ptr "); emit(slot); emit("\n")
let l1 = emit_bind(sconcat("add i32 ", sconcat(l, ", 1")))
let l1 = emit_bind(("add i32 " + (l + ", 1")))
emit(" store i32 "); emit(l1); emit(", ptr "); emit(lp); emit("\n")
return val("0", "void")
}

View file

@ -4,7 +4,7 @@
fn find_arch_id(name: ptr) -> int {
var i = 0; var n = 1
while i < len(prog) { if prog[i].kind == N_ARCH { if streq(prog[i].s, name) { return n }; n = n + 1 }; i = i + 1 }
while i < len(prog) { if prog[i].kind == N_ARCH { if (prog[i].s == name) { return n }; n = n + 1 }; i = i + 1 }
return 0
}
@ -18,16 +18,16 @@ fn emit_query(st: Node) -> void {
let cond = lbl("qcond"); let body = lbl("qbody"); let nxt = lbl("qnext"); let endl = lbl("qend")
emit(" br label %"); emit(cond); emit("\n")
emit(cond); emit(":\n")
let i0 = emit_bind(sconcat("load i32, ptr ", ip))
let i0 = emit_bind(("load i32, ptr " + ip))
let ec = emit_bind("load i32, ptr @L_entc")
let lt = emit_bind(sconcat("icmp slt i32 ", sconcat(i0, sconcat(", ", ec))))
let lt = emit_bind(("icmp slt i32 " + (i0 + (", " + ec))))
emit(" br i1 "); emit(lt); emit(", label %"); emit(body); emit(", label %"); emit(endl); emit("\n")
emit(body); emit(":\n"); g_term = false
let i1 = emit_bind(sconcat("load i32, ptr ", ip))
let i1 = emit_bind(("load i32, ptr " + ip))
# alive?
let ap = nreg(); emit(" "); emit(ap); emit(" = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_alive, i32 0, i32 "); emit(i1); emit("\n")
let al = emit_bind(sconcat("load i32, ptr ", ap))
let alc = emit_bind(sconcat("icmp ne i32 ", sconcat(al, ", 0")))
let al = emit_bind(("load i32, ptr " + ap))
let alc = emit_bind(("icmp ne i32 " + (al + ", 0")))
let ka = lbl("qa")
emit(" br i1 "); emit(alc); emit(", label %"); emit(ka); emit(", label %"); emit(nxt); emit("\n")
emit(ka); emit(":\n")
@ -41,15 +41,15 @@ fn emit_query(st: Node) -> void {
var ok = "0"
if ak > 0 {
let kp = nreg(); emit(" "); emit(kp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 "); emit(i1); emit("\n")
let kv = emit_bind(sconcat("load i32, ptr ", kp))
let kok = emit_bind(sconcat("icmp eq i32 ", sconcat(kv, sconcat(", ", itoa(ak)))))
let mev = emit_bind(sconcat("load i32, ptr @ME_", tm.s)) # model-enabled flag
let meok = emit_bind(sconcat("icmp ne i32 ", sconcat(mev, ", 0")))
ok = emit_bind(sconcat("and i1 ", sconcat(kok, sconcat(", ", meok))))
let kv = emit_bind(("load i32, ptr " + kp))
let kok = emit_bind(("icmp eq i32 " + (kv + (", " + itoa(ak)))))
let mev = emit_bind(("load i32, ptr @ME_" + tm.s)) # model-enabled flag
let meok = emit_bind(("icmp ne i32 " + (mev + ", 0")))
ok = emit_bind(("and i1 " + (kok + (", " + meok))))
} else {
let hp = nreg(); emit(" "); emit(hp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(tm.s); emit(", i32 0, i32 "); emit(i1); emit("\n")
let hv = emit_bind(sconcat("load i8, ptr ", hp))
ok = emit_bind(sconcat("icmp ne i8 ", sconcat(hv, ", 0")))
let hv = emit_bind(("load i8, ptr " + hp))
ok = emit_bind(("icmp ne i8 " + (hv + ", 0")))
}
let keep = lbl("qt")
emit(" br i1 "); emit(ok); emit(", label %"); emit(keep); emit(", label %"); emit(nxt); emit("\n")
@ -79,7 +79,7 @@ fn emit_query(st: Node) -> void {
# optional where-clause
if (st.b != null) {
let w = emit_expr(st.b)
let wc = emit_bind(sconcat("icmp ne i32 ", sconcat(w.code, ", 0")))
let wc = emit_bind(("icmp ne i32 " + (w.code + ", 0")))
let kw = lbl("qw")
emit(" br i1 "); emit(wc); emit(", label %"); emit(kw); emit(", label %"); emit(nxt); emit("\n")
emit(kw); emit(":\n")
@ -92,8 +92,8 @@ fn emit_query(st: Node) -> void {
if not g_term { emit(" br label %"); emit(nxt); emit("\n") }
emit(nxt); emit(":\n")
let i2 = emit_bind(sconcat("load i32, ptr ", ip))
let i3 = emit_bind(sconcat("add i32 ", sconcat(i2, ", 1")))
let i2 = emit_bind(("load i32, ptr " + ip))
let i3 = emit_bind(("add i32 " + (i2 + ", 1")))
store_at("i32", i3, ip)
emit(" br label %"); emit(cond); emit("\n")
emit(endl); emit(":\n"); g_term = false

View file

@ -6,7 +6,7 @@
var g_iok: int = 0
fn emit_io(fn2: ptr, p: ptr, bytes: ptr) -> void {
let r = sconcat("%io", itoa(g_iok)); g_iok = g_iok + 1
let r = (("%io") + itoa(g_iok)); g_iok = g_iok + 1
emit(" "); emit(r); emit(" = call i64 @"); emit(fn2); emit("(ptr "); emit(p); emit(", i64 1, i64 "); emit(bytes); emit(", ptr %f)\n")
}
@ -20,16 +20,16 @@ fn emit_snapshot_blocks(fn2: ptr) -> void {
emit_io(fn2, "@L_freelist", "%nalive")
emit_io(fn2, "@L_kind", "%nalive")
var i = 0
while i < len(prog) { if prog[i].kind == N_VAR { emit_io(fn2, sconcat("@g_", prog[i].s), "4") }; i = i + 1 }
while i < len(prog) { if prog[i].kind == N_VAR { emit_io(fn2, ("@g_" + prog[i].s), "4") }; i = i + 1 }
var ci = 0
i = 0
while i < len(prog) {
if prog[i].kind == N_COMP {
let c = prog[i].s
let csz = sconcat("%csz", itoa(ci))
let csz = (("%csz") + itoa(ci))
emit(" "); emit(csz); emit(" = ptrtoint ptr getelementptr (%Cmp_"); emit(c); emit(", ptr null, i32 "); emit(me); emit(") to i64\n")
emit_io(fn2, sconcat("@S_", c), csz)
emit_io(fn2, sconcat("@H_", c), me)
emit_io(fn2, ("@S_" + c), csz)
emit_io(fn2, ("@H_" + c), me)
ci = ci + 1
}
i = i + 1

View file

@ -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 (c == null) { perr(sconcat("spawn: unknown property ", comp)) }
if (c == null) { perr(("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()
@ -17,7 +17,7 @@ fn emit_init_component(e: ptr, comp: ptr, rec: Node) -> void {
emit(" "); emit(addr); emit(" = getelementptr inbounds %Cmp_"); emit(comp); emit(", ptr "); emit(slot); emit(", i32 0, i32 "); emit(itoa(f)); emit("\n")
let lt = llty(fd.ty)
var v = "0"
if streq(lt, "ptr") { v = "null" }
if (lt == "ptr") { v = "null" }
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
@ -78,7 +78,7 @@ fn emit_spawn(st: Node) -> void {
let cn = arch.kids[c].s
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 }
while i < len(st.kids) { if (st.kids[i].s == cn) { rec = st.kids[i].a }; i = i + 1 }
emit_init_component(e, cn, rec)
c = c + 1
}
@ -101,11 +101,11 @@ fn emit_despawn(st: Node) -> void {
if len(g_ondespawn) > 0 {
let me = itoa(MAX_ENT)
let kp = nreg(); emit(" "); emit(kp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 "); emit(v.code); emit("\n")
let kind = emit_bind(sconcat("load i32, ptr ", kp))
let kind = emit_bind(("load i32, ptr " + kp))
var i = 0
while i < len(g_ondespawn) {
let mname = g_ondespawn[i].s
let c = emit_bind(sconcat("icmp eq i32 ", sconcat(kind, sconcat(", ", itoa(find_arch_id(mname))))))
let c = emit_bind(("icmp eq i32 " + (kind + (", " + itoa(find_arch_id(mname))))))
let yes = lbl("dh"); let no = lbl("dhn")
emit(" br i1 "); emit(c); emit(", label %"); emit(yes); emit(", label %"); emit(no); emit("\n")
emit(yes); emit(":\n call void @on_despawn_"); emit(mname); emit("(i32 "); emit(v.code); emit(")\n")

View file

@ -25,13 +25,13 @@ fn emit_assign(st: Node) -> void {
if li >= 0 {
# a `let` binding is immutable: rebinding it is an error (mutating THROUGH
# it — `x.field = …`, `x[i] = …` — is fine, and lands in the branches below).
if loc_mut[li] == 0 { perr(sconcat("cannot assign to immutable '", sconcat(t.s, sconcat("' (declared with let; use var) — line ", itoa(st.line))))) }
if loc_mut[li] == 0 { perr(("cannot assign to immutable '" + (t.s + ("' (declared with let; use var) — line " + itoa(st.line))))) }
addr = loc_reg[li]; ty = loc_ty[li]
}
else {
let g = find_global(t.s)
if (g == null) { perr(sconcat("assign to unknown ", t.s)) }
addr = sconcat("@g_", t.s); ty = g.ty
if (g == null) { perr(("assign to unknown " + t.s)) }
addr = ("@g_" + t.s); ty = g.ty
}
} else {
if t.kind == E_MEMBER { addr = emit_member_addr(t); ty = g_addr_ty }
@ -41,20 +41,20 @@ fn emit_assign(st: Node) -> void {
let lt = llty(ty)
let rv = emit_expr(st.b)
var v = rv.code
if not streq(st.s, "=") {
let cur = emit_bind(sconcat("load ", sconcat(lt, sconcat(", ptr ", addr))))
if not (st.s == "=") {
let cur = emit_bind(("load " + (lt + (", ptr " + addr))))
var opc = "add"
if streq(st.s, "-=") { opc = "sub" }
if streq(st.s, "*=") { opc = "mul" }
if streq(st.s, "/=") { opc = "sdiv" }
v = emit_bind(sconcat(opc, sconcat(" i32 ", sconcat(cur, sconcat(", ", v)))))
if (st.s == ("-=")) { opc = "sub" }
if (st.s == ("*=")) { opc = "mul" }
if (st.s == ("/=")) { opc = "sdiv" }
v = emit_bind((opc + (" i32 " + (cur + (", " + v)))))
}
store_at(lt, v, addr)
}
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 cc = emit_bind(("icmp ne i32 " + (c.code + ", 0")))
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") }
@ -76,7 +76,7 @@ fn emit_while(st: Node) -> void {
emit(" br label %"); emit(cl); emit("\n")
emit(cl); emit(":\n")
let c = emit_expr(st.a)
let cc = emit_bind(sconcat("icmp ne i32 ", sconcat(c.code, ", 0")))
let cc = emit_bind(("icmp ne i32 " + (c.code + ", 0")))
emit(" br i1 "); emit(cc); emit(", label %"); emit(bl); emit(", label %"); emit(en); emit("\n")
emit(bl); emit(":\n"); g_term = false
loop_push(cl, en)
@ -94,9 +94,9 @@ fn emit_for(st: Node) -> void {
let cl = lbl("fcond"); let bl = lbl("fbody"); let ct = lbl("fcont"); let en = lbl("fend")
emit(" br label %"); emit(cl); emit("\n")
emit(cl); emit(":\n")
let iv = emit_bind(sconcat("load i32, ptr ", slot))
let iv = emit_bind(("load i32, ptr " + slot))
let hi = emit_expr(st.b)
let cc = emit_bind(sconcat("icmp slt i32 ", sconcat(iv, sconcat(", ", hi.code))))
let cc = emit_bind(("icmp slt i32 " + (iv + (", " + hi.code))))
emit(" br i1 "); emit(cc); emit(", label %"); emit(bl); emit(", label %"); emit(en); emit("\n")
emit(bl); emit(":\n"); g_term = false
loop_push(ct, en)
@ -104,8 +104,8 @@ fn emit_for(st: Node) -> void {
loop_pop()
if not g_term { emit(" br label %"); emit(ct); emit("\n") }
emit(ct); emit(":\n")
let i2 = emit_bind(sconcat("load i32, ptr ", slot))
let i3 = emit_bind(sconcat("add i32 ", sconcat(i2, ", 1")))
let i2 = emit_bind(("load i32, ptr " + slot))
let i3 = emit_bind(("add i32 " + (i2 + ", 1")))
store_at("i32", i3, slot)
emit(" br label %"); emit(cl); emit("\n")
emit(en); emit(":\n"); g_term = false
@ -122,7 +122,7 @@ fn emit_return(st: Node) -> void {
fn arm_is_default(arm: Node) -> bool {
var p = 0
while p < len(arm.kids) { if arm.kids[p].kind == E_ID and streq(arm.kids[p].s, "_") { return true }; p = p + 1 }
while p < len(arm.kids) { if arm.kids[p].kind == E_ID and (arm.kids[p].s == "_") { return true }; p = p + 1 }
return false
}
@ -140,9 +140,9 @@ fn emit_match(st: Node) -> void {
var p = 0
while p < len(arm.kids) {
let pv = emit_expr(arm.kids[p])
let c = emit_bind(sconcat("icmp eq i32 ", sconcat(sv.code, sconcat(", ", pv.code))))
let c = emit_bind(("icmp eq i32 " + (sv.code + (", " + pv.code))))
if first { acc = c; first = false }
else { acc = emit_bind(sconcat("or i1 ", sconcat(acc, sconcat(", ", c)))) }
else { acc = emit_bind(("or i1 " + (acc + (", " + c)))) }
p = p + 1
}
let bodyl = lbl("mbody"); let nextl = lbl("marm")
@ -167,7 +167,7 @@ fn emit_stmt(st: Node) -> void {
let slot = emit_alloca(llty(ty))
if (st.a == null) {
var z = "0"
if streq(llty(ty), "ptr") { z = "null" }
if (llty(ty) == "ptr") { z = "null" }
store_at(llty(ty), z, slot)
} else { let v = emit_expr(st.a); store_at(llty(ty), v.code, slot) }
loc_push(st.s, slot, ty)

View file

@ -9,14 +9,14 @@ var ui_roots: []int # first-widget index of each ui block
fn ui_wtype(w: Node) -> int {
let s = w.s
if streq(s, "panel") { return 0 }; if streq(s, "col") { return 1 }; if streq(s, "row") { return 2 }
if streq(s, "label") { return 3 }; if streq(s, "button") { return 4 }
if streq(s, "image") { return 5 }; if streq(s, "spacer") { return 6 }
if (s == "panel") { return 0 }; if (s == "col") { return 1 }; if (s == "row") { return 2 }
if (s == "label") { return 3 }; if (s == "button") { return 4 }
if (s == "image") { return 5 }; if (s == "spacer") { return 6 }
return 0
}
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 }
while i < len(w.b.kids) { if (w.b.kids[i].s == key) { return w.b.kids[i].a }; i = i + 1 }
return null
}
fn ui_flatten(w: Node, parent: int) -> void {
@ -45,13 +45,13 @@ fn ui_index_of(nm: ptr) -> int {
let u = 0; var bi = 0
var i = 0
while i < len(prog) {
if prog[i].kind == N_UI and prog[i].ival == 1 { if streq(prog[i].s, s) { return ui_roots[bi] }; bi = bi + 1 }
if prog[i].kind == N_UI and prog[i].ival == 1 { if (prog[i].s == s) { return ui_roots[bi] }; bi = bi + 1 }
i = i + 1
}
i = 0
while i < len(uiw) {
let idp = ui_prop(uiw[i], "id")
if (idp != null) { if idp.kind == E_ID and streq(idp.s, s) { return i } }
if (idp != null) { if idp.kind == E_ID and (idp.s == s) { return i } }
i = i + 1
}
return 0
@ -64,10 +64,10 @@ fn ll_ui_set(idx: int, key: int, val: ptr) -> void {
emit(" call void @fn_rt_ui_set(i32 "); emit(itoa(idx)); emit(", i32 "); emit(itoa(key)); emit(", i32 "); emit(val); emit(")\n")
}
fn ui_prop_key(k: ptr) -> int {
if streq(k,"w") { return 2 }; if streq(k,"h") { return 3 }; if streq(k,"x") { return 4 }; if streq(k,"y") { return 5 }
if streq(k,"pad") { return 7 }; if streq(k,"gap") { return 8 }; if streq(k,"bg") { return 9 }; if streq(k,"fg") { return 10 }
if streq(k,"border") { return 11 }; if streq(k,"grow") { return 13 }; if streq(k,"font") { return 14 }; if streq(k,"size") { return 15 }
if streq(k,"inset") { return 16 }; if streq(k,"focus") { return 17 }
if (k == "w") { return 2 }; if (k == "h") { return 3 }; if (k == "x") { return 4 }; if (k == "y") { return 5 }
if (k == "pad") { return 7 }; if (k == "gap") { return 8 }; if (k == "bg") { return 9 }; if (k == "fg") { return 10 }
if (k == "border") { return 11 }; if (k == "grow") { return 13 }; if (k == "font") { return 14 }; if (k == "size") { return 15 }
if (k == "inset") { return 16 }; if (k == "focus") { return 17 }
return 0 - 1
}
@ -91,18 +91,18 @@ fn emit_ui_build() -> void {
let pr = w.b.kids[p]
let k = pr.s
let v = pr.a
if streq(k, "id") { p = p + 1; continue }
if streq(k, "text") {
if (k == "id") { p = p + 1; continue }
if (k == "text") {
let sv = emit_expr(v)
emit(" call void @fn_rt_ui_static_text(i32 "); emit(itoa(i)); emit(", ptr "); emit(sv.code); emit(")\n")
} else { if streq(k, "skin") or streq(k, "image") {
} else { if (k == "skin") or (k == "image") {
let sv = emit_expr(v)
let r = emit_bind(sconcat("call i32 @fn_rt_image_load(ptr ", sconcat(sv.code, ")")))
if streq(k, "skin") { ll_ui_set(i, 20, r) } else { ll_ui_set(i, 19, r) }
} else { if streq(k, "align") {
let r = emit_bind(("call i32 @fn_rt_image_load(ptr " + (sv.code + ")")))
if (k == "skin") { ll_ui_set(i, 20, r) } else { ll_ui_set(i, 19, r) }
} else { if (k == "align") {
if v.kind == E_ID {
var a = 0
if streq(v.s, "center") { a = 1 }; if streq(v.s, "end") { a = 2 }
if (v.s == "center") { a = 1 }; if (v.s == "end") { a = 2 }
ll_ui_set(i, 12, itoa(a))
} else { let vv = emit_expr(v); ll_ui_set(i, 12, vv.code) }
} else {

File diff suppressed because it is too large Load diff

View file

@ -26,7 +26,7 @@ fn base_name(path: ptr) -> ptr {
fn strip_ludic(name: ptr) -> ptr {
let n = slen(name)
if n > 6 {
if streq(substr(name, n - 6, 6), ".ludic") { return substr(name, 0, n - 6) }
if (substr(name, n - 6, 6) == ".ludic") { return substr(name, 0, n - 6) }
}
return name
}
@ -43,7 +43,7 @@ fn ensure_slash(d: ptr) -> ptr {
let n = slen(d)
if n == 0 { return d }
if peek8(d, n - 1) == 47 { return d }
return sconcat(d, "/")
return (d + ("/"))
}
fn die(msg: ptr) -> void {
@ -61,23 +61,23 @@ entry {
var run = false # compile then execute the result
# multi-call: invoked as `ludic` -> run mode by default
if streq(base_name(os_arg(0)), "ludic") { run = true }
if (base_name(os_arg(0)) == "ludic") { run = true }
var ai = 1
while ai < os_argc() {
let a = os_arg(ai)
if streq(a, "--windowed") { want = 1 }
else { if streq(a, "--headless") { want = 2 }
else { if streq(a, "--emit-llvm") { emit_ir = true }
else { if streq(a, "--fmt") { fmt = true }
else { if streq(a, "--save-temps") { save = true }
else { if streq(a, "--run") { run = true }
else { if streq(a, "-o") { ai = ai + 1; if ai < os_argc() { out = os_arg(ai) } }
if (a == ("--windowed")) { want = 1 }
else { if (a == ("--headless")) { want = 2 }
else { if (a == ("--emit-llvm")) { emit_ir = true }
else { if (a == ("--fmt")) { fmt = true }
else { if (a == ("--save-temps")) { save = true }
else { if (a == ("--run")) { run = true }
else { if (a == ("-o")) { ai = ai + 1; if ai < os_argc() { out = os_arg(ai) } }
else {
# an unknown flag is ignored (with a note) rather than mistaken for the
# input file — '-' is ASCII 45
if peek8(a, 0) == 45 {
let m = sconcat("ludicc: ignoring unknown flag ", sconcat(a, "\n"))
let m = ("ludicc: ignoring unknown flag " + (a + "\n"))
file_write(file_stderr(), m, slen(m))
} else { path = a }
} } } } } } }
@ -117,14 +117,14 @@ entry {
# a run with no explicit -o lands in a temp file
if run and (out == null) {
out = sconcat("/tmp/ludic-run-", strip_ludic(base_name(path)))
out = (("/tmp/ludic-run-") + strip_ludic(base_name(path)))
}
# make the output directory if the user asked for e.g. bin/app
let odir = dir_of(out)
if slen(odir) > 0 { os_system(sconcat("mkdir -p ", odir)) }
if slen(odir) > 0 { os_system((("mkdir -p ") + odir)) }
let ll = sconcat(out, ".ll")
let ll = (out + ".ll")
if not ir_flush(ll) { die("ludicc: cannot write IR\n") }
let cc = getenv_or("LUDIC_CC", "clang")
@ -134,15 +134,15 @@ entry {
# platform layer and the Cocoa framework.
# -Wno-override-module: our IR carries an explicit target triple, which clang
# would otherwise warn about on every build.
var cmd = sconcat(cc, sconcat(" -O2 -Wno-override-module ", ll))
var cmd = (cc + ((" -O2 -Wno-override-module ") + ll))
if g_windowed {
let cocoa = path_join(home, "runtime/native/cocoa.ll")
cmd = sconcat(cmd, sconcat(" ", sconcat(cocoa, " -framework Cocoa -Wl,-rpath,@loader_path")))
cmd = (cmd + (" " + (cocoa + (" -framework Cocoa -Wl,-rpath,@loader_path"))))
}
cmd = sconcat(cmd, sconcat(" -o ", out))
cmd = (cmd + ((" -o ") + out))
let rc = os_system(cmd)
if not save { os_system(sconcat("rm -f ", ll)) }
if not save { os_system((("rm -f ") + ll)) }
if rc != 0 { die("ludicc: link failed\n") }
# run mode: execute the binary we just built and forward its exit code

View file

@ -8,8 +8,8 @@ var g_game_name: ptr # the `game`/`module` name
fn cur() -> Tok { return toks[pi] }
fn pk(o: int) -> Tok { return toks[pi + o] }
fn is_op(v: ptr) -> bool { let t = toks[pi]; return t.kind == TK_OP and streq(t.text, v) }
fn is_id(v: ptr) -> bool { let t = toks[pi]; return t.kind == TK_ID and streq(t.text, v) }
fn is_op(v: ptr) -> bool { let t = toks[pi]; return t.kind == TK_OP and (t.text == v) }
fn is_id(v: ptr) -> bool { let t = toks[pi]; return t.kind == TK_ID and (t.text == v) }
fn is_kw(v: ptr) -> bool { return is_id(v) }
fn perr(msg: ptr) -> void {
@ -60,10 +60,10 @@ fn p_primary() -> Node {
if t.kind == TK_FLOAT { let n = node(E_FLOAT); n.ival = t.ival; pi = pi + 1; return n }
if t.kind == TK_STR { let n = node(E_STR); n.s = t.text; pi = pi + 1; return n }
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 }
if (t.text == "true") { let n = node(E_BOOL); n.ival = 1; pi = pi + 1; return n }
if (t.text == "false") { let n = node(E_BOOL); n.ival = 0; pi = pi + 1; return n }
if (t.text == "null") { pi = pi + 1; return node(E_NULL) }
if (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
}
if is_op("(") { pi = pi + 1; skipnl(); let e = expr(); skipnl(); eat_op(")"); return e }
@ -153,19 +153,19 @@ fn block() -> Node {
fn stmt() -> Node {
let t = toks[pi]
if t.kind == TK_ID {
if streq(t.text, "let") or streq(t.text, "var") {
var mut = 0; if streq(t.text, "var") { mut = 1 } # let = immutable, var = mutable
if (t.text == "let") or (t.text == "var") {
var mut = 0; if (t.text == "var") { mut = 1 } # let = immutable, var = mutable
pi = pi + 1; let n = node(S_LET); n.ival = mut; n.line = toks[pi].line; n.s = eat_id()
if is_op(":") { pi = pi + 1; n.ty = ptype() }
if is_op("=") { pi = pi + 1; n.a = expr() }
return n
}
if streq(t.text, "return") {
if (t.text == "return") {
pi = pi + 1; let n = node(S_RETURN)
if toks[pi].kind != TK_NL and not is_op("}") { n.a = expr() }
return n
}
if streq(t.text, "if") {
if (t.text == "if") {
pi = pi + 1; let n = node(S_IF); n.a = expr(); n.b = block()
let save = pi; skipnl() # peek past newlines for a trailing `else`
if is_id("else") { pi = pi + 1; skipnl()
@ -173,8 +173,8 @@ fn stmt() -> Node {
else { pi = save } # no else: keep the separator for block()'s Rule-B check
return n
}
if streq(t.text, "while") { pi = pi + 1; let n = node(S_WHILE); n.a = expr(); n.b = block(); return n }
if streq(t.text, "for") {
if (t.text == "while") { pi = pi + 1; let n = node(S_WHILE); n.a = expr(); n.b = block(); return n }
if (t.text == "for") {
pi = pi + 1
if is_op("(") { return parse_query_for() }
let n = node(S_FOR); n.s = eat_id()
@ -182,8 +182,8 @@ fn stmt() -> Node {
n.a = expr(); eat_op(".."); n.b = expr(); n.c = block()
return n
}
if streq(t.text, "spawn") { return parse_spawn() }
if streq(t.text, "machine") {
if (t.text == "spawn") { return parse_spawn() }
if (t.text == "machine") {
pi = pi + 1; let n = node(S_MACHINE); n.a = expr(); skipnl(); eat_op("{")
var sidx = 0 # states auto-number by declaration order
while true { skipnl(); if is_op("}") { break }
@ -195,17 +195,17 @@ fn stmt() -> Node {
push(n.kids, s); sidx = sidx + 1 }
eat_op("}"); return n
}
if streq(t.text, "become") { pi = pi + 1; let n = node(S_BECOME); n.s = eat_id(); return n }
if streq(t.text, "despawn") { pi = pi + 1; let n = node(S_DESPAWN); n.a = expr(); return n }
if streq(t.text, "enable") or streq(t.text, "disable") {
var en = 0; if streq(t.text, "enable") { en = 1 }
if (t.text == "become") { pi = pi + 1; let n = node(S_BECOME); n.s = eat_id(); return n }
if (t.text == "despawn") { pi = pi + 1; let n = node(S_DESPAWN); n.a = expr(); return n }
if (t.text == "enable") or (t.text == "disable") {
var en = 0; if (t.text == "enable") { en = 1 }
pi = pi + 1; let n = node(S_TOGGLE); n.ival = en; n.s = eat_id() # `enable P on e` / `disable Model` / `disable Handler`
if is_id("on") { pi = pi + 1; n.a = expr() } # property on an entity
return n
}
if streq(t.text, "break") { pi = pi + 1; return node(S_BREAK) }
if streq(t.text, "continue") { pi = pi + 1; return node(S_CONTINUE) }
if streq(t.text, "match") {
if (t.text == "break") { pi = pi + 1; return node(S_BREAK) }
if (t.text == "continue") { pi = pi + 1; return node(S_CONTINUE) }
if (t.text == "match") {
pi = pi + 1; let n = node(S_MATCH); n.a = expr(); skipnl(); eat_op("{")
while true {
skipnl(); if is_op("}") { break }
@ -259,7 +259,7 @@ fn dir_of(path: ptr) -> ptr {
}
fn path_join(dir: ptr, rel: ptr) -> ptr {
if peek8(rel, 0) == 47 { return rel } # absolute
return sconcat(dir, rel)
return (dir + rel)
}
var loaded_paths: []ptr
@ -267,7 +267,7 @@ var cur_dir: ptr
fn already_loaded(full: ptr) -> bool {
var i = 0
while i < len(loaded_paths) { if streq(loaded_paths[i], full) { return true }; i = i + 1 }
while i < len(loaded_paths) { if (loaded_paths[i] == full) { return true }; i = i + 1 }
return false
}
@ -285,15 +285,15 @@ fn parse_one_decl() -> void {
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 }
else { if streq(a, "Queries") { qspec = parse_queries_anno() } # @Queries(these: [...], on: ...)
else { if streq(a, "OnSpawn") { eat_op("("); onspawn_model = eat_id(); eat_op(")") }
else { if streq(a, "OnDespawn") { eat_op("("); ondespawn_model = eat_id(); eat_op(")") }
else { if streq(a, "OnAttach") { eat_op("("); onattach_prop = eat_id(); eat_op(")") }
else { if streq(a, "OnEnable") { eat_op("("); onenable_prop = eat_id(); eat_op(")") }
else { if streq(a, "OnDisable") { eat_op("("); ondisable_prop = eat_id(); eat_op(")") }
else { if streq(a, "OnStart") { hook_phase = "Start" } # boot
else { if streq(a, "OnQuit") { hook_phase = "OnQuit" } # shutdown
if (a == "export") { is_export = true }
else { if (a == "Queries") { qspec = parse_queries_anno() } # @Queries(these: [...], on: ...)
else { if (a == "OnSpawn") { eat_op("("); onspawn_model = eat_id(); eat_op(")") }
else { if (a == "OnDespawn") { eat_op("("); ondespawn_model = eat_id(); eat_op(")") }
else { if (a == "OnAttach") { eat_op("("); onattach_prop = eat_id(); eat_op(")") }
else { if (a == "OnEnable") { eat_op("("); onenable_prop = eat_id(); eat_op(")") }
else { if (a == "OnDisable") { eat_op("("); ondisable_prop = eat_id(); eat_op(")") }
else { if (a == "OnStart") { hook_phase = "Start" } # boot
else { if (a == "OnQuit") { hook_phase = "OnQuit" } # shutdown
else { if is_op("(") { var d = 0 # any other @anno(args) — parsed and skipped
while true { if is_op("(") { d = d + 1 }; if is_op(")") { d = d - 1 }; pi = pi + 1; if d == 0 { break } } } } } } } } } } } }
skipnl()
@ -337,7 +337,7 @@ fn do_import(rel: ptr) -> void {
if already_loaded(full) { return }
push(loaded_paths, full)
let src = read_file(full)
if (src == null) { perr(sconcat("cannot open import ", full)) }
if (src == null) { perr(("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

View file

@ -6,7 +6,7 @@ fn parse_component() -> Node {
pi = pi + 1; let n = node(N_COMP); n.s = eat_id(); skipnl(); eat_op("{")
while true { skipnl(); if is_op("}") { break }
var is_computed = false
if is_op("@") { pi = pi + 1; let ann = eat_id(); if streq(ann, "Computed") { is_computed = true }; skipnl() }
if is_op("@") { pi = pi + 1; let ann = eat_id(); if (ann == "Computed") { is_computed = true }; skipnl() }
let f = node(N_FIELD); f.s = eat_id(); eat_op(":"); f.ty = ptype()
if is_op("=") { pi = pi + 1; f.a = expr() }
if is_computed { register_computed(n.s, f.s, f.ty, f.a) } # derived: no storage
@ -104,7 +104,7 @@ fn parse_queries_anno() -> Node {
skipnl()
if is_op(")") { break }
let key = eat_id(); eat_op(":")
if streq(key, "these") {
if (key == "these") {
eat_op("["); skipnl()
while not is_op("]") {
let pname = eat_id()
@ -116,7 +116,7 @@ fn parse_queries_anno() -> Node {
skipnl()
}
eat_op("]")
} else { if streq(key, "on") {
} else { if (key == "on") {
let mname = eat_id(); let t = node(E_ID); t.s = mname; t.ival = 1; push(terms.kids, t) # {Model} tag
} else { expr() } } # unknown key: skip its value
if is_op(",") { pi = pi + 1 }
@ -179,7 +179,7 @@ fn parse_extern() -> Node {
fn parse_widget() -> Node {
let w = node(N_UI); w.s = eat_id() # widget type name
w.b = node(N_BLOCK) # b.kids = props (E_FINIT)
while toks[pi].kind == TK_ID and toks[pi + 1].kind == TK_OP and streq(toks[pi + 1].text, ":") {
while toks[pi].kind == TK_ID and toks[pi + 1].kind == TK_OP and (toks[pi + 1].text == ":") {
let pr = node(E_FINIT); pr.s = eat_id(); eat_op(":"); pr.a = expr(); push(w.b.kids, pr) # widget prop `key: value` (Rule A)
}
skipnl()

View file

@ -2,18 +2,6 @@
# slicing out substrings, and integer<->text. All over raw NUL-terminated
# byte buffers reached with peek8/poke8.
fn streq(a: ptr, b: ptr) -> bool {
var i = 0
while true {
let ca = peek8(a, i)
let cb = peek8(b, i)
if ca != cb { return false }
if ca == 0 { return true }
i = i + 1
}
return false
}
fn slen(s: ptr) -> int {
var n = 0
while peek8(s, n) != 0 { n = n + 1 }

View file

@ -66,6 +66,11 @@ if ./selfhost/game-build.sh build/ludicc examples/toggle.ludic "/tmp/ludic_tog"
&& [ "$(/tmp/ludic_tog </dev/null | tr '\n' ' ')" = "6 0 7 1 0 " ]; then
ok "toggle.ludic (enable/disable + @OnDisable/@OnEnable across property/model)"
else bad "toggle: $(tail -1 /tmp/tog.out)"; fi
# String operators: ==/!= compare by content, + concatenates (str_* prelude).
if ./selfhost/game-build.sh build/ludicc examples/strings.ludic "/tmp/ludic_str" >/tmp/str.out 2>&1 \
&& [ "$(/tmp/ludic_str </dev/null | tr '\n' ' ')" = "1 2 3 4 5 " ]; then
ok "strings.ludic (str ==/!=/+ operators)"
else bad "strings: $(tail -1 /tmp/str.out)"; fi
# --- Toolchain-agent CLI smoke tests append below this line ---
echo "== self-hosted front-end binaries (ludicc / ludic) =="
# The two commands are one multi-call native binary built from the seed with