feat(lang): L6 namespaces declared in Ludic - alias

`alias meth(labels) = target` in a namespace block makes Ns.meth a call to
target with those labels (the target's own parameter names without a list). The
engine's 41 table-driven namespaces - 438 methods: Http, Udp, Process, Json,
Value, Screen, Input, Audio, World, Tiled, ... - leave emit_ns_call for
runtime/native/namespaces.ludic, spliced into every program; 532 lines of
compiler go and the seed shrinks by 23k lines of IR. The game's IR is byte
identical. The checker checks an alias call's arguments against its target.
Still built in: the inline namespaces (Math, Text, List, Vector, Color, Time,
Date, ...) and the methods that pick a target by argument type.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-24 02:40:57 +03:00
parent 9662680bb0
commit 9259808f80
16 changed files with 49226 additions and 95736 deletions

View file

@ -187,6 +187,34 @@ Generics are compiled by instantiation: each instance the program uses is an ord
function, made and checked once, so it costs exactly what writing it out by hand would. A generic function, made and checked once, so it costs exactly what writing it out by hand would. A generic
nothing instantiates is not compiled at all. nothing instantiates is not compiled at all.
### Namespaces declared in Ludic (`alias`)
A namespace method can be a name for a function. Inside a `namespace` block,
```ludic
program Trails {
namespace Trail {
export alias length(from, to) = trail_distance
alias km = trail_km
}
function trail_distance(a: int, b: int) -> int { return b - a }
function trail_km(metres: int) -> int { return metres / 1000 }
entry { print(Trail.length(to: 12, from: 2) + Trail.km(metres: 5000)) }
}
```
makes `Trail.length(...)` a call to `trail_distance`: the list after the method is the labels a call
may name its arguments by, in the target's parameter order, and without one the target's own
parameter names are the labels (`alias show() = present` takes none). The call is the target's -
same arguments, same checks, same code - so a namespace costs nothing over calling the function.
This is how the engine declares its own namespaces: `Http`, `Udp`, `Process`, `Json`, `Value`,
`Screen`, `Input`, `Audio`, `World`, `Tiled` and the rest are `alias` blocks in
`runtime/native/namespaces.ludic`, not branches in the compiler, and a package owns an API the same
way in its own files. What is still built into the compiler is the namespaces that compute inline -
`Math`, `Text`, `List`, `Vector`, `Color`, `Time`, `Date` - and the few methods that choose their
target by an argument's type (`Audio.play` of a handle or a name).
## Models (entity kinds) ## Models (entity kinds)
An `model` names a *kind* of entity and the fixed set of properties it An `model` names a *kind* of entity and the fixed set of properties it

View file

@ -0,0 +1,9 @@
bump: minor
type: feature
**Namespaces are declared in Ludic.** `alias meth(labels) = target` in a `namespace` block makes
`Ns.meth(...)` a call to `target`, taking named arguments by those labels; with no label list the
target's own parameter names are the labels. The engine's 41 table-driven namespaces - `Http`,
`Udp`, `Process`, `Json`, `Value`, `Screen`, `Input`, `Audio`, `World`, `Tiled` and the rest, 438
methods - moved out of the compiler into `runtime/native/namespaces.ludic`, and a package owns an
API the same way. The code a program compiles to is unchanged byte for byte, and the checker now
checks an alias call's arguments against its target.

View file

@ -144,6 +144,11 @@ error naming both modules.
## Package-declarable namespaces and engine systems (issue #62) ## Package-declarable namespaces and engine systems (issue #62)
A package's namespace can also be an `alias` block (L6): `namespace Weapon { alias fire(slot) =
wp_fire_slot }` names each method and the function it calls, with its own labels, so the public
names need not be the implementation's. The engine's own `Http.*`, `Json.*`, `Screen.*` and the
rest are declared exactly this way, in `runtime/native/namespaces.ludic`.
A package can register two things that used to be compiler-hardcoded — a `Foo.*` A package can register two things that used to be compiler-hardcoded — a `Foo.*`
namespace and an engine-owned system — with **no compiler edit**, via two namespace and an engine-owned system — with **no compiler edit**, via two
keyword-free annotations. This is what lets gameplay-controller libraries ship as keyword-free annotations. This is what lets gameplay-controller libraries ship as

View file

@ -0,0 +1,19 @@
# aliases.ludic — L6: a namespace method that is a name for a function. `alias` gives the method,
# the labels a call may use (in the target's parameter order) and the function it calls; without a
# label list the target's own parameter names are the labels. This is how the engine declares
# Http, Screen, Json and the rest (runtime/native/namespaces.ludic), and how a package owns an API.
#
# Running it prints: 10 5 3
program Aliases {
namespace Trail {
export alias length(from, to) = trail_distance
alias km = trail_km
alias stops() = trail_stops
}
function trail_distance(a: int, b: int) -> int { return b - a }
function trail_km(metres: int) -> int { return metres / 1000 }
function trail_stops() -> int { return 3 }
entry {
print(`{Trail.length(to: 12, from: 2)} {Trail.km(metres: 5000)} {Trail.stops()}`)
}
}

View file

@ -0,0 +1,8 @@
# L6: an alias is its target, so a call gives it the target's arguments
program AliasArity {
namespace Trail {
alias length(from, to) = trail_distance
}
function trail_distance(a: int, b: int) -> int { return b - a }
entry { print(Trail.length(4)) }
}

View file

@ -0,0 +1,9 @@
# L6: a namespace method is declared once
program AliasTwice {
namespace Trail {
alias length(from, to) = trail_distance
alias length(a, b) = trail_distance
}
function trail_distance(a: int, b: int) -> int { return b - a }
entry { print(Trail.length(1, 2)) }
}

View file

@ -0,0 +1,544 @@
# namespaces.ludic — the engine's namespaces that are a name for a runtime function: `Http.get`
# is `http_get`, `Screen.fill_rectangle` is `fill_rect`. Each `alias` names the method, the labels
# a call may name its arguments by (in the target's parameter order), and the function it calls.
# Spliced into every program, and costs nothing until a method is called: the file that defines
# the target is spliced by its own use. A package declares its namespace the same way (L6).
namespace Screen {
alias clear(color) = clear
alias fill_rectangle(x, y, width, height, color) = fill_rect
alias bar(x, y, width, height, value, max, color, back) = bar
alias draw_rectangle(x, y, width, height, color) = frame_rect
alias put_pixel(x, y, color) = put_px
alias draw_text(x, y, text, color, scale) = text
alias draw_number(x, y, value, color, scale) = text_int
alias show() = present
alias width() = screen_w
alias height() = screen_h
alias status(text) = status
alias line(x1, y1, x2, y2, color) = line
alias circle(x, y, radius, color) = circle
alias fill_circle(x, y, radius, color) = fill_circle
alias triangle(x1, y1, x2, y2, x3, y3, color) = triangle
alias fill_triangle(x1, y1, x2, y2, x3, y3, color) = fill_triangle
alias sprite(id, x, y) = draw_sprite
alias sprite_scaled(id, x, y, scale) = draw_sprite_scaled
alias oval(x, y, rx, ry, color) = oval
alias camera(x, y) = camera
alias clip(x, y, width, height) = clip
alias clip_reset() = clip_reset
alias blend_mode(mode) = blend_mode
alias measure_text(text) = measure_text
alias pixel(x, y) = get_px
}
namespace Sprite {
alias sheet(path, cellw, cellh) = atlas_sheet
alias cell(sheet, col, row) = atlas_cell
alias cell_span(sheet, col, row, cols, rows) = atlas_cell_span
alias strip(sheet, col, row, count, rows) = atlas_strip
alias define(name, sheet, col, row) = atlas_define
alias named(name) = atlas_named
alias draw(id, x, y) = atlas_draw
alias draw_scaled(id, x, y, scale) = atlas_draw_scaled
alias draw_meter(x, y, value, max, per_icon, spacing, full, half, empty) = atlas_draw_meter
alias width(id) = atlas_width
alias height(id) = atlas_height
}
namespace Font {
alias load(path) = font_load
}
namespace Ui {
alias build() = ui_build
alias open(id) = ui_open
alias close() = ui_close
alias tick(key) = ui_tick
alias clicked(id) = ui_clicked
alias set_text(id, text) = ui_set_text
alias render() = ui_render
}
namespace File {
alias open(path, mode) = file_open
alias read(file, buffer, count) = file_read
alias write(file, buffer, count) = file_write
alias seek(file, offset, whence) = file_seek
alias tell(file) = file_tell
alias close(file) = file_close
}
namespace Fx {
alias sparks(x, y, color, count) = fx_sparks
alias number(x, y, value, color) = fx_number
alias clear() = fx_clear
}
namespace Assets {
alias font(name) = assets_font # a .ttf loaded through the queue
alias enqueue_dir(path) = assets_enqueue_dir # every file of a directory, by base name
alias image(path) = atlas_image
alias load(path) = atlas_image # alias
alias get(name) = atlas_named # alias of Sprite.named
# #82 — incremental preload queue + progress for a loading scene.
alias enqueue(name, path) = assets_enqueue
alias pump(max) = assets_pump
alias total() = assets_total
alias loaded() = assets_loaded
alias ready() = assets_ready
alias progress() = assets_progress
}
namespace Camera {
alias set(x, y) = camera
alias follow(x, y, lerp) = camera_follow
alias shake(amount) = camera_shake
alias shake_for(amount, frames) = camera_shake_for # a timed shake the engine decays
alias zoom(scale) = camera_zoom # #78 deterministic Q16.16 zoom
}
namespace Map {
alias size(width, height) = map_size
alias row(y, cells) = map_row
alias tile(x, y) = tile
alias get(x, y) = tile
# the cell API: edit the grid in place, ask it what is where
alias set(x, y, glyph) = map_set
alias fill(glyph) = map_fill
alias rect(x, y, width, height, glyph) = map_rect
alias border(glyph) = map_border
alias random_cell(glyph) = map_random_cell
alias random_cell_far(glyph, from, min_tiles) = map_random_cell_far
alias is_solid(x, y) = map_is_solid
alias is_solid_at(x, y) = map_is_solid_at
alias to_tile(pixel) = map_to_tile
alias width() = map_width
alias height() = map_height
}
namespace Random {
alias range(low, high) = rng_range
alias weighted(weights) = rng_weighted # an index in proportion to its weight
alias chance(percent) = rng_chance
alias seed(value) = seed
alias value() = rng_value
alias int(max) = rng_int
alias sign() = rng_sign
}
namespace Input {
alias key() = key
# action maps + deterministic record/replay (#7) — spliced runtime in
# runtime/native/input.ludic, reached as ordinary @fn_input_* calls.
alias bind(action, key) = input_bind
alias rebind(action, old, new) = input_rebind
alias poll() = input_poll
alias down() = input_down
alias pressed() = input_pressed
alias record() = input_record
alias replay() = input_replay
# Input Manager (#83): default bindings + device-agnostic actions that read the
# multi-key device layer (the frame loop now commits it automatically).
alias action(action, key) = input_default
alias bind_pad(action, button) = input_bind_pad
alias active(action) = input_active
alias just_pressed(action) = input_just_pressed
alias just_released(action) = input_just_released
# device layer (#50): multi-key held state, analog axes/vectors, mouse,
# gamepads, touch — reached as ordinary @fn_input_* calls into input.ludic.
alias key_down(key) = input_key_down
alias key_pressed(key) = input_key_pressed
alias key_released(key) = input_key_released
alias key_label(key) = input_key_label
alias text() = input_text # what was TYPED, UTF-8
alias press(key) = input_press
alias release(key) = input_release
alias axis(neg, pos) = input_axis
alias axis_i(neg, pos) = input_axis_i # #79 directional int (-1/0/1)
alias move_i() = input_move_i # WASD / arrows / left stick -> IVec2
alias vector(left, right, up, down) = input_vector
alias strength(action) = input_strength
alias mouse_x() = input_mouse_x
alias mouse_y() = input_mouse_y
alias mouse_dx() = input_mouse_dx
alias mouse_dy() = input_mouse_dy
alias mouse_down(button) = input_mouse_down
alias wheel() = input_wheel
alias set_mouse(x, y, buttons, wheel) = input_set_mouse
alias pad_connected(pad) = input_pad_connected
alias pad_button(pad, button) = input_pad_button
alias pad_axis(pad, axis) = input_pad_axis
alias set_pad(pad, connected, buttons, lx, ly, rx, ry) = input_set_pad
alias touch_count() = input_touch_count
alias touch_x(index) = input_touch_x
alias touch_y(index) = input_touch_y
alias set_touch(index, x, y, active) = input_set_touch
alias cursor_mode(mode) = input_cursor_mode # #89 cursor capture
}
namespace Audio {
alias load(path) = audio_load
alias define(name, path) = audio_define
alias named(name) = audio_named
alias play_sound(id) = audio_play
# Audio.play_at(id, gain:, pitch:, pan:) — one shot with its own gain, pitch and
# stereo position. Audio.volume / Audio.pitch stay global (they are the options
# screen); this is the per-voice one, and it is what distance attenuation is built on.
alias play_at(id, gain, pitch, pan) = audio_play_at
alias stop(id) = audio_stop
alias stop_music() = audio_stop_music
alias stop_all() = audio_stop_all
alias volume(v) = audio_volume
alias pitch(v) = audio_pitch
alias is_playing(id) = audio_is_playing
}
namespace Http {
alias get(url) = http_get
alias post(url, body) = http_post
alias request(method, url, body) = http_request
alias open(method, url) = http_open
alias set(handle, name, value) = http_set_header
alias body(handle, body) = http_body
alias body_bytes(handle, bytes, len) = http_body_n
alias send(handle) = http_send_req
alias poll(handle) = http_poll
alias status(handle) = http_status_of
alias ok(handle) = http_ok
alias text(handle) = http_text
alias body_len(handle) = http_body_len
alias header(handle, name) = http_header_of
alias free(handle) = http_close
alias parse(bytes, len) = http_parse
alias save_to(handle, path) = http_save_to
alias received(handle) = http_received
alias expected(handle) = http_expected
}
namespace Udp {
alias open(port) = udp_open
alias port(socket) = udp_port
alias send(socket, ip, port, bytes, len) = udp_send
alias recv(socket, bytes, cap) = udp_recv
alias from_ip(socket) = udp_from_ip
alias from_port(socket) = udp_from_port
alias close(socket) = udp_close
alias resolve(name) = udp_resolve
alias local_ip() = udp_local_ip
alias ip(text) = udp_ip
alias ip_text(ip) = udp_ip_text
}
namespace Process {
alias spawn(path, args) = process_spawn
alias poll(handle) = process_poll
alias kill(handle) = process_kill
alias free(handle) = process_free
}
namespace World {
alias get() = world_get
alias set() = world_set
alias has() = world_has
alias count() = world_count
alias size() = world_size
alias spawn() = world_spawn
alias despawn() = world_despawn # #84 — despawn an entity by id (runs @OnDespawn + frees)
alias save() = world_save
alias load() = world_load
alias prop_id() = world_prop_id
alias field_id() = world_field_id
alias model_id() = world_model_id
alias kind() = world_kind
alias register_prop() = world_register_prop
alias attach() = world_attach_dyn
alias detach() = world_detach_dyn
alias query_next() = world_query_next
}
namespace Network {
alias send() = net_send
alias poll() = net_poll
alias serialize() = serialize
alias apply() = apply
alias owner() = owner
alias set_owner() = set_owner
alias is_server() = is_server
alias is_owner() = is_owner
alias local_id() = local_id
}
namespace 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).
alias run() = run
alias read_char() = read_char
alias file_open() = file_open
alias file_read() = file_read
alias file_write() = file_write
alias file_seek() = file_seek
alias file_tell() = file_tell
alias file_close() = file_close
}
namespace Save {
alias write() = save
alias read() = load
}
namespace Regex {
alias compile(pattern) = regex_compile
alias valid(pattern) = regex_valid
alias matches(text, pattern) = regex_matches
alias test(text, re) = regex_test
alias find(text, pattern) = regex_find
alias exec(text, re) = regex_exec
alias next(text, re, from) = regex_next
alias replace(text, pattern, replacement) = regex_replace
alias group(match, n) = regex_group
alias group_count(match) = regex_group_count
alias start(match, n) = regex_start
alias end(match, n) = regex_end
alias ok(match) = regex_ok
}
namespace BigInt {
alias from(value) = bigint_from
alias parse(text) = bigint_from_str
alias add(a, b) = bigint_add
alias sub(a, b) = bigint_sub
alias mul(a, b) = bigint_mul
alias neg(a) = bigint_neg
alias pow(a, exp) = bigint_pow
alias div(a, d) = bigint_div_int
alias mod(a, d) = bigint_mod_int
alias cmp(a, b) = bigint_cmp
alias eq(a, b) = bigint_eq
alias is_zero(a) = bigint_is_zero
alias to_int(a) = bigint_to_int
alias str(a) = bigint_str
}
namespace Decimal {
alias from(value) = decimal_from
alias parse(text) = decimal_from_str
alias add(a, b) = decimal_add
alias sub(a, b) = decimal_sub
alias mul(a, b) = decimal_mul
alias neg(a) = decimal_neg
alias cmp(a, b) = decimal_cmp
alias eq(a, b) = decimal_eq
alias scale(d) = decimal_scale
alias rescale(d, places) = decimal_rescale
alias str(d) = decimal_str
}
namespace Dict {
alias new() = dict_new
alias set(d, key, value) = dict_set
alias get(d, key) = dict_get
alias get_or(d, key, fallback) = dict_get_or
alias has(d, key) = dict_has
alias remove(d, key) = dict_remove
alias size(d) = dict_size
alias clear(d) = dict_clear
alias keys(d) = dict_keys
}
namespace Set {
alias new() = set_new
alias add(s, key) = set_add
alias has(s, key) = set_has
alias remove(s, key) = set_remove
alias size(s) = set_size
alias clear(s) = set_clear
alias members(s) = set_members
}
namespace Job {
alias defer() = job_defer
alias run(kind, arg) = job_run
alias fulfill(handle, value) = job_fulfill
alias fail(handle, error) = job_fail
alias cancel(handle) = job_cancel
alias pump(budget) = job_pump
alias done(handle) = job_done
alias ok(handle) = job_ok
alias failed(handle) = job_failed
alias cancelled(handle) = job_cancelled
alias result(handle) = job_result
alias error(handle) = job_error
alias pending() = job_pending
alias free(handle) = job_free
alias is_worker() = job_is_worker
}
namespace Promise {
alias all(handles) = prom_all
alias race(handles) = prom_race
alias count_done(handles) = prom_count_done
alias all_done(handles) = prom_all_done
}
namespace Sync {
alias mutex() = sync_mutex
alias lock(mutex) = sync_lock
alias unlock(mutex) = sync_unlock
alias try_lock(mutex) = sync_try_lock
alias atomic() = sync_atomic
alias get(atomic) = sync_get
alias set(atomic, value) = sync_set
alias add(atomic, delta) = sync_add
alias cas(atomic, expect, next) = sync_cas
alias channel() = sync_channel
alias send(channel, value) = sync_send
alias recv(channel) = sync_recv
alias can_recv(channel) = sync_can_recv
alias len(channel) = sync_len
alias cpu_count() = sync_cpu_count
}
namespace Huge {
alias from(value) = huge_from
alias add(a, b) = huge_add
alias sub(a, b) = huge_sub
alias mul(a, b) = huge_mul
alias neg(a) = huge_neg
alias cmp(a, b) = huge_cmp
alias sign(a) = huge_sign
alias mantissa(a) = huge_mantissa
alias exp(a) = huge_exp
alias str(a) = huge_str
}
namespace Angle {
alias from_degrees(d) = angle_from_degrees
alias to_degrees(a) = angle_to_degrees
alias wrap(a) = angle_wrap
alias sin(a) = angle_sin
alias cos(a) = angle_cos
alias add(a, b) = angle_add
alias diff(a, b) = angle_diff
alias diff_degrees(a, b) = angle_diff_degrees # signed, -180..180, ints
alias lerp(a, b, t) = angle_lerp
}
namespace Percent {
alias clamp(v) = percent_clamp
alias of(num, den) = percent_of
alias lerp(a, b, t) = percent_lerp
alias apply(value, p) = percent_apply
}
namespace Grid {
alias line(x0, y0, x1, y1) = grid_line
alias blocked(x, y, wall) = grid_blocked
alias line_of_sight(x0, y0, x1, y1, wall) = grid_line_of_sight
alias flood(x, y, wall) = grid_flood
alias a_star(x0, y0, x1, y1, wall) = path_a_star
}
namespace Light {
alias ambient(color) = light_ambient
alias point(x, y, radius, color, energy) = light_point
alias spot(x, y, radius, color, energy, direction, spread) = light_spot
alias occlude(x, y, width, height) = light_occlude
alias clear_occluders() = light_clear_occluders
alias falloff(exponent) = light_set_falloff
alias soft(radius) = light_set_soft
alias gel(color) = light_set_gel
alias clear_gel() = light_clear_gel
alias height(height) = light_set_height
alias normal(x, y, width, height, nx, ny) = light_normal_rect
alias clear_normals() = light_clear_normals
alias time_of_day(t) = light_time_of_day
}
namespace Motion {
alias to(entity, from, to, dur, ease) = motion_to
}
namespace Tween {
alias to(from, to, dur, ease) = tween_to
alias chain(handle, to, dur, ease) = tween_chain
alias delay(handle, ticks) = tween_delay
alias value(handle) = tween_value
alias stop(handle) = tween_stop
alias parallel(a, b) = tween_parallel
}
namespace Query {
alias count(prop) = query_count
alias first(prop) = query_first
alias nearest(prop, pos, x_field, y_field, x, y) = query_nearest
alias within(prop, pos, x, y, radius, x_field, y_field) = query_within
}
namespace Reflect {
alias prop(name) = world_prop_id
alias field(prop, name) = world_field_id
alias prop_count() = world_prop_count
alias prop_name(index) = world_prop_name
alias field_count(prop) = world_field_count
alias field_name(prop, index) = world_field_name
alias field_type(prop, index) = world_field_type
alias get(entity, prop, field) = world_get
alias set(entity, prop, field, value) = world_set
alias has(entity, prop) = world_has
alias kind(entity) = world_kind
alias model(name) = world_model_id
# #44 — serialize an entity to a value tree and apply one back (reflect_io.ludic)
alias serialize(entity) = reflect_serialize
alias apply(entity, value) = reflect_apply
}
namespace Value {
alias null() = value_null
alias int(n) = value_int
alias fixed(f) = value_fixed
alias bool(b) = value_bool
alias str(s) = value_str
alias list() = value_list
alias object() = value_object
alias add(list, item) = value_add
alias put(obj, key, item) = value_put
alias get(obj, key) = value_get
alias has(obj, key) = value_has
alias at(list, index) = value_at
alias key_at(obj, index) = value_key_at
alias count(value) = value_count
alias kind(value) = value_kind
alias as_int(value) = value_as_int
alias as_str(value) = value_as_str
}
namespace Json {
alias encode(value) = json_encode
alias parse(text) = json_parse
}
namespace Xml {
alias parse(text) = xml_parse
alias tag(node) = xml_tag
alias text(node) = xml_text
alias attr(node, key) = xml_attr
alias attr_int(node, key, dflt) = xml_attr_int
alias has(node, key) = xml_has
alias attr_count(node) = xml_attr_count
alias child_count(node) = xml_child_count
alias child(node, index) = xml_child
alias find(node, tag) = xml_find
alias count(node, tag) = xml_count
}
namespace Base64 {
alias decode(src) = base64_decode
alias encode(src) = base64_encode
}
namespace Tiled {
alias read(path) = tiled_read
alias read_tsx(path) = tiled_read_tsx
alias load(path) = tiled_load
alias gid(map, layer, x, y) = tmap_gid
alias resolve(map, gid) = tmap_resolve
alias width(map) = tmap_width
alias height(map) = tmap_height
alias layer_count(map) = tmap_layer_count
alias layer_name(map, index) = tmap_layer_name
alias draw(map, camx, camy) = tmap_draw
alias draw_anim(map, camx, camy, frame) = tmap_draw_anim
alias frame_gid(map, gid, frame) = tmap_frame_gid
alias animated(map, gid) = tmap_is_animated
alias project(map, layer) = tmap_project
alias collide(map, layer) = tmap_collide
alias collision_kind(map, gid) = tmap_collision_kind
alias tree(map) = tmap_tree
alias tile_prop(map, gid, name) = tmap_tile_prop
alias tile_shapes(map, gid) = tmap_tile_has_shapes
# P4 (#72): objects / properties / custom types / templates / opt-in spawn
alias object_count(map, layer) = tmap_object_count
alias object(map, layer, index) = tmap_object
alias object_shape(object) = tiled_object_shape
alias prop(container, name) = tiled_prop_str
alias prop_int(container, name) = tiled_prop_int
alias prop_type(container, name) = tiled_prop_type
alias load_types(path) = tiled_load_types
alias template(path) = tiled_read_template
alias spawn(map, object, model) = tiled_spawn_object
alias spawn_layer(map, layer, model) = tiled_spawn_layer
# P5 (#73): orientation coords + image/group-layer accessors
alias cell_x(map, x, y) = tmap_cell_sx
alias cell_y(map, x, y) = tmap_cell_sy
alias layer_kind(map, layer) = tmap_layer_kind
alias layer_opacity(map, layer) = tmap_layer_opacity
alias layer_tint(map, layer) = tmap_layer_tint
alias layer_offsetx(map, layer) = tmap_layer_offsetx
alias layer_offsety(map, layer) = tmap_layer_offsety
# P6 (#74): .world stitching
alias world(path) = tiled_read_world
alias world_count(world) = tiled_world_count
alias world_map(world, index) = tiled_world_map
}

View file

@ -300,80 +300,19 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
} }
perr(`unknown builtin Pool.{meth}`) perr(`unknown builtin Pool.{meth}`)
} }
# L6: a method declared by `alias` in a namespace block - the engine's own in
# runtime/native/namespaces.ludic, a package's in its files - is a call to its target
let al = ns_alias_find(ns, meth)
if al >= 0 { return emit_alias_call(al, e) }
var bare: pointer = null var bare: pointer = null
let labels = new []pointer 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 == "bar") { bare = "bar"; push(labels, "x"); push(labels, "y"); push(labels, "width"); push(labels, "height"); push(labels, "value"); push(labels, "max"); push(labels, "color"); push(labels, "back") }
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") }
}
# Sprite.* / Assets.* — the namespaced spritesheet / atlas API (#81), a runtime # Sprite.* / Assets.* — the namespaced spritesheet / atlas API (#81), a runtime
# in atlas.ludic over the variable-size image loader. A cell (or multi-cell span) # in atlas.ludic over the variable-size image loader. A cell (or multi-cell span)
# is a sub-rect of a loaded sheet; draws go through rt_put_px so camera/zoom/clip # is a sub-rect of a loaded sheet; draws go through rt_put_px so camera/zoom/clip
# apply. Distinct from the `Sprite` engine component (#85) — same name, different # apply. Distinct from the `Sprite` engine component (#85) — same name, different
# namespace (a namespaced builtin, never an entity). # namespace (a namespaced builtin, never an entity).
if (ns == "Sprite") {
if (meth == "sheet") { bare = "atlas_sheet"; push(labels, "path"); push(labels, "cellw"); push(labels, "cellh") }
if (meth == "cell") { bare = "atlas_cell"; push(labels, "sheet"); push(labels, "col"); push(labels, "row") }
if (meth == "cell_span") { bare = "atlas_cell_span"; push(labels, "sheet"); push(labels, "col"); push(labels, "row"); push(labels, "cols"); push(labels, "rows") }
if (meth == "strip") { bare = "atlas_strip"; push(labels, "sheet"); push(labels, "col"); push(labels, "row"); push(labels, "count"); push(labels, "rows") }
if (meth == "define") { bare = "atlas_define"; push(labels, "name"); push(labels, "sheet"); push(labels, "col"); push(labels, "row") }
if (meth == "named") { bare = "atlas_named"; push(labels, "name") }
if (meth == "draw") { bare = "atlas_draw"; push(labels, "id"); push(labels, "x"); push(labels, "y") }
if (meth == "draw_scaled") { bare = "atlas_draw_scaled"; push(labels, "id"); push(labels, "x"); push(labels, "y"); push(labels, "scale") }
if (meth == "draw_meter") { bare = "atlas_draw_meter"; push(labels, "x"); push(labels, "y"); push(labels, "value"); push(labels, "max"); push(labels, "per_icon"); push(labels, "spacing"); push(labels, "full"); push(labels, "half"); push(labels, "empty") }
if (meth == "width") { bare = "atlas_width"; push(labels, "id") }
if (meth == "height") { bare = "atlas_height"; push(labels, "id") }
}
if (ns == "Font") {
if (meth == "load") { bare = "font_load"; push(labels, "path") }
}
# Ui.* — the retained UI (ui blocks): navigation is engine-ticked (esys_ui) and # Ui.* — the retained UI (ui blocks): navigation is engine-ticked (esys_ui) and
# activations arrive as UiClicked events; these are the remaining verbs. # activations arrive as UiClicked events; these are the remaining verbs.
if (ns == "Ui") {
if (meth == "build") { bare = "ui_build" }
if (meth == "open") { bare = "ui_open"; push(labels, "id") }
if (meth == "close") { bare = "ui_close" }
if (meth == "tick") { bare = "ui_tick"; push(labels, "key") }
if (meth == "clicked") { bare = "ui_clicked"; push(labels, "id") }
if (meth == "set_text") { bare = "ui_set_text"; push(labels, "id"); push(labels, "text") }
if (meth == "render") { bare = "ui_render" }
}
if (ns == "File") {
if (meth == "open") { bare = "file_open"; push(labels, "path"); push(labels, "mode") }
if (meth == "read") { bare = "file_read"; push(labels, "file"); push(labels, "buffer"); push(labels, "count") }
if (meth == "write") { bare = "file_write"; push(labels, "file"); push(labels, "buffer"); push(labels, "count") }
if (meth == "seek") { bare = "file_seek"; push(labels, "file"); push(labels, "offset"); push(labels, "whence") }
if (meth == "tell") { bare = "file_tell"; push(labels, "file") }
if (meth == "close") { bare = "file_close"; push(labels, "file") }
}
if (ns == "Fx") { # engine-owned sparks and floating numbers (fx.ludic)
if (meth == "sparks") { bare = "fx_sparks"; push(labels, "x"); push(labels, "y"); push(labels, "color"); push(labels, "count") }
if (meth == "number") { bare = "fx_number"; push(labels, "x"); push(labels, "y"); push(labels, "value"); push(labels, "color") }
if (meth == "clear") { bare = "fx_clear" }
}
if (ns == "Prefab") { # spawn a prefab by name at runtime (see emit_prefab_fns) if (ns == "Prefab") { # spawn a prefab by name at runtime (see emit_prefab_fns)
if (meth == "spawn_at") { # Prefab.spawn_at(name:, at: IVec2) — spawned, then placed if (meth == "spawn_at") { # Prefab.spawn_at(name:, at: IVec2) — spawned, then placed
if not has_prefabs() { perr("Prefab.spawn_at: the program declares no prefab") } if not has_prefabs() { perr("Prefab.spawn_at: the program declares no prefab") }
@ -404,243 +343,29 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
} }
perr(`unknown builtin Prefab.{meth}`) perr(`unknown builtin Prefab.{meth}`)
} }
if (ns == "Assets") {
if (meth == "font") { bare = "assets_font"; push(labels, "name") } # a .ttf loaded through the queue
if (meth == "enqueue_dir") { bare = "assets_enqueue_dir"; push(labels, "path") } # every file of a directory, by base name
if (meth == "image") { bare = "atlas_image"; push(labels, "path") }
if (meth == "load") { bare = "atlas_image"; push(labels, "path") } # alias
if (meth == "get") { bare = "atlas_named"; push(labels, "name") } # alias of Sprite.named
# #82 — incremental preload queue + progress for a loading scene.
if (meth == "enqueue") { bare = "assets_enqueue"; push(labels, "name"); push(labels, "path") }
if (meth == "pump") { bare = "assets_pump"; push(labels, "max") }
if (meth == "total") { bare = "assets_total" }
if (meth == "loaded") { bare = "assets_loaded" }
if (meth == "ready") { bare = "assets_ready" }
if (meth == "progress") { bare = "assets_progress" }
}
# Camera.* — the world-space camera: a draw offset threaded through the render # 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 # path (runtime/native/core.ludic). set/follow move it; shake jitters it from
# the seeded RNG, so a replay shakes identically. # 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 (meth == "shake_for") { bare = "camera_shake_for"; push(labels, "amount"); push(labels, "frames") } # a timed shake the engine decays
if (meth == "zoom") { bare = "camera_zoom"; push(labels, "scale") } # #78 deterministic Q16.16 zoom
}
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 (meth == "get") { bare = "tile"; push(labels, "x"); push(labels, "y") }
# the cell API: edit the grid in place, ask it what is where
if (meth == "set") { bare = "map_set"; push(labels, "x"); push(labels, "y"); push(labels, "glyph") }
if (meth == "fill") { bare = "map_fill"; push(labels, "glyph") }
if (meth == "rect") { bare = "map_rect"; push(labels, "x"); push(labels, "y"); push(labels, "width"); push(labels, "height"); push(labels, "glyph") }
if (meth == "border") { bare = "map_border"; push(labels, "glyph") }
if (meth == "random_cell") { bare = "map_random_cell"; push(labels, "glyph") }
if (meth == "random_cell_far") { bare = "map_random_cell_far"; push(labels, "glyph"); push(labels, "from"); push(labels, "min_tiles") }
if (meth == "is_solid") { bare = "map_is_solid"; push(labels, "x"); push(labels, "y") }
if (meth == "is_solid_at") { bare = "map_is_solid_at"; push(labels, "x"); push(labels, "y") }
if (meth == "to_tile") { bare = "map_to_tile"; push(labels, "pixel") }
if (meth == "width") { bare = "map_width" }
if (meth == "height") { bare = "map_height" }
}
if (ns == "Random") {
if (meth == "range") { bare = "rng_range"; push(labels, "low"); push(labels, "high") }
if (meth == "weighted") { bare = "rng_weighted"; push(labels, "weights") } # an index in proportion to its weight
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" }
# action maps + deterministic record/replay (#7) — spliced runtime in
# runtime/native/input.ludic, reached as ordinary @fn_input_* calls.
if (meth == "bind") { bare = "input_bind"; push(labels, "action"); push(labels, "key") }
if (meth == "rebind") { bare = "input_rebind"; push(labels, "action"); push(labels, "old"); push(labels, "new") }
if (meth == "poll") { bare = "input_poll" }
if (meth == "down") { bare = "input_down" }
if (meth == "pressed") { bare = "input_pressed" }
if (meth == "record") { bare = "input_record" }
if (meth == "replay") { bare = "input_replay" }
# Input Manager (#83): default bindings + device-agnostic actions that read the
# multi-key device layer (the frame loop now commits it automatically).
if (meth == "action") { bare = "input_default"; push(labels, "action"); push(labels, "key") }
if (meth == "bind_pad") { bare = "input_bind_pad"; push(labels, "action"); push(labels, "button") }
if (meth == "active") { bare = "input_active"; push(labels, "action") }
if (meth == "just_pressed") { bare = "input_just_pressed"; push(labels, "action") }
if (meth == "just_released") { bare = "input_just_released"; push(labels, "action") }
# device layer (#50): multi-key held state, analog axes/vectors, mouse,
# gamepads, touch — reached as ordinary @fn_input_* calls into input.ludic.
if (meth == "key_down") { bare = "input_key_down"; push(labels, "key") }
if (meth == "key_pressed") { bare = "input_key_pressed"; push(labels, "key") }
if (meth == "key_released") { bare = "input_key_released"; push(labels, "key") }
if (meth == "key_label") { bare = "input_key_label"; push(labels, "key") }
if (meth == "text") { bare = "input_text" } # what was TYPED, UTF-8
if (meth == "press") { bare = "input_press"; push(labels, "key") }
if (meth == "release") { bare = "input_release"; push(labels, "key") }
if (meth == "axis") { bare = "input_axis"; push(labels, "neg"); push(labels, "pos") }
if (meth == "axis_i") { bare = "input_axis_i"; push(labels, "neg"); push(labels, "pos") } # #79 directional int (-1/0/1)
if (meth == "move_i") { bare = "input_move_i" } # WASD / arrows / left stick -> IVec2
if (meth == "vector") { bare = "input_vector"; push(labels, "left"); push(labels, "right"); push(labels, "up"); push(labels, "down") }
if (meth == "strength") { bare = "input_strength"; push(labels, "action") }
if (meth == "mouse_x") { bare = "input_mouse_x" }
if (meth == "mouse_y") { bare = "input_mouse_y" }
if (meth == "mouse_dx") { bare = "input_mouse_dx" }
if (meth == "mouse_dy") { bare = "input_mouse_dy" }
if (meth == "mouse_down") { bare = "input_mouse_down"; push(labels, "button") }
if (meth == "wheel") { bare = "input_wheel" }
if (meth == "set_mouse") { bare = "input_set_mouse"; push(labels, "x"); push(labels, "y"); push(labels, "buttons"); push(labels, "wheel") }
if (meth == "pad_connected") { bare = "input_pad_connected"; push(labels, "pad") }
if (meth == "pad_button") { bare = "input_pad_button"; push(labels, "pad"); push(labels, "button") }
if (meth == "pad_axis") { bare = "input_pad_axis"; push(labels, "pad"); push(labels, "axis") }
if (meth == "set_pad") { bare = "input_set_pad"; push(labels, "pad"); push(labels, "connected"); push(labels, "buttons"); push(labels, "lx"); push(labels, "ly"); push(labels, "rx"); push(labels, "ry") }
if (meth == "touch_count") { bare = "input_touch_count" }
if (meth == "touch_x") { bare = "input_touch_x"; push(labels, "index") }
if (meth == "touch_y") { bare = "input_touch_y"; push(labels, "index") }
if (meth == "set_touch") { bare = "input_set_touch"; push(labels, "index"); push(labels, "x"); push(labels, "y"); push(labels, "active") }
if (meth == "cursor_mode") { bare = "input_cursor_mode"; push(labels, "mode") } # #89 cursor capture
}
# Audio.* (#22) — sfx/music playback over the platform audio backend # Audio.* (#22) — sfx/music playback over the platform audio backend
# (runtime/native/audio.ludic + audio.ll). Playback is out-of-band; the # (runtime/native/audio.ludic + audio.ll). Playback is out-of-band; the
# triggers are ordinary frame-driven calls, so a replay fires the same sounds. # triggers are ordinary frame-driven calls, so a replay fires the same sounds.
if (ns == "Audio") { if (ns == "Audio") {
if (meth == "load") { bare = "audio_load"; push(labels, "path") }
if (meth == "define") { bare = "audio_define"; push(labels, "name"); push(labels, "path") }
if (meth == "named") { bare = "audio_named"; push(labels, "name") }
# Audio.play / play_music take a handle, or a name from the sound bank (Audio.define) # Audio.play / play_music take a handle, or a name from the sound bank (Audio.define)
if (meth == "play") { if first_arg_is_text(e) { bare = "audio_play_named"; push(labels, "name") } else { bare = "audio_play"; push(labels, "id") } } if (meth == "play") { if first_arg_is_text(e) { bare = "audio_play_named"; push(labels, "name") } else { bare = "audio_play"; push(labels, "id") } }
if (meth == "play_sound") { bare = "audio_play"; push(labels, "id") }
# Audio.play_at(id, gain:, pitch:, pan:) — one shot with its own gain, pitch and
# stereo position. Audio.volume / Audio.pitch stay global (they are the options
# screen); this is the per-voice one, and it is what distance attenuation is built on.
if (meth == "play_at") { bare = "audio_play_at"; push(labels, "id"); push(labels, "gain"); push(labels, "pitch"); push(labels, "pan") }
if (meth == "play_music") { if first_arg_is_text(e) { bare = "audio_play_music_named"; push(labels, "name") } else { bare = "audio_play_music"; push(labels, "id") } } if (meth == "play_music") { if first_arg_is_text(e) { bare = "audio_play_music_named"; push(labels, "name") } else { bare = "audio_play_music"; push(labels, "id") } }
if (meth == "stop") { bare = "audio_stop"; push(labels, "id") }
if (meth == "stop_music") { bare = "audio_stop_music" }
if (meth == "stop_all") { bare = "audio_stop_all" }
if (meth == "volume") { bare = "audio_volume"; push(labels, "v") }
if (meth == "pitch") { bare = "audio_pitch"; push(labels, "v") }
if (meth == "is_playing") { bare = "audio_is_playing"; push(labels, "id") }
} }
# Http.* (#6) — a poll-based HTTP/HTTPS client (runtime/native/http.ludic + # Http.* (#6) — a poll-based HTTP/HTTPS client (runtime/native/http.ludic +
# http.ll). Out-of-band, never part of the deterministic sim. Pairs with Json.* # http.ll). Out-of-band, never part of the deterministic sim. Pairs with Json.*
# (#44) for (de)serialization: Json.parse(Http.text(h)). # (#44) for (de)serialization: Json.parse(Http.text(h)).
if (ns == "Http") {
if (meth == "get") { bare = "http_get"; push(labels, "url") }
if (meth == "post") { bare = "http_post"; push(labels, "url"); push(labels, "body") }
if (meth == "request") { bare = "http_request"; push(labels, "method"); push(labels, "url"); push(labels, "body") }
if (meth == "open") { bare = "http_open"; push(labels, "method"); push(labels, "url") }
if (meth == "set") { bare = "http_set_header"; push(labels, "handle"); push(labels, "name"); push(labels, "value") }
if (meth == "body") { bare = "http_body"; push(labels, "handle"); push(labels, "body") }
if (meth == "body_bytes") { bare = "http_body_n"; push(labels, "handle"); push(labels, "bytes"); push(labels, "len") }
if (meth == "send") { bare = "http_send_req"; push(labels, "handle") }
if (meth == "poll") { bare = "http_poll"; push(labels, "handle") }
if (meth == "status") { bare = "http_status_of"; push(labels, "handle") }
if (meth == "ok") { bare = "http_ok"; push(labels, "handle") }
if (meth == "text") { bare = "http_text"; push(labels, "handle") }
if (meth == "body_len") { bare = "http_body_len"; push(labels, "handle") }
if (meth == "header") { bare = "http_header_of"; push(labels, "handle"); push(labels, "name") }
if (meth == "free") { bare = "http_close"; push(labels, "handle") }
if (meth == "parse") { bare = "http_parse"; push(labels, "bytes"); push(labels, "len") }
if (meth == "save_to") { bare = "http_save_to"; push(labels, "handle"); push(labels, "path") }
if (meth == "received") { bare = "http_received"; push(labels, "handle") }
if (meth == "expected") { bare = "http_expected"; push(labels, "handle") }
}
# Udp.* — polled IPv4 datagrams (runtime/native/udp.ludic + udp.ll / udp_win.ll). # Udp.* — polled IPv4 datagrams (runtime/native/udp.ludic + udp.ll / udp_win.ll).
# Out-of-band like Http.*: the transport under a game's own netcode. # Out-of-band like Http.*: the transport under a game's own netcode.
if (ns == "Udp") {
if (meth == "open") { bare = "udp_open"; push(labels, "port") }
if (meth == "port") { bare = "udp_port"; push(labels, "socket") }
if (meth == "send") { bare = "udp_send"; push(labels, "socket"); push(labels, "ip"); push(labels, "port"); push(labels, "bytes"); push(labels, "len") }
if (meth == "recv") { bare = "udp_recv"; push(labels, "socket"); push(labels, "bytes"); push(labels, "cap") }
if (meth == "from_ip") { bare = "udp_from_ip"; push(labels, "socket") }
if (meth == "from_port") { bare = "udp_from_port"; push(labels, "socket") }
if (meth == "close") { bare = "udp_close"; push(labels, "socket") }
if (meth == "resolve") { bare = "udp_resolve"; push(labels, "name") }
if (meth == "local_ip") { bare = "udp_local_ip" }
if (meth == "ip") { bare = "udp_ip"; push(labels, "text") }
if (meth == "ip_text") { bare = "udp_ip_text"; push(labels, "ip") }
}
# Process.* — child processes, started and polled (runtime/native/process.ludic + # Process.* — child processes, started and polled (runtime/native/process.ludic +
# process.ll / process_win.ll). Out-of-band, like Http.*. # process.ll / process_win.ll). Out-of-band, like Http.*.
if (ns == "Process") {
if (meth == "spawn") { bare = "process_spawn"; push(labels, "path"); push(labels, "args") }
if (meth == "poll") { bare = "process_poll"; push(labels, "handle") }
if (meth == "kill") { bare = "process_kill"; push(labels, "handle") }
if (meth == "free") { bare = "process_free"; push(labels, "handle") }
}
# Phase 3: the bare reflection / networking / process builtins, namespaced. # Phase 3: the bare reflection / networking / process builtins, namespaced.
# Each is a pure alias — the callee is rewritten to the bare name below. # 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 == "despawn") { bare = "world_despawn" } # #84 — despawn an entity by id (runs @OnDespawn + frees)
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 # Regex.* -> the regex_* engine functions (spliced from runtime/native/regex*.ludic
# when a program mentions Regex.*). Each is a plain alias; the engine functions # 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_*. # 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 # Grid.* — tile geometry and pathfinding over the Map tilemap, from
# runtime/native/grid.ludic (spliced with core.ludic). `wall` is the impassable # runtime/native/grid.ludic (spliced with core.ludic). `wall` is the impassable
# tile char, e.g. '#'. line/flood/a_star return []Cell slices. (Pathfinding # tile char, e.g. '#'. line/flood/a_star return []Cell slices. (Pathfinding
@ -650,162 +375,24 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
# spliced on demand). These are ordinary Ludic functions, so the generic call # spliced on demand). These are ordinary Ludic functions, so the generic call
# path resolves them to @fn_bigint_* / @fn_decimal_* and keeps their return # path resolves them to @fn_bigint_* / @fn_decimal_* and keeps their return
# types (BigNum / Dec / int / bool / string). # types (BigNum / Dec / int / bool / string).
if (ns == "BigInt") {
if (meth == "from") { bare = "bigint_from"; push(labels, "value") }
if (meth == "parse") { bare = "bigint_from_str"; push(labels, "text") }
if (meth == "add") { bare = "bigint_add"; push(labels, "a"); push(labels, "b") }
if (meth == "sub") { bare = "bigint_sub"; push(labels, "a"); push(labels, "b") }
if (meth == "mul") { bare = "bigint_mul"; push(labels, "a"); push(labels, "b") }
if (meth == "neg") { bare = "bigint_neg"; push(labels, "a") }
if (meth == "pow") { bare = "bigint_pow"; push(labels, "a"); push(labels, "exp") }
if (meth == "div") { bare = "bigint_div_int"; push(labels, "a"); push(labels, "d") }
if (meth == "mod") { bare = "bigint_mod_int"; push(labels, "a"); push(labels, "d") }
if (meth == "cmp") { bare = "bigint_cmp"; push(labels, "a"); push(labels, "b") }
if (meth == "eq") { bare = "bigint_eq"; push(labels, "a"); push(labels, "b") }
if (meth == "is_zero") { bare = "bigint_is_zero"; push(labels, "a") }
if (meth == "to_int") { bare = "bigint_to_int"; push(labels, "a") }
if (meth == "str") { bare = "bigint_str"; push(labels, "a") }
}
if (ns == "Decimal") {
if (meth == "from") { bare = "decimal_from"; push(labels, "value") }
if (meth == "parse") { bare = "decimal_from_str"; push(labels, "text") }
if (meth == "add") { bare = "decimal_add"; push(labels, "a"); push(labels, "b") }
if (meth == "sub") { bare = "decimal_sub"; push(labels, "a"); push(labels, "b") }
if (meth == "mul") { bare = "decimal_mul"; push(labels, "a"); push(labels, "b") }
if (meth == "neg") { bare = "decimal_neg"; push(labels, "a") }
if (meth == "cmp") { bare = "decimal_cmp"; push(labels, "a"); push(labels, "b") }
if (meth == "eq") { bare = "decimal_eq"; push(labels, "a"); push(labels, "b") }
if (meth == "scale") { bare = "decimal_scale"; push(labels, "d") }
if (meth == "rescale") { bare = "decimal_rescale"; push(labels, "d"); push(labels, "places") }
if (meth == "str") { bare = "decimal_str"; push(labels, "d") }
}
# Dict.* / Set.* -> the hash-table engine (runtime/native/dict.ludic, spliced # Dict.* / Set.* -> the hash-table engine (runtime/native/dict.ludic, spliced
# on demand). Ordinary Ludic functions, so the generic call path resolves them # on demand). Ordinary Ludic functions, so the generic call path resolves them
# to @fn_dict_* / @fn_set_* and keeps their return types (Dict / int / bool / # to @fn_dict_* / @fn_set_* and keeps their return types (Dict / int / bool /
# []pointer). # []pointer).
if (ns == "Dict") {
if (meth == "new") { bare = "dict_new" }
if (meth == "set") { bare = "dict_set"; push(labels, "d"); push(labels, "key"); push(labels, "value") }
if (meth == "get") { bare = "dict_get"; push(labels, "d"); push(labels, "key") }
if (meth == "get_or") { bare = "dict_get_or"; push(labels, "d"); push(labels, "key"); push(labels, "fallback") }
if (meth == "has") { bare = "dict_has"; push(labels, "d"); push(labels, "key") }
if (meth == "remove") { bare = "dict_remove"; push(labels, "d"); push(labels, "key") }
if (meth == "size") { bare = "dict_size"; push(labels, "d") }
if (meth == "clear") { bare = "dict_clear"; push(labels, "d") }
if (meth == "keys") { bare = "dict_keys"; push(labels, "d") }
}
if (ns == "Set") {
if (meth == "new") { bare = "set_new" }
if (meth == "add") { bare = "set_add"; push(labels, "s"); push(labels, "key") }
if (meth == "has") { bare = "set_has"; push(labels, "s"); push(labels, "key") }
if (meth == "remove") { bare = "set_remove"; push(labels, "s"); push(labels, "key") }
if (meth == "size") { bare = "set_size"; push(labels, "s") }
if (meth == "clear") { bare = "set_clear"; push(labels, "s") }
if (meth == "members") { bare = "set_members"; push(labels, "s") }
}
# Job.* / Promise.* / Sync.* -> the concurrency runtime (runtime/native/jobs.ludic, # Job.* / Promise.* / Sync.* -> the concurrency runtime (runtime/native/jobs.ludic,
# spliced on demand). Ordinary Ludic functions, so the generic call path keeps # spliced on demand). Ordinary Ludic functions, so the generic call path keeps
# their return types (int / bool). The safe tier (Job/Promise) is a deterministic # their return types (int / bool). The safe tier (Job/Promise) is a deterministic
# cooperative scheduler; Sync.* is the advanced, opt-in message-passing tier. #14. # cooperative scheduler; Sync.* is the advanced, opt-in message-passing tier. #14.
if (ns == "Job") { if (ns == "Job") {
if (meth == "defer") { bare = "job_defer" }
if (meth == "run") { bare = "job_run"; push(labels, "kind"); push(labels, "arg") }
if (meth == "fulfill") { bare = "job_fulfill"; push(labels, "handle"); push(labels, "value") }
if (meth == "fail") { bare = "job_fail"; push(labels, "handle"); push(labels, "error") }
if (meth == "cancel") { bare = "job_cancel"; push(labels, "handle") }
if (meth == "pump") { bare = "job_pump"; push(labels, "budget") }
if (meth == "done") { bare = "job_done"; push(labels, "handle") }
if (meth == "ok") { bare = "job_ok"; push(labels, "handle") }
if (meth == "failed") { bare = "job_failed"; push(labels, "handle") }
if (meth == "cancelled") { bare = "job_cancelled"; push(labels, "handle") }
if (meth == "result") { bare = "job_result"; push(labels, "handle") }
if (meth == "error") { bare = "job_error"; push(labels, "handle") }
if (meth == "pending") { bare = "job_pending" }
if (meth == "free") { bare = "job_free"; push(labels, "handle") }
if (meth == "parallel_for") { bare = "job_parallel_for"; push(labels, "count"); push(labels, "work"); push(labels, "ctx"); if len(e.kids) > 1 { check_worker_ref(e.kids[1]) } } if (meth == "parallel_for") { bare = "job_parallel_for"; push(labels, "count"); push(labels, "work"); push(labels, "ctx"); if len(e.kids) > 1 { check_worker_ref(e.kids[1]) } }
if (meth == "is_worker") { bare = "job_is_worker" }
}
if (ns == "Promise") {
if (meth == "all") { bare = "prom_all"; push(labels, "handles") }
if (meth == "race") { bare = "prom_race"; push(labels, "handles") }
if (meth == "count_done") { bare = "prom_count_done"; push(labels, "handles") }
if (meth == "all_done") { bare = "prom_all_done"; push(labels, "handles") }
}
if (ns == "Sync") {
if (meth == "mutex") { bare = "sync_mutex" }
if (meth == "lock") { bare = "sync_lock"; push(labels, "mutex") }
if (meth == "unlock") { bare = "sync_unlock"; push(labels, "mutex") }
if (meth == "try_lock") { bare = "sync_try_lock"; push(labels, "mutex") }
if (meth == "atomic") { bare = "sync_atomic" }
if (meth == "get") { bare = "sync_get"; push(labels, "atomic") }
if (meth == "set") { bare = "sync_set"; push(labels, "atomic"); push(labels, "value") }
if (meth == "add") { bare = "sync_add"; push(labels, "atomic"); push(labels, "delta") }
if (meth == "cas") { bare = "sync_cas"; push(labels, "atomic"); push(labels, "expect"); push(labels, "next") }
if (meth == "channel") { bare = "sync_channel" }
if (meth == "send") { bare = "sync_send"; push(labels, "channel"); push(labels, "value") }
if (meth == "recv") { bare = "sync_recv"; push(labels, "channel") }
if (meth == "can_recv") { bare = "sync_can_recv"; push(labels, "channel") }
if (meth == "len") { bare = "sync_len"; push(labels, "channel") }
if (meth == "cpu_count") { bare = "sync_cpu_count" }
} }
# Huge.* / Angle.* / Percent.* -> the numeric runtime (runtime/native/numeric.ludic, # Huge.* / Angle.* / Percent.* -> the numeric runtime (runtime/native/numeric.ludic,
# spliced on demand). Ordinary Ludic functions, so the generic call path keeps # spliced on demand). Ordinary Ludic functions, so the generic call path keeps
# their return types (Huge / fixed / int / bool). # their return types (Huge / fixed / int / bool).
if (ns == "Huge") {
if (meth == "from") { bare = "huge_from"; push(labels, "value") }
if (meth == "add") { bare = "huge_add"; push(labels, "a"); push(labels, "b") }
if (meth == "sub") { bare = "huge_sub"; push(labels, "a"); push(labels, "b") }
if (meth == "mul") { bare = "huge_mul"; push(labels, "a"); push(labels, "b") }
if (meth == "neg") { bare = "huge_neg"; push(labels, "a") }
if (meth == "cmp") { bare = "huge_cmp"; push(labels, "a"); push(labels, "b") }
if (meth == "sign") { bare = "huge_sign"; push(labels, "a") }
if (meth == "mantissa") { bare = "huge_mantissa"; push(labels, "a") }
if (meth == "exp") { bare = "huge_exp"; push(labels, "a") }
if (meth == "str") { bare = "huge_str"; push(labels, "a") }
}
if (ns == "Angle") {
if (meth == "from_degrees") { bare = "angle_from_degrees"; push(labels, "d") }
if (meth == "to_degrees") { bare = "angle_to_degrees"; push(labels, "a") }
if (meth == "wrap") { bare = "angle_wrap"; push(labels, "a") }
if (meth == "sin") { bare = "angle_sin"; push(labels, "a") }
if (meth == "cos") { bare = "angle_cos"; push(labels, "a") }
if (meth == "add") { bare = "angle_add"; push(labels, "a"); push(labels, "b") }
if (meth == "diff") { bare = "angle_diff"; push(labels, "a"); push(labels, "b") }
if (meth == "diff_degrees") { bare = "angle_diff_degrees"; push(labels, "a"); push(labels, "b") } # signed, -180..180, ints
if (meth == "lerp") { bare = "angle_lerp"; push(labels, "a"); push(labels, "b"); push(labels, "t") }
}
if (ns == "Percent") {
if (meth == "clamp") { bare = "percent_clamp"; push(labels, "v") }
if (meth == "of") { bare = "percent_of"; push(labels, "num"); push(labels, "den") }
if (meth == "lerp") { bare = "percent_lerp"; push(labels, "a"); push(labels, "b"); push(labels, "t") }
if (meth == "apply") { bare = "percent_apply"; push(labels, "value"); push(labels, "p") }
}
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 # 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 # 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 # down, point adds a radial glow (blocked by occluders -> hard shadows). Screen
# space, deterministic (integer + Q16.16), diffable. `energy` is a fixed. # 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 == "spot") { bare = "light_spot"; push(labels, "x"); push(labels, "y"); push(labels, "radius"); push(labels, "color"); push(labels, "energy"); push(labels, "direction"); push(labels, "spread") }
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" }
if (meth == "falloff") { bare = "light_set_falloff"; push(labels, "exponent") }
if (meth == "soft") { bare = "light_set_soft"; push(labels, "radius") }
if (meth == "gel") { bare = "light_set_gel"; push(labels, "color") }
if (meth == "clear_gel") { bare = "light_clear_gel" }
if (meth == "height") { bare = "light_set_height"; push(labels, "height") }
if (meth == "normal") { bare = "light_normal_rect"; push(labels, "x"); push(labels, "y"); push(labels, "width"); push(labels, "height"); push(labels, "nx"); push(labels, "ny") }
if (meth == "clear_normals") { bare = "light_clear_normals" }
if (meth == "time_of_day") { bare = "light_time_of_day"; push(labels, "t") }
}
# Anim.* / Motion.* ergonomic writes over the engine components (#48), spliced # Anim.* / Motion.* ergonomic writes over the engine components (#48), spliced
# from runtime/native/systems.ludic. Anim.play(entity, "run") plays a named clip # from runtime/native/systems.ludic. Anim.play(entity, "run") plays a named clip
# registered with Anim.clip; the four-arg Anim.play sets fps/frames/mode # registered with Anim.clip; the four-arg Anim.play sets fps/frames/mode
@ -820,147 +407,29 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
else { bare = "anim_play"; push(labels, "entity"); push(labels, "fps"); push(labels, "frames"); push(labels, "mode") } else { bare = "anim_play"; push(labels, "entity"); push(labels, "fps"); push(labels, "frames"); push(labels, "mode") }
} }
} }
if (ns == "Motion") {
if (meth == "to") { bare = "motion_to"; push(labels, "entity"); push(labels, "from"); push(labels, "to"); push(labels, "dur"); push(labels, "ease") }
}
# Tween.* fluent stateful handles (#48), spliced from runtime/native/tween.ludic # Tween.* fluent stateful handles (#48), spliced from runtime/native/tween.ludic
# and advanced each Update tick by esys_tween. These stand alongside the pure # and advanced each Update tick by esys_tween. These stand alongside the pure
# Tween.* interpolators (emit_anim.ludic): the stateful ones take/return a handle. # Tween.* interpolators (emit_anim.ludic): the stateful ones take/return a handle.
if (ns == "Tween") {
if (meth == "to") { bare = "tween_to"; push(labels, "from"); push(labels, "to"); push(labels, "dur"); push(labels, "ease") }
if (meth == "chain") { bare = "tween_chain"; push(labels, "handle"); push(labels, "to"); push(labels, "dur"); push(labels, "ease") }
if (meth == "delay") { bare = "tween_delay"; push(labels, "handle"); push(labels, "ticks") }
if (meth == "value") { bare = "tween_value"; push(labels, "handle") }
if (meth == "stop") { bare = "tween_stop"; push(labels, "handle") }
if (meth == "parallel") { bare = "tween_parallel"; push(labels, "a"); push(labels, "b") }
}
# Query.* — ECS spatial queries over the reflection ABI (runtime/native/query.ludic, # 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 # 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 = # 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 # none); within returns a []int of entities. A linear scan — ample for the entity
# counts Ludic targets, like the grid pathfinder's open set. # 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). # 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 # 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 # a field by (prop, field) id — the foundation for auto-serialization and debug
# inspectors. Reads the same generated metadata a foreign mod binds. # 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") }
# #44 — serialize an entity to a value tree and apply one back (reflect_io.ludic)
if (meth == "serialize") { bare = "reflect_serialize"; push(labels, "entity") }
if (meth == "apply") { bare = "reflect_apply"; push(labels, "entity"); push(labels, "value") }
}
# Value.* — the generic value tree (runtime/native/value.ludic, spliced on # Value.* — the generic value tree (runtime/native/value.ludic, spliced on
# demand). Nodes are int/fixed/bool/str/list/object; the methods map straight # demand). Nodes are int/fixed/bool/str/list/object; the methods map straight
# to the spliced value_* functions, whose signatures carry the return types. # to the spliced value_* functions, whose signatures carry the return types.
if (ns == "Value") {
if (meth == "null") { bare = "value_null" }
if (meth == "int") { bare = "value_int"; push(labels, "n") }
if (meth == "fixed") { bare = "value_fixed"; push(labels, "f") }
if (meth == "bool") { bare = "value_bool"; push(labels, "b") }
if (meth == "str") { bare = "value_str"; push(labels, "s") }
if (meth == "list") { bare = "value_list" }
if (meth == "object") { bare = "value_object" }
if (meth == "add") { bare = "value_add"; push(labels, "list"); push(labels, "item") }
if (meth == "put") { bare = "value_put"; push(labels, "obj"); push(labels, "key"); push(labels, "item") }
if (meth == "get") { bare = "value_get"; push(labels, "obj"); push(labels, "key") }
if (meth == "has") { bare = "value_has"; push(labels, "obj"); push(labels, "key") }
if (meth == "at") { bare = "value_at"; push(labels, "list"); push(labels, "index") }
if (meth == "key_at") { bare = "value_key_at"; push(labels, "obj"); push(labels, "index") }
if (meth == "count") { bare = "value_count"; push(labels, "value") }
if (meth == "kind") { bare = "value_kind"; push(labels, "value") }
if (meth == "as_int") { bare = "value_as_int"; push(labels, "value") }
if (meth == "as_str") { bare = "value_as_str"; push(labels, "value") }
}
# Json.* — the text bridge over the value tree (runtime/native/value.ludic). # Json.* — the text bridge over the value tree (runtime/native/value.ludic).
if (ns == "Json") {
if (meth == "encode") { bare = "json_encode"; push(labels, "value") }
if (meth == "parse") { bare = "json_parse"; push(labels, "text") }
}
# Xml.* — the minimal XML reader (runtime/native/xml.ludic, spliced on demand). # Xml.* — the minimal XML reader (runtime/native/xml.ludic, spliced on demand).
# parse returns an Xml node; the accessors read tag/text/attributes/children. # parse returns an Xml node; the accessors read tag/text/attributes/children.
if (ns == "Xml") {
if (meth == "parse") { bare = "xml_parse"; push(labels, "text") }
if (meth == "tag") { bare = "xml_tag"; push(labels, "node") }
if (meth == "text") { bare = "xml_text"; push(labels, "node") }
if (meth == "attr") { bare = "xml_attr"; push(labels, "node"); push(labels, "key") }
if (meth == "attr_int") { bare = "xml_attr_int"; push(labels, "node"); push(labels, "key"); push(labels, "dflt") }
if (meth == "has") { bare = "xml_has"; push(labels, "node"); push(labels, "key") }
if (meth == "attr_count") { bare = "xml_attr_count"; push(labels, "node") }
if (meth == "child_count") { bare = "xml_child_count"; push(labels, "node") }
if (meth == "child") { bare = "xml_child"; push(labels, "node"); push(labels, "index") }
if (meth == "find") { bare = "xml_find"; push(labels, "node"); push(labels, "tag") }
if (meth == "count") { bare = "xml_count"; push(labels, "node"); push(labels, "tag") }
}
# Base64.* — standard base64 codec (runtime/native/base64.ludic, spliced on # Base64.* — standard base64 codec (runtime/native/base64.ludic, spliced on
# demand). decode/encode round-trip through NUL-terminated strings. # demand). decode/encode round-trip through NUL-terminated strings.
if (ns == "Base64") {
if (meth == "decode") { bare = "base64_decode"; push(labels, "src") }
if (meth == "encode") { bare = "base64_encode"; push(labels, "src") }
}
# Tiled.* — Tiled map support (runtime/native/tiled.ludic, spliced on demand). # Tiled.* — Tiled map support (runtime/native/tiled.ludic, spliced on demand).
# read/read_tsx produce the intermediate Value tree (#68); load/gid/resolve/ # read/read_tsx produce the intermediate Value tree (#68); load/gid/resolve/
# draw/prop operate on the loaded map model (#69+). # draw/prop operate on the loaded map model (#69+).
if (ns == "Tiled") {
if (meth == "read") { bare = "tiled_read"; push(labels, "path") }
if (meth == "read_tsx") { bare = "tiled_read_tsx"; push(labels, "path") }
if (meth == "load") { bare = "tiled_load"; push(labels, "path") }
if (meth == "gid") { bare = "tmap_gid"; push(labels, "map"); push(labels, "layer"); push(labels, "x"); push(labels, "y") }
if (meth == "resolve") { bare = "tmap_resolve"; push(labels, "map"); push(labels, "gid") }
if (meth == "width") { bare = "tmap_width"; push(labels, "map") }
if (meth == "height") { bare = "tmap_height"; push(labels, "map") }
if (meth == "layer_count") { bare = "tmap_layer_count"; push(labels, "map") }
if (meth == "layer_name") { bare = "tmap_layer_name"; push(labels, "map"); push(labels, "index") }
if (meth == "draw") { bare = "tmap_draw"; push(labels, "map"); push(labels, "camx"); push(labels, "camy") }
if (meth == "draw_anim") { bare = "tmap_draw_anim"; push(labels, "map"); push(labels, "camx"); push(labels, "camy"); push(labels, "frame") }
if (meth == "frame_gid") { bare = "tmap_frame_gid"; push(labels, "map"); push(labels, "gid"); push(labels, "frame") }
if (meth == "animated") { bare = "tmap_is_animated"; push(labels, "map"); push(labels, "gid") }
if (meth == "project") { bare = "tmap_project"; push(labels, "map"); push(labels, "layer") }
if (meth == "collide") { bare = "tmap_collide"; push(labels, "map"); push(labels, "layer") }
if (meth == "collision_kind") { bare = "tmap_collision_kind"; push(labels, "map"); push(labels, "gid") }
if (meth == "tree") { bare = "tmap_tree"; push(labels, "map") }
if (meth == "tile_prop") { bare = "tmap_tile_prop"; push(labels, "map"); push(labels, "gid"); push(labels, "name") }
if (meth == "tile_shapes") { bare = "tmap_tile_has_shapes"; push(labels, "map"); push(labels, "gid") }
# P4 (#72): objects / properties / custom types / templates / opt-in spawn
if (meth == "object_count") { bare = "tmap_object_count"; push(labels, "map"); push(labels, "layer") }
if (meth == "object") { bare = "tmap_object"; push(labels, "map"); push(labels, "layer"); push(labels, "index") }
if (meth == "object_shape") { bare = "tiled_object_shape"; push(labels, "object") }
if (meth == "prop") { bare = "tiled_prop_str"; push(labels, "container"); push(labels, "name") }
if (meth == "prop_int") { bare = "tiled_prop_int"; push(labels, "container"); push(labels, "name") }
if (meth == "prop_type") { bare = "tiled_prop_type"; push(labels, "container"); push(labels, "name") }
if (meth == "load_types") { bare = "tiled_load_types"; push(labels, "path") }
if (meth == "template") { bare = "tiled_read_template"; push(labels, "path") }
if (meth == "spawn") { bare = "tiled_spawn_object"; push(labels, "map"); push(labels, "object"); push(labels, "model") }
if (meth == "spawn_layer") { bare = "tiled_spawn_layer"; push(labels, "map"); push(labels, "layer"); push(labels, "model") }
# P5 (#73): orientation coords + image/group-layer accessors
if (meth == "cell_x") { bare = "tmap_cell_sx"; push(labels, "map"); push(labels, "x"); push(labels, "y") }
if (meth == "cell_y") { bare = "tmap_cell_sy"; push(labels, "map"); push(labels, "x"); push(labels, "y") }
if (meth == "layer_kind") { bare = "tmap_layer_kind"; push(labels, "map"); push(labels, "layer") }
if (meth == "layer_opacity") { bare = "tmap_layer_opacity"; push(labels, "map"); push(labels, "layer") }
if (meth == "layer_tint") { bare = "tmap_layer_tint"; push(labels, "map"); push(labels, "layer") }
if (meth == "layer_offsetx") { bare = "tmap_layer_offsetx"; push(labels, "map"); push(labels, "layer") }
if (meth == "layer_offsety") { bare = "tmap_layer_offsety"; push(labels, "map"); push(labels, "layer") }
# P6 (#74): .world stitching
if (meth == "world") { bare = "tiled_read_world"; push(labels, "path") }
if (meth == "world_count") { bare = "tiled_world_count"; push(labels, "world") }
if (meth == "world_map") { bare = "tiled_world_map"; push(labels, "world"); push(labels, "index") }
}
# #62: a package-provided namespace (declared with @Namespace(Foo)) that none # #62: a package-provided namespace (declared with @Namespace(Foo)) that none
# of the hardcoded core blocks matched — alias Foo.method to the bare function # of the hardcoded core blocks matched — alias Foo.method to the bare function
# foo_method (positional args), the same generic path the core aliases use. # foo_method (positional args), the same generic path the core aliases use.

View file

@ -340,6 +340,8 @@ function static_type(e: Node) -> pointer {
if (e.a.a.s == "Map") and ((e.a.s == "random_cell") or (e.a.s == "random_cell_far") or (e.a.s == "to_tile")) { return "IVec2" } if (e.a.a.s == "Map") and ((e.a.s == "random_cell") or (e.a.s == "random_cell_far") or (e.a.s == "to_tile")) { return "IVec2" }
if (e.a.a.s == "Collider") and (e.a.s == "center") { return "IVec2" } if (e.a.a.s == "Collider") and (e.a.s == "center") { return "IVec2" }
if (e.a.a.s == "Input") and (e.a.s == "move_i") { return "IVec2" } if (e.a.a.s == "Input") and (e.a.s == "move_i") { return "IVec2" }
let al = ns_alias_find(e.a.a.s, e.a.s)
if al >= 0 { let af = find_fn(g_al_target[al]); if (af != null) { return af.ty } }
let nf = find_fn(ns_lower(e.a.a.s) + ("_") + e.a.s); if (nf != null) { return nf.ty } let nf = find_fn(ns_lower(e.a.a.s) + ("_") + e.a.s); if (nf != null) { return nf.ty }
} }
} }

View file

@ -20,6 +20,15 @@ function ck_call_fn(e: Node, name: pointer, f: Node) -> pointer {
if f.ty == null { return "void" } if f.ty == null { return "void" }
return f.ty return f.ty
} }
function ck_call_alias(e: Node, name: pointer, al: int, f: Node) -> pointer {
let labels = new []pointer
let tys = new []pointer
ck_params(f, labels, tys)
let alabels = ns_alias_labels(al)
if len(alabels) == len(tys) { ck_args(e, name, alabels, tys) } else { ck_args(e, name, labels, tys) }
if f.ty == null { return "void" }
return f.ty
}
function ck_call_sig(e: Node, name: pointer, t: pointer) -> pointer { function ck_call_sig(e: Node, name: pointer, t: pointer) -> pointer {
ck_args(e, name, new []pointer, fn_ty_params(t)) ck_args(e, name, new []pointer, fn_ty_params(t))
return fn_ty_ret(t) return fn_ty_ret(t)
@ -97,8 +106,17 @@ function ck_call(e: Node) -> pointer {
if c.kind == E_MEMBER { if c.kind == E_MEMBER {
let b = c.a let b = c.a
if b.kind == E_ID and ck_local(b.s) < 0 and ck_global(b.s) == null { if b.kind == E_ID and ck_local(b.s) < 0 and ck_global(b.s) == null {
# Ns.fn: the emitter's own table lowers it, supplying defaults and reordering, so the # an alias (L6) is its target, labels and all, so its arguments are checked in full
# Ludic function behind it gives the result's type and nothing about the arguments let al = ns_alias_find(b.s, c.s)
if al >= 0 {
var tf = ck_fn(g_al_target[al])
if tf == null { tf = ck_extern(g_al_target[al]) }
if tf != null { return ck_call_alias(e, `{b.s}.{c.s}`, al, tf) }
ck_walk_args(e)
return "?"
}
# any other Ns.fn is the emitter's own table, which supplies defaults and reorders,
# so the Ludic function behind it gives the result's type and nothing about the arguments
ck_walk_args(e) ck_walk_args(e)
let nf = ck_fn(ns_lower(b.s) + "_" + c.s) let nf = ck_fn(ns_lower(b.s) + "_" + c.s)
if nf != null and nf.ty != null { return nf.ty } if nf != null and nf.ty != null { return nf.ty }

View file

@ -0,0 +1,74 @@
# aliases.ludic — L6: a namespace method that is a name for a function. Inside a namespace block,
# alias fill_rectangle(x, y, width, height, color) = fill_rect
# makes `Screen.fill_rectangle(...)` a call to `fill_rect`, taking named arguments by those labels
# in the target's parameter order. With no label list the target's own parameter names are the
# labels; an empty one, `alias show() = present`, takes no named arguments. The engine declares
# its namespaces this way in runtime/native/namespaces.ludic, and a package owns an API the same
# way, in its own files - neither needs a line in the compiler.
var g_al_ns: []pointer = new []pointer
var g_al_meth: []pointer = new []pointer
var g_al_target: []pointer = new []pointer
var g_al_labels: []pointer = new []pointer # "a|b|c"; "" for none; null for the target's own
function ns_parse_alias(nsname: pointer, is_exp: int) -> void {
pi += 1
let meth = eat_id()
var labels: pointer = null
if is_op("(") {
pi += 1
labels = ""
while not is_op(")") {
if len(labels) > 0 { labels = labels + "|" }
labels = labels + eat_id()
if is_op(",") { pi += 1 }
}
eat_op(")")
}
eat_op("=")
let target = eat_id()
register_namespace(nsname)
if is_exp == 0 { return }
if ns_alias_find(nsname, meth) >= 0 { perr(`{nsname}.{meth} is declared twice`) }
push(g_al_ns, nsname)
push(g_al_meth, meth)
push(g_al_target, target)
push(g_al_labels, labels)
}
function ns_alias_find(ns: pointer, meth: pointer) -> int {
var i = 0
while i < len(g_al_ns) {
if (g_al_meth[i] == meth) and (g_al_ns[i] == ns) { return i }
i += 1
}
return -1
}
function ns_alias_labels(i: int) -> []pointer {
let out = new []pointer
let ls = g_al_labels[i]
if ls == null {
var d = find_fn(g_al_target[i])
if d == null { d = find_extern(g_al_target[i]) }
if d != null { return param_labels(d) }
return out
}
var a = 0
var j = 0
let n = len(ls)
while j <= n {
if j == n or ls[j] == '|' {
if j > a { push(out, ls[a .. j]) }
a = j + 1
}
j += 1
}
return out
}
function emit_alias_call(i: int, e: Node) -> Val {
reorder_named(e, ns_alias_labels(i))
let id = node(E_ID)
id.s = g_al_target[i]
id.file = e.file
id.line = e.line
e.a = id
return emit_call(e)
}

View file

@ -594,7 +594,12 @@ function parse_namespace() -> void {
var is_exp = 1 # default: exported (public) var is_exp = 1 # default: exported (public)
if is_id("export") { pi += 1 } if is_id("export") { pi += 1 }
else { if is_id("internal") { pi += 1; is_exp = 0 } } else { if is_id("internal") { pi += 1; is_exp = 0 } }
if not is_id("function") { perr("a namespace body holds functions: expected `function`") } if is_id("alias") {
ns_parse_alias(nsname, is_exp)
skipnl()
continue
}
if not is_id("function") { perr("a namespace body holds functions or aliases: expected `function` or `alias`") }
let f = parse_fn() let f = parse_fn()
push(fns, f); push(shorts, f.s); push(exps, is_exp) push(fns, f); push(shorts, f.s); push(exps, is_exp)
skipnl() skipnl()
@ -1130,6 +1135,10 @@ function do_import(rel: pointer) -> void {
# Tools (an `entry` block, no ECS) get nothing. # Tools (an `entry` block, no ECS) get nothing.
function maybe_splice_runtime() -> void { function maybe_splice_runtime() -> void {
let saved = cur_dir let saved = cur_dir
# L6: the engine's namespaces that are aliases of runtime functions, declared in Ludic
cur_dir = ""
do_import("runtime/native/namespaces.ludic")
cur_dir = saved
# a game (has systems/components) links the Ludic runtime. # a game (has systems/components) links the Ludic runtime.
if has_ecs() { if has_ecs() {
cur_dir = "" cur_dir = ""

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -19,6 +19,7 @@ function selfhost_frags() -> []pointer {
push(f, "selfhost/frontend/parse.ludic") push(f, "selfhost/frontend/parse.ludic")
push(f, "selfhost/frontend/parse_game.ludic") push(f, "selfhost/frontend/parse_game.ludic")
push(f, "selfhost/frontend/generics.ludic") push(f, "selfhost/frontend/generics.ludic")
push(f, "selfhost/frontend/aliases.ludic")
push(f, "selfhost/backend/emit_core.ludic") push(f, "selfhost/backend/emit_core.ludic")
push(f, "selfhost/backend/emit_head.ludic") push(f, "selfhost/backend/emit_head.ludic")
push(f, "selfhost/backend/emit_addr.ludic") push(f, "selfhost/backend/emit_addr.ludic")

View file

@ -731,6 +731,9 @@ function cmd_dev_test() -> int {
reject_case("rejected/generic_mismatch", "words wants a Pool<string> and this is a Pool<int>", "two instances of one generic are two types") reject_case("rejected/generic_mismatch", "words wants a Pool<string> and this is a Pool<int>", "two instances of one generic are two types")
reject_case("rejected/generic_arity", "Pool takes 1 type argument(s) and Pool<int, string> gives 2", "a generic takes as many type arguments as it has parameters") reject_case("rejected/generic_arity", "Pool takes 1 type argument(s) and Pool<int, string> gives 2", "a generic takes as many type arguments as it has parameters")
reject_case("rejected/private_generic", "grow is private to module kit", "an instance of a private generic is private to its module") reject_case("rejected/private_generic", "grow is private to module kit", "an instance of a private generic is private to its module")
feat_case("lang/aliases", "", "10 5 3", "aliases.ludic (L6: a namespace method declared as an alias of a function, labelled or by its parameters)")
reject_case("rejected/alias_arity", "Trail.length takes 2 argument(s) and this call gives 1", "an alias's arguments are checked against its target")
reject_case("rejected/alias_twice", "Trail.length is declared twice", "a namespace method is declared once")
# EV2 the world table: the mod reflection ABI, callable from Ludic by name. # EV2 the world table: the mod reflection ABI, callable from Ludic by name.
net_case("ecs/world_get", "50 1 7") net_case("ecs/world_get", "50 1 7")