# emit_call.ludic — call lowering: namespaced builtins (Screen.*/Random.*/Input.* …), ordinary/user call emission, and the top-level emit_expr dispatch. Split out of emit_expr.ludic (concern: calls & expression dispatch, vs. emit_expr.ludic's operators/binary/coercion machinery). # ---- namespaced builtins: Screen.* / Random.* / Input.* -------------------- # The game-facing API reads as `subject.action(...)`. Each method maps to a bare # runtime builtin plus the parameter labels callers may use as named arguments; # after reordering we rewrite the callee to that bare name and fall back into the # ordinary builtin path (which resolves it to its rt_ function). function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val { # Math.* is computed inline (deterministic fixed-point), not routed through a # bare rt_ name — so `floor`/`round`/`lerp` never leak into the bare namespace. if (ns == "Math") { if is_math_ns(meth) { return emit_math_ns(meth, e) } perr(`unknown builtin Math.{meth}`) } if (ns == "Text") { if is_text_ns(meth) { return emit_text_ns(meth, e) } perr(`unknown builtin Text.{meth}`) } if (ns == "List") { if is_list_ns(meth) { return emit_list_ns(meth, e) } perr(`unknown builtin List.{meth}`) } if (ns == "Ease") { if is_ease_ns(meth) { return emit_ease_ns(meth, e) } perr(`unknown builtin Ease.{meth}`) } if (ns == "Anim") { if is_anim_ns(meth) { return emit_anim_ns(meth, e) } perr(`unknown builtin Anim.{meth}`) } if (ns == "Tween") { if is_tween_ns(meth) { return emit_tween_ns(meth, e) } perr(`unknown builtin Tween.{meth}`) } if (ns == "Collision") { if is_collide_ns(meth) { return emit_collide_ns(meth, e) } perr(`unknown builtin Collision.{meth}`) } if (ns == "Memory") { if is_mem_ns(meth) { return emit_mem_ns(meth, e) } perr(`unknown builtin Memory.{meth}`) } if (ns == "Color") { if is_colorfn_ns(meth) { return emit_colorfn_ns(meth, e) } perr(`unknown builtin Color.{meth}`) } if (ns == "Time") { if is_time_ns(meth) { return emit_time_ns(meth, e) } perr(`unknown builtin Time.{meth}`) } if (ns == "Hash") { if is_hash_ns(meth) { return emit_hash_ns(meth, e) } perr(`unknown builtin Hash.{meth}`) } if (ns == "Crypto") { if is_crypto_ns(meth) { return emit_crypto_ns(meth, e) } perr(`unknown builtin Crypto.{meth}`) } if (ns == "Uuid") { if is_uuid_ns(meth) { return emit_uuid_ns(meth, e) } perr(`unknown builtin Uuid.{meth}`) } if (ns == "Noise") { if is_noise_ns(meth) { return emit_noise_ns(meth, e) } perr(`unknown builtin Noise.{meth}`) } if (ns == "Log") { if is_log_ns(meth) { return emit_log_ns(meth, e) } perr(`unknown builtin Log.{meth}`) } if (ns == "Os") { if is_os_ns(meth) { return emit_os_ns(meth, e) } perr(`unknown builtin Os.{meth}`) } if (ns == "Unicode") { if is_unicode_ns(meth) { return emit_unicode_ns(meth, e) } perr(`unknown builtin Unicode.{meth}`) } if (ns == "Fs") { if is_fs_ns(meth) { return emit_fs_ns(meth, e) } perr(`unknown builtin Fs.{meth}`) } if (ns == "Path") { if is_path_ns(meth) { return emit_path_ns(meth, e) } perr(`unknown builtin Path.{meth}`) } if (ns == "Mime") { if is_mime_ns(meth) { return emit_mime_ns(meth, e) } perr(`unknown builtin Mime.{meth}`) } if (ns == "Vector") { if is_vector_ns(meth) { return emit_vector_ns(meth, e) } perr(`unknown builtin Vector.{meth}`) } if (ns == "Duration") { if is_duration_ns(meth) { return emit_duration_ns(meth, e) } perr(`unknown builtin Duration.{meth}`) } if (ns == "Date") { if is_date_ns(meth) { return emit_date_ns(meth, e) } perr(`unknown builtin Date.{meth}`) } if (ns == "DateTime") { if is_datetime_ns(meth) { return emit_datetime_ns(meth, e) } perr(`unknown builtin DateTime.{meth}`) } if (ns == "Clock") { if is_clock_ns(meth) { return emit_clock_ns(meth, e) } perr(`unknown builtin Clock.{meth}`) } var bare: pointer = null let labels = new []pointer if (ns == "Screen") { if (meth == "clear") { bare = "clear"; push(labels, "color") } if (meth == "fill_rectangle") { bare = "fill_rect"; push(labels, "x"); push(labels, "y"); push(labels, "width"); push(labels, "height"); push(labels, "color") } if (meth == "draw_rectangle") { bare = "frame_rect"; push(labels, "x"); push(labels, "y"); push(labels, "width"); push(labels, "height"); push(labels, "color") } if (meth == "put_pixel") { bare = "put_px"; push(labels, "x"); push(labels, "y"); push(labels, "color") } if (meth == "draw_text") { bare = "text"; push(labels, "x"); push(labels, "y"); push(labels, "text"); push(labels, "color"); push(labels, "scale") } if (meth == "draw_number") { bare = "text_int"; push(labels, "x"); push(labels, "y"); push(labels, "value"); push(labels, "color"); push(labels, "scale") } if (meth == "show") { bare = "present" } if (meth == "width") { bare = "screen_w" } if (meth == "height") { bare = "screen_h" } if (meth == "status") { bare = "status"; push(labels, "text") } if (meth == "line") { bare = "line"; push(labels, "x1"); push(labels, "y1"); push(labels, "x2"); push(labels, "y2"); push(labels, "color") } if (meth == "circle") { bare = "circle"; push(labels, "x"); push(labels, "y"); push(labels, "radius"); push(labels, "color") } if (meth == "fill_circle") { bare = "fill_circle"; push(labels, "x"); push(labels, "y"); push(labels, "radius"); push(labels, "color") } if (meth == "triangle") { bare = "triangle"; push(labels, "x1"); push(labels, "y1"); push(labels, "x2"); push(labels, "y2"); push(labels, "x3"); push(labels, "y3"); push(labels, "color") } if (meth == "fill_triangle") { bare = "fill_triangle"; push(labels, "x1"); push(labels, "y1"); push(labels, "x2"); push(labels, "y2"); push(labels, "x3"); push(labels, "y3"); push(labels, "color") } if (meth == "sprite") { bare = "draw_sprite"; push(labels, "id"); push(labels, "x"); push(labels, "y") } if (meth == "sprite_scaled") { bare = "draw_sprite_scaled"; push(labels, "id"); push(labels, "x"); push(labels, "y"); push(labels, "scale") } if (meth == "oval") { bare = "oval"; push(labels, "x"); push(labels, "y"); push(labels, "rx"); push(labels, "ry"); push(labels, "color") } if (meth == "camera") { bare = "camera"; push(labels, "x"); push(labels, "y") } if (meth == "clip") { bare = "clip"; push(labels, "x"); push(labels, "y"); push(labels, "width"); push(labels, "height") } if (meth == "clip_reset") { bare = "clip_reset" } if (meth == "blend_mode") { bare = "blend_mode"; push(labels, "mode") } if (meth == "measure_text") { bare = "measure_text"; push(labels, "text") } if (meth == "pixel") { bare = "get_px"; push(labels, "x"); push(labels, "y") } } # Camera.* — the world-space camera: a draw offset threaded through the render # path (runtime/native/core.ludic). set/follow move it; shake jitters it from # the seeded RNG, so a replay shakes identically. if (ns == "Camera") { if (meth == "set") { bare = "camera"; push(labels, "x"); push(labels, "y") } if (meth == "follow") { bare = "camera_follow"; push(labels, "x"); push(labels, "y"); push(labels, "lerp") } if (meth == "shake") { bare = "camera_shake"; push(labels, "amount") } } if (ns == "Map") { if (meth == "size") { bare = "map_size"; push(labels, "width"); push(labels, "height") } if (meth == "row") { bare = "map_row"; push(labels, "y"); push(labels, "cells") } if (meth == "tile") { bare = "tile"; push(labels, "x"); push(labels, "y") } } if (ns == "Random") { if (meth == "range") { bare = "rng_range"; push(labels, "low"); push(labels, "high") } if (meth == "chance") { bare = "rng_chance"; push(labels, "percent") } if (meth == "seed") { bare = "seed"; push(labels, "value") } if (meth == "value") { bare = "rng_value" } if (meth == "int") { bare = "rng_int"; push(labels, "max") } if (meth == "sign") { bare = "rng_sign" } } if (ns == "Input") { if (meth == "key") { bare = "key" } } # Phase 3: the bare reflection / networking / process builtins, namespaced. # Each is a pure alias — the callee is rewritten to the bare name below. if (ns == "World") { if (meth == "get") { bare = "world_get" } if (meth == "set") { bare = "world_set" } if (meth == "has") { bare = "world_has" } if (meth == "count") { bare = "world_count" } if (meth == "size") { bare = "world_size" } if (meth == "spawn") { bare = "world_spawn" } if (meth == "save") { bare = "world_save" } if (meth == "load") { bare = "world_load" } if (meth == "prop_id") { bare = "world_prop_id" } if (meth == "field_id") { bare = "world_field_id" } if (meth == "model_id") { bare = "world_model_id" } if (meth == "kind") { bare = "world_kind" } if (meth == "register_prop") { bare = "world_register_prop" } if (meth == "attach") { bare = "world_attach_dyn" } if (meth == "detach") { bare = "world_detach_dyn" } if (meth == "query_next") { bare = "world_query_next" } } if (ns == "Network") { if (meth == "send") { bare = "net_send" } if (meth == "poll") { bare = "net_poll" } if (meth == "serialize") { bare = "serialize" } if (meth == "apply") { bare = "apply" } if (meth == "owner") { bare = "owner" } if (meth == "set_owner") { bare = "set_owner" } if (meth == "is_server") { bare = "is_server" } if (meth == "is_owner") { bare = "is_owner" } if (meth == "local_id") { bare = "local_id" } } if (ns == "System") { # System.* is the low-level file/process surface only. The environment slice # (arg/arg_count/env/exit) and the standard streams (stdout/stderr) were # duplicated by the canonical Os.* namespace and have been retired — see # Os.arg/arg_count/env/exit and Os.stdout_write/stderr_write (issue #40). if (meth == "run") { bare = "run" } if (meth == "read_char") { bare = "read_char" } if (meth == "file_open") { bare = "file_open" } if (meth == "file_read") { bare = "file_read" } if (meth == "file_write") { bare = "file_write" } if (meth == "file_seek") { bare = "file_seek" } if (meth == "file_tell") { bare = "file_tell" } if (meth == "file_close") { bare = "file_close" } } if (ns == "Save") { if (meth == "write") { bare = "save" } if (meth == "read") { bare = "load" } } # Regex.* -> the regex_* engine functions (spliced from runtime/native/regex*.ludic # when a program mentions Regex.*). Each is a plain alias; the engine functions # are ordinary Ludic, so the generic call path resolves them to @fn_regex_*. if (ns == "Regex") { if (meth == "compile") { bare = "regex_compile"; push(labels, "pattern") } if (meth == "valid") { bare = "regex_valid"; push(labels, "pattern") } if (meth == "matches") { bare = "regex_matches"; push(labels, "text"); push(labels, "pattern") } if (meth == "test") { bare = "regex_test"; push(labels, "text"); push(labels, "re") } if (meth == "find") { bare = "regex_find"; push(labels, "text"); push(labels, "pattern") } if (meth == "exec") { bare = "regex_exec"; push(labels, "text"); push(labels, "re") } if (meth == "next") { bare = "regex_next"; push(labels, "text"); push(labels, "re"); push(labels, "from") } if (meth == "replace") { bare = "regex_replace"; push(labels, "text"); push(labels, "pattern"); push(labels, "replacement") } if (meth == "group") { bare = "regex_group"; push(labels, "match"); push(labels, "n") } if (meth == "group_count") { bare = "regex_group_count"; push(labels, "match") } if (meth == "start") { bare = "regex_start"; push(labels, "match"); push(labels, "n") } if (meth == "end") { bare = "regex_end"; push(labels, "match"); push(labels, "n") } if (meth == "ok") { bare = "regex_ok"; push(labels, "match") } } # Grid.* — tile geometry and pathfinding over the Map tilemap, from # runtime/native/grid.ludic (spliced with core.ludic). `wall` is the impassable # tile char, e.g. '#'. line/flood/a_star return []Cell slices. (Pathfinding # lives under Grid rather than a `Path` namespace — that name is the filesystem # paths library.) if (ns == "Grid") { if (meth == "line") { bare = "grid_line"; push(labels, "x0"); push(labels, "y0"); push(labels, "x1"); push(labels, "y1") } if (meth == "blocked") { bare = "grid_blocked"; push(labels, "x"); push(labels, "y"); push(labels, "wall") } if (meth == "line_of_sight") { bare = "grid_line_of_sight"; push(labels, "x0"); push(labels, "y0"); push(labels, "x1"); push(labels, "y1"); push(labels, "wall") } if (meth == "flood") { bare = "grid_flood"; push(labels, "x"); push(labels, "y"); push(labels, "wall") } if (meth == "a_star") { bare = "path_a_star"; push(labels, "x0"); push(labels, "y0"); push(labels, "x1"); push(labels, "y1"); push(labels, "wall") } } # Light.* — the 2D light-accumulation pass (runtime/native/light.ludic, spliced # on demand). A game runs it in its render phase: ambient multiplies the scene # down, point adds a radial glow (blocked by occluders -> hard shadows). Screen # space, deterministic (integer + Q16.16), diffable. `energy` is a fixed. if (ns == "Light") { if (meth == "ambient") { bare = "light_ambient"; push(labels, "color") } if (meth == "point") { bare = "light_point"; push(labels, "x"); push(labels, "y"); push(labels, "radius"); push(labels, "color"); push(labels, "energy") } if (meth == "occlude") { bare = "light_occlude"; push(labels, "x"); push(labels, "y"); push(labels, "width"); push(labels, "height") } if (meth == "clear_occluders") { bare = "light_clear_occluders" } } # Query.* — ECS spatial queries over the reflection ABI (runtime/native/query.ludic, # spliced on demand). `prop` is a property id (World.prop_id); the spatial forms # read two int fields (field ids) as (x, y). nearest/first return an entity (-1 = # none); within returns a []int of entities. A linear scan — ample for the entity # counts Ludic targets, like the grid pathfinder's open set. if (ns == "Query") { if (meth == "count") { bare = "query_count"; push(labels, "prop") } if (meth == "first") { bare = "query_first"; push(labels, "prop") } if (meth == "nearest") { bare = "query_nearest"; push(labels, "prop"); push(labels, "pos"); push(labels, "x_field"); push(labels, "y_field"); push(labels, "x"); push(labels, "y") } if (meth == "within") { bare = "query_within"; push(labels, "prop"); push(labels, "pos"); push(labels, "x"); push(labels, "y"); push(labels, "radius"); push(labels, "x_field"); push(labels, "y_field") } } # Reflect.* — runtime type reflection over the world schema (the EV2/EV8 ABI). # Enumerate properties and fields by index, resolve ids by name, and read/write # a field by (prop, field) id — the foundation for auto-serialization and debug # inspectors. Reads the same generated metadata a foreign mod binds. if (ns == "Reflect") { if (meth == "prop") { bare = "world_prop_id"; push(labels, "name") } if (meth == "field") { bare = "world_field_id"; push(labels, "prop"); push(labels, "name") } if (meth == "prop_count") { bare = "world_prop_count" } if (meth == "prop_name") { bare = "world_prop_name"; push(labels, "index") } if (meth == "field_count") { bare = "world_field_count"; push(labels, "prop") } if (meth == "field_name") { bare = "world_field_name"; push(labels, "prop"); push(labels, "index") } if (meth == "field_type") { bare = "world_field_type"; push(labels, "prop"); push(labels, "index") } if (meth == "get") { bare = "world_get"; push(labels, "entity"); push(labels, "prop"); push(labels, "field") } if (meth == "set") { bare = "world_set"; push(labels, "entity"); push(labels, "prop"); push(labels, "field"); push(labels, "value") } if (meth == "has") { bare = "world_has"; push(labels, "entity"); push(labels, "prop") } if (meth == "kind") { bare = "world_kind"; push(labels, "entity") } if (meth == "model") { bare = "world_model_id"; push(labels, "name") } } if (bare == null) { perr(`unknown builtin {ns}.{meth}`) } reorder_named(e, labels) let id = node(E_ID); id.s = bare; e.a = id return emit_call(e) } function emit_call(e: Node) -> Val { # `Subject.action(...)` — a namespaced builtin (Screen/Random/Input). if e.a.kind == E_MEMBER { if e.a.a.kind == E_ID { return emit_ns_call(e.a.a.s, e.a.s, e) } perr("call target is not a function") } let name = e.a.s 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 == "world_size") { return val(emit_bind("call i32 @L_world_size()"), "int") } if (name == "world_save") { # world_save(buf) -> bytes written let b = emit_expr(e.kids[0]) return val(emit_bind(`call i32 @L_world_save(ptr {b.code})`), "int") } if (name == "world_load") { # world_load(buf, len) let b = emit_expr(e.kids[0]) let l = emit_expr(e.kids[1]) emit(" call void @L_world_load(ptr "); emit(b.code); emit(", i32 "); emit(l.code); emit(")\n") return val("0", "void") } if (name == "quit") { emit(" store i32 0, ptr @L_running\n"); return val("0", "void") } # NETWORKING (NETWORKING-DESIGN §5) — the low-level freedom layer, callable from # Ludic. serialize/apply/sync_size lower to the @Sync by-kind dispatchers (N2); # owner/set_owner/is_owner to the @Owned storage (N3); is_server/local_id read # the runtime-set role registers (N5). Offline these hold their single-player # default (@L_role=1 → is_server()==true), so guards collapse to "run here" (§8). if (name == "serialize") { # serialize(e, buf) -> bytes written let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]) return val(emit_bind(`call i32 @ludic_serialize(i32 {a.code}, ptr {b.code})`), "int") } if (name == "apply") { # apply(e, buf, len) let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); let c = emit_expr(e.kids[2]) emit(" call void @ludic_apply(i32 "); emit(a.code); emit(", ptr "); emit(b.code); emit(", i32 "); emit(c.code); emit(")\n") return val("0", "void") } if (name == "sync_size") { # sync_size(e) -> replicated byte count for e's model let a = emit_expr(e.kids[0]) return val(emit_bind(`call i32 @ludic_sync_size(i32 {a.code})`), "int") } if (name == "owner") { # owner(e) -> peer id (-1 = unowned) let a = emit_expr(e.kids[0]) return val(emit_bind(`call i32 @L_owner(i32 {a.code})`), "int") } if (name == "set_owner") { # set_owner(e, id) let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]) emit(" call void @L_set_owner(i32 "); emit(a.code); emit(", i32 "); emit(b.code); emit(")\n") return val("0", "void") } if (name == "is_owner") { # is_owner(e) -> owner(e) == local_id() let a = emit_expr(e.kids[0]) return val(emit_bind(`call i32 @L_is_owner(i32 {a.code})`), "bool") } if (name == "is_server") { # is_server() -> the local peer is the authority let r = emit_bind("load i32, ptr @L_role") let c = emit_bind(`icmp eq i32 {r}, 1`) return val(emit_bind(`zext i1 {c} to i32`), "bool") } if (name == "local_id") { return val(emit_bind("load i32, ptr @L_localid"), "int") } if (name == "net_pump") { emit(" call void @L_net_pump()\n"); return val("0", "void") } # N4: drain + re-emit inbound RPCs if (name == "tick_fixed") { emit(" call void @L_tick_fixed()\n"); return val("0", "void") } # N5: run the sim phases if (name == "tick_render") { emit(" call void @L_tick_render()\n"); return val("0", "void") } # N5: run the Render phase if (name == "set_role") { # N5: the runtime sets the peer's role (1=server, 0=client) let a = emit_expr(e.kids[0]); emit(" store i32 "); emit(a.code); emit(", ptr @L_role\n"); return val("0", "void") } if (name == "set_local_id") { # N5: the runtime sets this peer's id let a = emit_expr(e.kids[0]); emit(" store i32 "); emit(a.code); emit(", ptr @L_localid\n"); return val("0", "void") } # net_send(peer, buf, len) / net_poll(buf, cap): the transport seam. An # `extern fn` of the same name (a real socket) wins; absent one, these lower to # the compiler's built-in loopback so a game is networked with zero foreign code. if (name == "net_send") and (find_extern("net_send") == null) { g_uses_loopback = true let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); let c = emit_expr(e.kids[2]) emit(" call void @L_net_send(i32 "); emit(a.code); emit(", ptr "); emit(b.code); emit(", i32 "); emit(c.code); emit(")\n") return val("0", "void") } if (name == "net_poll") and (find_extern("net_poll") == null) { g_uses_loopback = true let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]) return val(emit_bind(`call i32 @L_net_poll(ptr {a.code}, i32 {b.code})`), "int") } if (name == "len") { return emit_len(e) } if (name == "push") { return emit_push(e) } if (name == "string") { # string(x): int/bool/fixed/long -> text, a string passes through let a = emit_expr(e.kids[0]) if (llty(a.ty) == "ptr") { return a } if (llty(a.ty) == "i64") { g_uses_longstr = true; return val(emit_bind(`call ptr @fn_long_str(i64 {a.code})`), "string") } g_uses_intstr = true return val(emit_bind(`call ptr @fn_int_str(i32 {a.code})`), "string") } if (name == "print") { # print(x): a value + newline (string, long, or int) let a = emit_expr(e.kids[0]) if (llty(a.ty) == "ptr") { emit(" call i32 (ptr, ...) @printf(ptr @.fmt_line, ptr " + `{a.code})\n`) } else { if (llty(a.ty) == "i64") { emit(" call i32 (ptr, ...) @printf(ptr @.fmt_long, i64 " + `{a.code})\n`) } else { emit(" call i32 (ptr, ...) @printf(ptr @.fmt_int, i32 " + `{a.code})\n`) } } return val("0", "void") } if (name == "bytes") { # bytes(n): allocate n bytes -> a byte buffer let n = emit_expr(e.kids[0]) let w = emit_bind(`zext i32 {n.code} to i64`) return val(emit_bind(`call ptr @malloc(i64 {w})`), "pointer") } if (name == "words") { # words(n): allocate n 32-bit words let n = emit_expr(e.kids[0]) let by = emit_bind(`mul i32 {n.code}, 4`) let w = emit_bind(`zext i32 {by} to i64`) return val(emit_bind(`call ptr @malloc(i64 {w})`), "words") } if (name == "fixed") { let a = emit_expr(e.kids[0]); return val(emit_bind(`shl i32 {a.code}, 16`), "fixed") } if (name == "floor") { let a = emit_expr(e.kids[0]); return val(emit_bind(`ashr i32 {a.code}, 16`), "int") } # The EV2 reflection ABI (the world table), exposed to Ludic so a Ludic mod can # introspect the world by name — the same functions a foreign mod binds. Emitted # only for a modding program (ECS + events), so a plain game is unchanged. if (name == "world_prop_id") { let a = emit_expr(e.kids[0]); return val(emit_bind(`call i32 @ludic_prop_id(ptr {a.code})`), "int") } if (name == "world_field_id") { let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); return val(emit_bind(`call i32 @ludic_field_id(i32 {a.code}, ptr {b.code})`), "int") } if (name == "world_get") { let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); let c = emit_expr(e.kids[2]) let r = emit_bind(`call i64 @ludic_get(i32 {a.code}, i32 {b.code}, i32 {c.code})`) return val(emit_bind(`trunc i64 {r} to i32`), "int") } if (name == "world_set") { let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); let c = emit_expr(e.kids[2]); let d = emit_expr(e.kids[3]) let v64 = emit_bind(`sext i32 {d.code} to i64`) emit(" call void @ludic_set(i32 "); emit(a.code); emit(", i32 "); emit(b.code); emit(", i32 "); emit(c.code); emit(", i64 "); emit(v64); emit(")\n") return val("0", "void") } if (name == "world_has") { let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); return val(emit_bind(`call i32 @ludic_has(i32 {a.code}, i32 {b.code})`), "int") } if (name == "world_count") { return val(emit_bind("call i32 @ludic_entity_count()"), "int") } if (name == "world_kind") { let a = emit_expr(e.kids[0]); return val(emit_bind(`call i32 @ludic_kind(i32 {a.code})`), "int") } if (name == "world_model_id") { let a = emit_expr(e.kids[0]); return val(emit_bind(`call i32 @ludic_model_id(ptr {a.code})`), "int") } if (name == "world_query_next") { let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); return val(emit_bind(`call i32 @ludic_query_next(i32 {a.code}, i32 {b.code})`), "int") } if (name == "world_register_prop") { let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); return val(emit_bind(`call i32 @ludic_register_prop(ptr {a.code}, i32 {b.code})`), "int") } if (name == "world_attach_dyn") { let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); emit(" call void @ludic_attach_dyn(i32 "); emit(a.code); emit(", i32 "); emit(b.code); emit(")\n"); return val("0", "void") } if (name == "world_detach_dyn") { let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); emit(" call void @ludic_detach_dyn(i32 "); emit(a.code); emit(", i32 "); emit(b.code); emit(")\n"); return val("0", "void") } if (name == "world_spawn") { let a = emit_expr(e.kids[0]); return val(emit_bind(`call i32 @ludic_spawn(i32 {a.code})`), "int") } # EV8 — schema enumeration, walking property/field metadata by index (Reflect.*). if (name == "world_prop_count") { return val(emit_bind("call i32 @ludic_prop_count()"), "int") } if (name == "world_prop_name") { let a = emit_expr(e.kids[0]); return val(emit_bind(`call ptr @ludic_prop_name(i32 {a.code})`), "string") } if (name == "world_field_count") { let a = emit_expr(e.kids[0]); return val(emit_bind(`call i32 @ludic_field_count(i32 {a.code})`), "int") } if (name == "world_field_name") { let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); return val(emit_bind(`call ptr @ludic_field_name(i32 {a.code}, i32 {b.code})`), "string") } if (name == "world_field_type") { let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); return val(emit_bind(`call ptr @ludic_field_type(i32 {a.code}, i32 {b.code})`), "string") } 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) } # extern fn: a direct call to the declared link symbol (no @fn_ prefix) let ext = find_extern(name) if (ext != null) { reorder_named(e, param_labels(ext)) let eargs = new []pointer let eatys = new []pointer var ei = 0 while ei < len(e.kids) { let v = emit_expr(e.kids[ei]); push(eargs, v.code); push(eatys, v.ty); ei = ei + 1 } let erl = llty(ext.ty) emit(" ") var erreg = "0" if not (erl == "void") { erreg = nreg(); emit(erreg); emit(" = ") } emit("call "); emit(erl); emit(" @"); emit(ext.a.s); emit("(") ei = 0 while ei < len(eargs) { if ei > 0 { emit(", ") } emit(llty(eatys[ei])); emit(" "); emit(eargs[ei]) ei = ei + 1 } emit(")\n") return val(erreg, ext.ty) } var fn2 = find_fn(name) var cname = name if (fn2 == null) { # a builtin like clear()/reg() is satisfied by its rt_ function let rtname = `rt_{name}` fn2 = find_fn(rtname) if (fn2 == null) { perr(`unknown function {name}`) } cname = rtname } reorder_named(e, param_labels(fn2)) # evaluate args first (their IR is emitted before the call instruction), coercing # each to the parameter's declared type so an int passed for a `long` widens. let ptys = param_types(fn2) let args = new []pointer let atys = new []pointer var i = 0 while i < len(e.kids) { let v = emit_expr(e.kids[i]) var pty = v.ty if (i < len(ptys)) { pty = ptys[i] } push(args, coerce_code(v, pty)); push(atys, pty); i = i + 1 } let rl = llty(fn2.ty) emit(" ") var rreg = "0" if not (rl == "void") { rreg = nreg(); emit(rreg); emit(" = ") } emit("call "); emit(rl); emit(" @fn_"); emit(cname); emit("(") i = 0 while i < len(args) { if i > 0 { emit(", ") } emit(llty(atys[i])); emit(" "); emit(args[i]) i = i + 1 } emit(")\n") return val(rreg, fn2.ty) } function emit_expr(e: Node) -> Val { if (e == null) { return val("0", "int") } if e.kind == E_INT { return val(itoa(e.ival), "int") } if e.kind == E_FLOAT { return val(itoa(e.ival), "fixed") } if e.kind == E_BOOL { return val(itoa(e.ival), "bool") } if e.kind == E_NULL { return val("null", "pointer") } if e.kind == E_SLICE { # s[a..b] -> a fresh substring let base = emit_expr(e.a) let lo = emit_expr(e.b) let hi = emit_expr(e.c) g_uses_strslice = true return val(emit_bind(`call ptr @fn_str_slice(ptr {base.code}, i32 {lo.code}, i32 {hi.code})`), "string") } if e.kind == E_STR { return val(emit_str_const(e.s), "string") } if e.kind == E_NEW { if is_slice_ty(e.s) { return emit_new_slice(e.s) } return emit_new_struct(e.s, e.a) } if e.kind == E_ID { let li = loc_find(e.s) if li >= 0 { return emit_load_at(loc_reg[li], loc_ty[li]) } let g = find_global(e.s) if (g != null) { if g.kind == N_CONST { return val(itoa(g.a.ival), "int") } let r = emit_bind(`load {llty(g.ty)}, ptr @g_{e.s}`) return val(r, g.ty) } # a UI_ 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(`unknown identifier {e.s}`) } if e.kind == E_MEMBER { if e.a.kind == E_ID { if (e.a.s == "Color") { # `Color.Name` -> its 0xRRGGBB int, at compile time let cv = color_lookup(e.s) if (cv < 0) { perr(`unknown color Color.{e.s}`) } return val(itoa(cv), "int") } let ord = enum_ordinal(e.a.s, e.s) # `Enum.Variant` -> its ordinal, a compile-time int if ord >= 0 { return val(itoa(ord), "int") } } let bt = static_type(e.a) # `x.field` where field is @Computed -> inline it if (bt != null) { let cx = computed_expr(bt, e.s) if (cx != null) { return emit_expr(qualify_fields(cx, e.a)) } } let a = emit_member_addr(e); return emit_load_at(a, g_addr_ty) } if e.kind == E_INDEX { let a = emit_index_addr(e) if (g_addr_ty == "byte") { # a byte read, widened to int let b = emit_bind(`load i8, ptr {a}`) return val(emit_bind(`zext i8 {b} to i32`), "int") } return emit_load_at(a, g_addr_ty) } if e.kind == S_EMIT { return emit_emit(e) } # emit as an expression -> cancelled flag if e.kind == E_CALL { return emit_call(e) } if e.kind == E_BIN { return emit_bin(e) } if e.kind == E_UN { let a = emit_expr(e.a) if (llty(a.ty) == "i64") { # negate / bit-flip a long, staying 64-bit if (e.s == ("-")) { return val(emit_bind(`sub i64 0, {a.code}`), "long") } if (e.s == "~") { return val(emit_bind(`xor i64 {a.code}, -1`), "long") } } 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") }