feat(lang): block value requires no trailing ; (Rust-style)

A block's value is now its last statement ONLY when that statement is a
trailing expression with no `;`. A trailing `;` discards the value,
leaving the block void. This makes value-vs-statement explicit and lets
the compiler reject "this block was supposed to produce a value".

Compiler:
- Parser records `Block.produces_value` (last stmt is a no-`;` trailing
  expression) + `Block.discarded_semi` (the `;` that discarded a value),
  via `expectSemicolonAfter`. A trailing expression before `}` may now
  omit its `;` (previously a parse error). Match-arm and else-arm bodies
  are built value-producing regardless of the arm `;` (arms are exempt —
  the `;` is an arm terminator).
- Lowering: `lowerBlockValue` / the block-expr path / `inferExprType`
  respect `produces_value`. A value-position block that discards its value
  is a hard error (`lowerValueBody` for function bodies; the value-context
  `.block` path for if/else branches, `catch` bodies, value bindings,
  match arms). Pure-failable `-> !` bodies (value rides the error channel)
  and a value-if whose branches are void are handled without false errors.
- `defer`/`onfail` cleanup bodies lower as statements (void), so a
  trailing `;` there is fine.

Migration (behavior-preserving — output unchanged):
- stdlib + ~210 examples: dropped the trailing `;` on value-position last
  expressions. `format` now ends with an explicit `#insert "return
  result;"` (it relied on `#insert`-as-block-value, which `;` discards).
- Two `main :: () -> s32` examples that relied on the old silent
  default-return got an explicit trailing `0`.
- Rejection snapshots 0412 / 1013 regenerated (their quoted source lines
  lost a `;`); the diagnostics themselves are unchanged.

Docs/tests: specs.md "Block values" section; examples 0040 (rules) + 0041
(rejection); 3 parser unit tests. Filed issue 0066 (pre-existing
match-arm negated-literal phi-width quirk, surfaced not caused here).

Gates: zig build, zig build test, run_examples.sh -> 343 passed,
cross_compile.sh -> 7 passed (also refreshed its stale example names).
This commit is contained in:
agra
2026-06-02 09:23:50 +03:00
parent 634cf9bc7f
commit bdd0e96d78
265 changed files with 1070 additions and 761 deletions

View File

@@ -42,7 +42,7 @@ GPA :: struct {
alloc_count: s64;
init :: () -> GPA {
GPA.{ alloc_count = 0 };
GPA.{ alloc_count = 0 }
}
}
@@ -96,7 +96,7 @@ Arena :: struct {
init :: (parent_alloc: Allocator, size: s64) -> Arena {
self : Arena = .{ first = null, end_index = 0, parent = parent_alloc };
self.add_chunk(size);
self;
self
}
reset :: (a: *Arena) {
@@ -140,7 +140,7 @@ impl Allocator for Arena {
buf : [*]u8 = xx self.first;
ptr := @buf[16 + self.end_index];
self.end_index = self.end_index + aligned;
ptr;
ptr
}
dealloc :: (self: *Arena, ptr: *void) {}
}
@@ -165,7 +165,7 @@ BufAlloc :: struct {
b.buf = @buf[self_size];
b.len = len - self_size;
b.pos = 0;
b;
b
}
reset :: (b: *BufAlloc) {
@@ -181,7 +181,7 @@ impl Allocator for BufAlloc {
}
ptr := @self.buf[self.pos];
self.pos = self.pos + aligned;
ptr;
ptr
}
dealloc :: (self: *BufAlloc, ptr: *void) {}
}
@@ -221,11 +221,11 @@ TrackingAllocator :: struct {
alloc_count = 0,
dealloc_count = 0,
total_alloc_bytes = 0,
};
}
}
leak_count :: (t: *TrackingAllocator) -> s64 {
t.alloc_count - t.dealloc_count;
t.alloc_count - t.dealloc_count
}
report :: (t: *TrackingAllocator) {
@@ -241,7 +241,7 @@ impl Allocator for TrackingAllocator {
self.alloc_count += 1;
self.total_alloc_bytes += size;
}
ptr;
ptr
}
dealloc :: (self: *TrackingAllocator, ptr: *void) {
self.parent.dealloc(ptr);

View File

@@ -69,25 +69,25 @@ SeekFrom :: enum { set; current; end; }
File :: struct {
fd: s32 = -1;
is_valid :: (self: *File) -> bool { self.fd >= 0; }
is_valid :: (self: *File) -> bool { self.fd >= 0 }
close :: (self: *File) -> bool {
if self.fd < 0 { return false; }
rc := close(self.fd);
self.fd = -1;
rc == 0;
rc == 0
}
read :: (self: *File, buf: string) -> s64 {
if self.fd < 0 { return -1; }
n := read(self.fd, buf.ptr, xx buf.len);
cast(s64) n;
cast(s64) n
}
write :: (self: *File, data: string) -> s64 {
if self.fd < 0 { return -1; }
n := write(self.fd, data.ptr, xx data.len);
cast(s64) n;
cast(s64) n
}
seek :: (self: *File, offset: s64, whence: SeekFrom) -> s64 {
@@ -95,7 +95,7 @@ File :: struct {
w := SEEK_SET;
if whence == .current { w = SEEK_CUR; }
if whence == .end { w = SEEK_END; }
lseek(self.fd, offset, w);
lseek(self.fd, offset, w)
}
}
@@ -110,13 +110,13 @@ mode_to_flags :: (m: OpenMode) -> s32 {
if m == .write { return O_WRONLY | O_CREAT | O_TRUNC; }
if m == .append { return O_WRONLY | O_CREAT | O_APPEND; }
if m == .read_write { return O_RDWR; }
O_RDONLY;
O_RDONLY
}
open_file :: (path: [:0]u8, mode: OpenMode) -> ?File {
fd := open(path, mode_to_flags(mode), 420); // 0o644 = 420
if fd < 0 { return null; }
File.{ fd = fd };
File.{ fd = fd }
}
// One-shot read: opens, slurps the whole file into a fresh buffer,
@@ -134,7 +134,7 @@ read_file :: (path: [:0]u8) -> ?string {
n := read(fd, buf.ptr, xx size);
close(fd);
if cast(s64) n != size { return null; }
buf;
buf
}
// One-shot write: creates / truncates and writes the whole buffer.
@@ -143,7 +143,7 @@ write_file :: (path: [:0]u8, data: string) -> bool {
if fd < 0 { return false; }
n := write(fd, data.ptr, xx data.len);
close(fd);
cast(s64) n == cast(s64) data.len;
cast(s64) n == cast(s64) data.len
}
append_file :: (path: [:0]u8, data: string) -> bool {
@@ -151,33 +151,33 @@ append_file :: (path: [:0]u8, data: string) -> bool {
if fd < 0 { return false; }
n := write(fd, data.ptr, xx data.len);
close(fd);
cast(s64) n == cast(s64) data.len;
cast(s64) n == cast(s64) data.len
}
// ── Single-syscall ops ───────────────────────────────────────────────
exists :: (path: [:0]u8) -> bool {
access(path, F_OK) == 0;
access(path, F_OK) == 0
}
delete_file :: (path: [:0]u8) -> bool {
unlink(path) == 0;
unlink(path) == 0
}
delete_dir :: (path: [:0]u8) -> bool {
rmdir(path) == 0;
rmdir(path) == 0
}
create_dir :: (path: [:0]u8) -> bool {
mkdir(path, 493) == 0; // 0o755 = 493
mkdir(path, 493) == 0 // 0o755 = 493
}
set_mode :: (path: [:0]u8, mode: u32) -> bool {
chmod(path, mode) == 0;
chmod(path, mode) == 0
}
move :: (oldp: [:0]u8, newp: [:0]u8) -> bool {
rename(oldp, newp) == 0;
rename(oldp, newp) == 0
}
// Recursive mkdir -p. Walks the path and creates each missing
@@ -195,7 +195,7 @@ create_dir_all :: (path: [:0]u8) -> bool {
memcpy(parent.ptr, path.ptr, last);
if !create_dir_all(parent) { return false; }
}
create_dir(path);
create_dir(path)
}
// Copy a file by streaming through a 64KB buffer. Uses libc directly
@@ -224,7 +224,7 @@ copy_file :: (src: [:0]u8, dst: [:0]u8) -> bool {
}
close(src_fd);
close(dst_fd);
ok;
ok
}
// ── Path helpers ─────────────────────────────────────────────────────
@@ -243,7 +243,7 @@ basename :: (p: string) -> string {
if p[last - 1] == 47 { return substr(p, last, end - last); }
last -= 1;
}
substr(p, 0, end);
substr(p, 0, end)
}
dirname :: (p: string) -> string {
@@ -264,5 +264,5 @@ dirname :: (p: string) -> string {
last -= 1;
}
if p[0] == 47 { return "/"; }
".";
"."
}

View File

@@ -101,7 +101,7 @@ impl GPU for Gles3Gpu {
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
true;
true
}
shutdown :: (self: *Gles3Gpu) {
@@ -122,7 +122,7 @@ impl GPU for Gles3Gpu {
glViewport(0, 0, self.pixel_w, self.pixel_h);
glClearColor(clear.r, clear.g, clear.b, clear.a);
glClear(GL_COLOR_BUFFER_BIT);
true;
true
}
// eglSwapBuffers happens in AndroidPlatform.end_frame; nothing for us.
@@ -141,7 +141,7 @@ impl GPU for Gles3Gpu {
tex_loc = glGetUniformLocation(prog, "uTex".ptr),
};
self.shaders.append(slot, self.parent_allocator);
xx self.shaders.len;
xx self.shaders.len
}
create_buffer :: (self: *Gles3Gpu, size_bytes: s64) -> BufferHandle {
@@ -152,7 +152,7 @@ impl GPU for Gles3Gpu {
glBindBuffer(GL_ARRAY_BUFFER, b);
glBufferData(GL_ARRAY_BUFFER, xx size_bytes, null, GL_DYNAMIC_DRAW);
self.buffers.append(b, self.parent_allocator);
xx self.buffers.len;
xx self.buffers.len
}
update_buffer :: (self: *Gles3Gpu, handle: BufferHandle, data: *void, size_bytes: s64) {
@@ -206,7 +206,7 @@ impl GPU for Gles3Gpu {
slot : Gles3TextureSlot = .{ tex = t, bytes_per_pixel = bpp };
self.textures.append(slot, self.parent_allocator);
xx self.textures.len;
xx self.textures.len
}
update_texture_region :: (self: *Gles3Gpu, handle: TextureHandle, x: s32, y: s32, w: s32, h: s32, pixels: *void) {
@@ -332,5 +332,5 @@ gles3_lookup_buffer :: (self: *Gles3Gpu, handle: u32) -> u32 {
if handle == 0 { return 0; }
h64 : s64 = xx handle;
if h64 > self.buffers.len { return 0; }
self.buffers.items[handle - 1];
self.buffers.items[handle - 1]
}

View File

@@ -107,7 +107,7 @@ impl GPU for MetalGPU {
self.pixel_h = pixel_h;
}
self.parent_allocator = context.allocator;
metal_init_ios(self);
metal_init_ios(self)
}
shutdown :: (self: *MetalGPU) {
@@ -125,7 +125,7 @@ impl GPU for MetalGPU {
begin_frame :: (self: *MetalGPU, clear: ClearColor) -> bool {
inline if OS != .ios { return false; }
metal_begin_frame_ios(self, clear);
metal_begin_frame_ios(self, clear)
}
end_frame :: (self: *MetalGPU, target_time: f64) {
@@ -141,12 +141,12 @@ impl GPU for MetalGPU {
create_shader :: (self: *MetalGPU, vsrc: string, fsrc: string) -> ShaderHandle {
inline if OS != .ios { return 0; }
metal_create_shader_ios(self, vsrc);
metal_create_shader_ios(self, vsrc)
}
create_buffer :: (self: *MetalGPU, size_bytes: s64) -> BufferHandle {
inline if OS != .ios { return 0; }
metal_create_buffer_ios(self, size_bytes);
metal_create_buffer_ios(self, size_bytes)
}
update_buffer :: (self: *MetalGPU, buf: BufferHandle, data: *void, size_bytes: s64) {
@@ -163,7 +163,7 @@ impl GPU for MetalGPU {
create_texture :: (self: *MetalGPU, w: s32, h: s32, format: TextureFormat, pixels: *void) -> TextureHandle {
inline if OS != .ios { return 0; }
metal_create_texture_ios(self, w, h, format, pixels);
metal_create_texture_ios(self, w, h, format, pixels)
}
update_texture_region :: (self: *MetalGPU, tex: TextureHandle, x: s32, y: s32, w: s32, h: s32, pixels: *void) {
@@ -282,7 +282,7 @@ metal_init_ios :: (self: *MetalGPU) -> bool {
}
}
true;
true
}
metal_resize_ios :: (self: *MetalGPU) {
@@ -343,7 +343,7 @@ metal_begin_frame_ios :: (self: *MetalGPU, clear: ClearColor) -> bool {
sel_registerName("renderCommandEncoderWithDescriptor:".ptr), pass);
if self.encoder == null { self.cmd_buffer = null; self.drawable = null; return false; }
true;
true
}
metal_end_frame_ios :: (self: *MetalGPU, target_time: f64) {
@@ -438,7 +438,7 @@ metal_create_shader_ios :: (self: *MetalGPU, src: string) -> u32 {
}
self.shaders.append(state, self.parent_allocator);
xx self.shaders.len;
xx self.shaders.len
}
// ── Buffers ──────────────────────────────────────────────────────────────
@@ -458,7 +458,7 @@ metal_create_buffer_ios :: (self: *MetalGPU, size_bytes: s64) -> u32 {
if buf == null { return 0; }
self.buffers.append(buf, self.parent_allocator);
xx self.buffers.len;
xx self.buffers.len
}
metal_update_buffer_ios :: (self: *MetalGPU, handle: u32, data: *void, size_bytes: s64) {
@@ -498,7 +498,7 @@ metal_lookup_buffer :: (self: *MetalGPU, handle: u32) -> *void {
if handle == 0 { return null; }
h64 : s64 = xx handle;
if h64 > self.buffers.len { return null; }
self.buffers.items[handle - 1];
self.buffers.items[handle - 1]
}
metal_lookup_shader :: (self: *MetalGPU, handle: u32) -> *void {
@@ -506,7 +506,7 @@ metal_lookup_shader :: (self: *MetalGPU, handle: u32) -> *void {
if handle == 0 { return null; }
h64 : s64 = xx handle;
if h64 > self.shaders.len { return null; }
self.shaders.items[handle - 1];
self.shaders.items[handle - 1]
}
// ── Textures ─────────────────────────────────────────────────────────────
@@ -553,7 +553,7 @@ metal_create_texture_ios :: (self: *MetalGPU, w: s32, h: s32, format: TextureFor
metal_update_texture_region_ios(self, handle, 0, 0, w, h, pixels);
}
xx self.textures.len;
xx self.textures.len
}
metal_update_texture_region_ios :: (self: *MetalGPU, handle: u32, x: s32, y: s32, w: s32, h: s32, pixels: *void) {

View File

@@ -9,29 +9,29 @@ cos :: (x: $T) -> T #builtin;
floor :: (x: $T) -> T #builtin;
min :: (a: $T, b: T) -> T {
if a < b then a else b;
if a < b then a else b
}
max :: (a: $T, b: T) -> T {
if a > b then a else b;
if a > b then a else b
}
clamp :: (val: $T, lo: T, hi: T) -> T {
if val < lo then lo
else if val > hi then hi
else val;
else val
}
abs :: (x: $T) -> T {
if x < 0 then 0 - x else x;
if x < 0 then 0 - x else x
}
lerp :: (a: f32, b: f32, t: f32) -> f32 {
a + (b - a) * t;
a + (b - a) * t
}
sign :: (x: $T) -> T {
if x > 0 then 1
else if x < 0 then 0 - 1
else 0;
else 0
}

View File

@@ -10,7 +10,7 @@ Mat4 :: struct {
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0
]};
]}
}
zero :: () -> Mat4 {
@@ -19,7 +19,7 @@ Mat4 :: struct {
0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0
]};
]}
}
mul :: (self: Mat4, b: Mat4) -> Mat4 {
@@ -39,7 +39,7 @@ Mat4 :: struct {
}
col += 1;
}
r;
r
}
translate :: (x: f32, y: f32, z: f32) -> Mat4 {
@@ -47,7 +47,7 @@ Mat4 :: struct {
m.data[12] = x;
m.data[13] = y;
m.data[14] = z;
m;
m
}
scale :: (x: f32, y: f32, z: f32) -> Mat4 {
@@ -56,7 +56,7 @@ Mat4 :: struct {
m.data[5] = y;
m.data[10] = z;
m.data[15] = 1.0;
m;
m
}
rotate_x :: (angle: f32) -> Mat4 {
@@ -67,7 +67,7 @@ Mat4 :: struct {
m.data[6] = s;
m.data[9] = 0.0 - s;
m.data[10] = c;
m;
m
}
rotate_y :: (angle: f32) -> Mat4 {
@@ -78,7 +78,7 @@ Mat4 :: struct {
m.data[2] = 0.0 - s;
m.data[8] = s;
m.data[10] = c;
m;
m
}
rotate_z :: (angle: f32) -> Mat4 {
@@ -89,7 +89,7 @@ Mat4 :: struct {
m.data[1] = s;
m.data[4] = 0.0 - s;
m.data[5] = c;
m;
m
}
ortho :: (left: f32, right: f32, bottom: f32, top: f32, near: f32, far: f32) -> Mat4 {
@@ -101,7 +101,7 @@ Mat4 :: struct {
m.data[13] = 0.0 - (top + bottom) / (top - bottom);
m.data[14] = 0.0 - (far + near) / (far - near);
m.data[15] = 1.0;
m;
m
}
perspective :: (fov: f32, aspect: f32, near: f32, far: f32) -> Mat4 {
@@ -112,6 +112,6 @@ Mat4 :: struct {
m.data[10] = 0.0 - (far + near) / (far - near);
m.data[11] = 0.0 - 1.0;
m.data[14] = 0.0 - 2.0 * far * near / (far - near);
m;
m
}
}

View File

@@ -1,26 +1,26 @@
Vec2 :: struct {
x, y: f32;
zero :: () -> Vec2 { Vec2.{ x = 0.0, y = 0.0 }; }
zero :: () -> Vec2 { Vec2.{ x = 0.0, y = 0.0 } }
add :: (self: Vec2, b: Vec2) -> Vec2 {
Vec2.{ x = self.x + b.x, y = self.y + b.y };
Vec2.{ x = self.x + b.x, y = self.y + b.y }
}
sub :: (self: Vec2, b: Vec2) -> Vec2 {
Vec2.{ x = self.x - b.x, y = self.y - b.y };
Vec2.{ x = self.x - b.x, y = self.y - b.y }
}
scale :: (self: Vec2, s: f32) -> Vec2 {
Vec2.{ x = self.x * s, y = self.y * s };
Vec2.{ x = self.x * s, y = self.y * s }
}
dot :: (self: Vec2, b: Vec2) -> f32 {
self.x * b.x + self.y * b.y;
self.x * b.x + self.y * b.y
}
length :: (self: Vec2) -> f32 {
sqrt(self.x * self.x + self.y * self.y);
sqrt(self.x * self.x + self.y * self.y)
}
normalize :: (self: Vec2) -> Vec2 {
@@ -28,22 +28,22 @@ Vec2 :: struct {
if len > 0.0 {
return Vec2.{ x = self.x / len, y = self.y / len };
}
Vec2.zero();
Vec2.zero()
}
lerp :: (self: Vec2, b: Vec2, t: f32) -> Vec2 {
Vec2.{ x = self.x + (b.x - self.x) * t, y = self.y + (b.y - self.y) * t };
Vec2.{ x = self.x + (b.x - self.x) * t, y = self.y + (b.y - self.y) * t }
}
distance :: (self: Vec2, b: Vec2) -> f32 {
self.sub(b).length();
self.sub(b).length()
}
negate :: (self: Vec2) -> Vec2 {
Vec2.{ x = 0.0 - self.x, y = 0.0 - self.y };
Vec2.{ x = 0.0 - self.x, y = 0.0 - self.y }
}
equals :: (self: Vec2, b: Vec2) -> bool {
self.x == b.x and self.y == b.y;
self.x == b.x and self.y == b.y
}
}

View File

@@ -270,7 +270,7 @@ sx_android_render_thread_entry :: (arg: *void) -> *void callconv(.c) {
fn := plat.user_main_fn;
fn();
}
null;
null
}
// Bring up EGL on `plat.app_window`. Sets the egl_* fields and makes
@@ -313,7 +313,7 @@ sx_android_egl_init :: (plat: *AndroidPlatform) -> bool {
plat.pixel_w = ANativeWindow_getWidth(plat.app_window);
plat.pixel_h = ANativeWindow_getHeight(plat.app_window);
sx_android_recompute_scale(plat);
true;
true
}
// Recompute `dpi_scale` from `pixel_w / logical_w`. Called on viewport
@@ -378,7 +378,7 @@ impl Platform for AndroidPlatform {
self.width = w;
self.height = h;
sx_android_recompute_scale(self);
true;
true
}
// NOTE: method order must match the `Platform` protocol declaration
@@ -405,7 +405,7 @@ impl Platform for AndroidPlatform {
result : []Event = ---;
result.ptr = self.events.items;
result.len = self.events.len;
result;
result
}
begin_frame :: (self: *AndroidPlatform) -> FrameContext {
@@ -421,7 +421,7 @@ impl Platform for AndroidPlatform {
dpi_scale = self.dpi_scale,
delta_time = 0.016,
target_present_time = 0.0,
};
}
}
end_frame :: (self: *AndroidPlatform) {
@@ -431,11 +431,11 @@ impl Platform for AndroidPlatform {
}
safe_insets :: (self: *AndroidPlatform) -> EdgeInsets {
EdgeInsets.{};
EdgeInsets.{}
}
keyboard :: (self: *AndroidPlatform) -> KeyboardState {
KeyboardState.zero();
KeyboardState.zero()
}
show_keyboard :: (self: *AndroidPlatform) { }
hide_keyboard :: (self: *AndroidPlatform) { }

View File

@@ -195,7 +195,7 @@ bundle_main :: () -> bool {
out("bundled: ");
out(bundle);
out("\n");
true;
true
}
// ── Helpers ──────────────────────────────────────────────────────────
@@ -206,7 +206,7 @@ bundle_main :: () -> bool {
str_to_cstr :: (s: string) -> [:0]u8 {
buf := cstring(s.len);
memcpy(buf.ptr, s.ptr, s.len);
buf;
buf
}
// Minimum iOS version baked into the Info.plist — matches what the
@@ -293,7 +293,7 @@ PLIST, xml_escape(bundle_id), xml_escape(exe_name), xml_escape(exe_name), IOS_MI
<string>0.1</string>
</dict>
</plist>
PLIST, xml_escape(bundle_id), xml_escape(exe_name), xml_escape(exe_name));
PLIST, xml_escape(bundle_id), xml_escape(exe_name), xml_escape(exe_name))
}
// Read a `.mobileprovision` and write it to
@@ -313,7 +313,7 @@ embed_provisioning_profile :: (profile: string, bundle: string) -> bool {
out("error: bundle: cannot read provisioning profile: ");
out(profile);
out("\n");
false;
false
}
// Recursive-copy `<src_dir>` (relative to the build CWD) into
@@ -359,7 +359,7 @@ copy_asset_dir :: (src: string, dest: string, bundle: string) -> bool {
return true;
}
out("error: cp -R spawn failed\n");
false;
false
}
// Recursive-copy `<name>.framework` from one of the user's `-F` search
@@ -402,7 +402,7 @@ embed_framework :: (opts: BuildOptions, name: string, dest_dir: string) -> bool
}
i += 1;
}
false;
false
}
// Extract entitlements XML from a `.mobileprovision` and resolve the
@@ -505,7 +505,7 @@ extract_entitlements :: (profile: string, bundle_id: string) -> ?string {
return null;
}
ent_path;
ent_path
}
// Codesign the bundle. Empty `ent_path` means no `--entitlements`
@@ -532,7 +532,7 @@ codesign :: (bundle: string, identity: string, ent_path: string) -> bool {
return true;
}
out("error: codesign spawn failed\n");
false;
false
}
// =====================================================================
@@ -572,7 +572,7 @@ absolutify :: (path: string) -> string {
if cwd.len == 0 { return path; }
return path_join(cwd, path);
}
path;
path
}
android_bundle_main :: (opts: BuildOptions, binary: string, apk_path: string, bundle_id: string) -> bool {
@@ -771,7 +771,7 @@ android_bundle_main :: (opts: BuildOptions, binary: string, apk_path: string, bu
out("apk: ");
out(apk_path);
out("\n");
true;
true
}
// ── Android helpers ──────────────────────────────────────────────────
@@ -795,7 +795,7 @@ run_in_dir :: (dir: string, cmd: string) -> bool {
return true;
}
out("error: shell spawn failed\n");
false;
false
}
// Discover the Android SDK root. Honors $ANDROID_HOME /
@@ -808,7 +808,7 @@ discover_android_sdk :: () -> string {
candidate := path_join(home, "Library/Android/sdk");
if exists(str_to_cstr(candidate)) { return candidate; }
}
"";
""
}
// Pick the lexicographically-highest subdir of `parent`. Equivalent to
@@ -831,7 +831,7 @@ find_highest_subdir :: (parent: string) -> string {
if name.len == 0 { return ""; }
return path_join(parent, name);
}
"";
""
}
// `libfoo.so` → `foo`. Android's `android.app.lib_name` meta-data
@@ -858,7 +858,7 @@ lib_name_from_so_basename :: (basename: string) -> string {
}
}
}
name;
name
}
// AndroidManifest.xml synthesizer. When the program declares a
@@ -922,7 +922,7 @@ MANIFEST, pkg_esc, lib_esc, cls_esc, lib_esc);
</activity>
</application>
</manifest>
MANIFEST, pkg_esc, lib_esc, lib_esc, lib_esc);
MANIFEST, pkg_esc, lib_esc, lib_esc, lib_esc)
}
// `co/swipelab/sxchess/SxApp` → `co.swipelab.sxchess.SxApp`.
@@ -934,7 +934,7 @@ slash_to_dot :: (path: string) -> string {
buf[i] = if c == 47 then 46 else c; // 47 = '/', 46 = '.'
i += 1;
}
buf;
buf
}
// Last `/`-separated component of a forward-slash path (used to split
@@ -947,7 +947,7 @@ last_slash_component :: (path: string) -> string {
if path[i - 1] == 47 { return substr(path, i, path.len - i); }
i -= 1;
}
path;
path
}
dir_part :: (path: string) -> string {
@@ -956,7 +956,7 @@ dir_part :: (path: string) -> string {
if path[i - 1] == 47 { return substr(path, 0, i - 1); }
i -= 1;
}
"";
""
}
// Write each `#jni_main` decl's `.java` source, then compile to
@@ -1054,7 +1054,7 @@ compile_jni_main_sources :: (opts: BuildOptions, stage: string, android_jar: str
out("error: d8 spawn failed\n");
return false;
}
true;
true
}
// Locate `javac`. Honors `$JAVA_HOME/bin/javac` first (Android
@@ -1066,7 +1066,7 @@ discover_javac :: () -> string {
if exists(str_to_cstr(cand)) { return cand; }
}
if path := find_executable("javac") { return path; }
"";
""
}
// Zip the contents of `<src>` into the APK at `<dest>/`. Uses a
@@ -1109,7 +1109,7 @@ zip_asset_dir :: (src: string, dest: string, apk: string) -> bool {
zip_cmd = concat(zip_cmd, dest);
zip_cmd = concat(zip_cmd, "\"");
if !run_in_dir(".sx-tmp/apk-assets", zip_cmd) { return false; }
true;
true
}
// Generate the Android debug keystore on first use. The defaults
@@ -1133,5 +1133,5 @@ ensure_debug_keystore :: (keystore_path: string) -> bool {
return true;
}
out("error: keytool spawn failed\n");
false;
false
}

View File

@@ -122,7 +122,7 @@ impl Platform for SdlPlatform {
g_sdl_plat = self;
self.last_perf = SDL_GetPerformanceCounter();
true;
true
}
run_frame_loop :: (self: *SdlPlatform, frame_fn: Closure()) {
@@ -164,7 +164,7 @@ impl Platform for SdlPlatform {
result : []Event = ---;
result.ptr = self.events.items;
result.len = self.events.len;
result;
result
}
begin_frame :: (self: *SdlPlatform) -> FrameContext {
@@ -195,7 +195,7 @@ impl Platform for SdlPlatform {
pixel_h = self.pixel_h,
dpi_scale = self.dpi_scale,
delta_time = self.delta_time,
};
}
}
end_frame :: (self: *SdlPlatform) {
@@ -203,11 +203,11 @@ impl Platform for SdlPlatform {
}
safe_insets :: (self: *SdlPlatform) -> EdgeInsets {
EdgeInsets.zero();
EdgeInsets.zero()
}
keyboard :: (self: *SdlPlatform) -> KeyboardState {
KeyboardState.zero();
KeyboardState.zero()
}
show_keyboard :: (self: *SdlPlatform) { }
@@ -242,7 +242,7 @@ sdl_event_watch :: (userdata: *void, event: *SDL_Event) -> bool callconv(.c) {
}
}
}
true;
true
}
sdl_wasm_tick :: () callconv(.c) {

View File

@@ -357,7 +357,7 @@ impl Platform for UIKitPlatform {
self.read_screen_scale();
}
}
true;
true
}
run_frame_loop :: (self: *UIKitPlatform, frame_fn: Closure()) {
@@ -380,7 +380,7 @@ impl Platform for UIKitPlatform {
result.ptr = self.events.items;
result.len = self.events.len;
self.events.len = 0;
result;
result
}
begin_frame :: (self: *UIKitPlatform) -> FrameContext {
@@ -395,7 +395,7 @@ impl Platform for UIKitPlatform {
dpi_scale = self.dpi_scale,
delta_time = self.delta_time,
target_present_time = self.last_target_ts,
};
}
}
end_frame :: (self: *UIKitPlatform) {
@@ -417,14 +417,14 @@ impl Platform for UIKitPlatform {
left = self.safe_left,
bottom = bottom,
right = self.safe_right,
};
}
}
keyboard :: (self: *UIKitPlatform) -> KeyboardState {
KeyboardState.{
visible = self.keyboard_visible,
height = self.keyboard_height,
};
}
}
show_keyboard :: (self: *UIKitPlatform) {
@@ -793,7 +793,7 @@ impl Platform for UIKitPlatform {
// callconv(.c) so this is callable from `load_gl`'s C-conv proc-loader slot.
ios_gl_proc :: (name: [*]u8) -> *void callconv(.c) {
rtld_default : *void = xx (0 - 2);
dlsym(rtld_default, name);
dlsym(rtld_default, name)
}
// Read a `extern NSString * const k...` global from the loaded image. The
@@ -804,7 +804,7 @@ uikit_extern_nsstring :: (name: [*]u8) -> *void {
p := dlsym(rtld_default, name);
if p == null { return null; }
pp : **void = xx p;
pp.*;
pp.*
}
// ───────────────────────────────────────────────────────────────────────────
@@ -869,12 +869,12 @@ SxGLView :: #objc_class("SxGLView") {
uikit_touch_location :: (touch: *void, view: *void) -> Point {
p := #objc_call(CGPoint)(touch, "locationInView:", view);
Point.{ x = xx p.x, y = xx p.y };
Point.{ x = xx p.x, y = xx p.y }
}
uikit_first_touch :: (touches: *void) -> *void {
touches_set : *NSSet = xx touches;
touches_set.anyObject();
touches_set.anyObject()
}
// uikit_register_gl_view_class — deleted (M3.3). SxGLView is now

View File

@@ -78,7 +78,7 @@ run :: (cmd: [:0]u8) -> ?ProcessResult {
exit_code = 128 + (raw_status & 0x7F);
}
}
ProcessResult.{ exit_code = exit_code, stdout = out };
ProcessResult.{ exit_code = exit_code, stdout = out }
}
// Read an environment variable. Returns null if unset; an empty
@@ -91,7 +91,7 @@ env :: (name: [:0]u8) -> ?string {
if n == 0 { return ""; }
buf := cstring(cast(s64) n);
memcpy(buf.ptr, xx p, cast(s64) n);
buf;
buf
}
// Locate an executable by walking `$PATH`. Returns the absolute path
@@ -115,7 +115,7 @@ find_executable :: (name: [:0]u8) -> ?string {
if out.len == 0 { return null; }
return out;
}
null;
null
}
// ── Process termination (ERR step E4.1) ───────────────────────────────
@@ -139,7 +139,7 @@ exit :: (code: u8, loc: Source_Location = #caller_location) -> noreturn {
print("\nprocess.exit({}) called from {} at {}:{}\n", code, loc.func, loc.file, loc.line);
trace.print_interpreter_frames();
}
clib_exit(xx code);
clib_exit(xx code)
}
// Abort with a message when `cond` is false. Prints `ASSERTION FAILED at

View File

@@ -29,5 +29,5 @@ SockAddr :: struct {
}
htons :: (port: s64) -> u16 {
cast(u16) (((port & 0xFF) << 8) | ((port >> 8) & 0xFF));
cast(u16) (((port & 0xFF) << 8) | ((port >> 8) & 0xFF))
}

View File

@@ -52,7 +52,7 @@ cstring :: (size: s64) -> string {
s : string = ---;
s.ptr = xx raw;
s.len = size;
s;
s
}
alloc_slice :: ($T: Type, count: s64) -> []T {
@@ -61,7 +61,7 @@ alloc_slice :: ($T: Type, count: s64) -> []T {
s : []T = ---;
s.ptr = xx raw;
s.len = count;
s;
s
}
int_to_string :: (n: s64) -> string {
@@ -77,11 +77,11 @@ int_to_string :: (n: s64) -> string {
i -= 1;
}
if neg { tmp[i] = 45; i -= 1; }
substr(tmp, i + 1, 20 - i - 1);
substr(tmp, i + 1, 20 - i - 1)
}
bool_to_string :: (b: bool) -> string {
if b then "true" else "false";
if b then "true" else "false"
}
float_to_string :: (f: f64) -> string {
@@ -107,7 +107,7 @@ float_to_string :: (f: f64) -> string {
memset(@buf[pos], 48, pad);
pos = pos + pad;
memcpy(@buf[pos], fstr.ptr, fl);
buf;
buf
}
hex_group :: (buf: string, offset: s64, val: s64) {
@@ -149,7 +149,7 @@ int_to_hex_string :: (n: s64) -> string {
if buf[start] != 48 { break; }
start += 1;
}
substr(buf, start, 16 - start);
substr(buf, start, 16 - start)
}
concat :: (a: string, b: string) -> string {
@@ -158,13 +158,13 @@ concat :: (a: string, b: string) -> string {
buf := cstring(al + bl);
memcpy(buf.ptr, a.ptr, al);
memcpy(@buf[al], b.ptr, bl);
buf;
buf
}
substr :: (s: string, start: s64, len: s64) -> string {
buf := cstring(len);
memcpy(buf.ptr, @s[start], len);
buf;
buf
}
// Replace XML special characters with their entity references. Used
@@ -195,7 +195,7 @@ xml_escape :: (s: string) -> string {
if seg_start < s.len {
result = concat(result, substr(s, seg_start, s.len - seg_start));
}
result;
result
}
// Join path components with the POSIX separator ('/'). Skips empty
@@ -225,7 +225,7 @@ path_join :: (..parts: []string) -> string {
}
i += 1;
}
result;
result
}
struct_to_string :: (s: $T) -> string {
@@ -238,7 +238,7 @@ struct_to_string :: (s: $T) -> string {
result = concat(result, any_to_string(field_value(s, i)));
i += 1;
}
concat(result, "}");
concat(result, "}")
}
vector_to_string :: (v: $T) -> string {
@@ -249,7 +249,7 @@ vector_to_string :: (v: $T) -> string {
result = concat(result, any_to_string(field_value(v, i)));
i += 1;
}
concat(result, "]");
concat(result, "]")
}
array_to_string :: (a: $T) -> string {
@@ -260,7 +260,7 @@ array_to_string :: (a: $T) -> string {
result = concat(result, any_to_string(field_value(a, i)));
i += 1;
}
concat(result, "]");
concat(result, "]")
}
slice_to_string :: (items: []$T) -> string {
@@ -271,13 +271,13 @@ slice_to_string :: (items: []$T) -> string {
result = concat(result, any_to_string(field_value(items, i)));
i += 1;
}
concat(result, "]");
concat(result, "]")
}
pointer_to_string :: (p: $T) -> string {
addr : s64 = xx p;
if addr == 0 { "null"; } else {
concat(type_name(T), concat("@0x", int_to_hex_string(addr)));
if addr == 0 { "null" } else {
concat(type_name(T), concat("@0x", int_to_hex_string(addr)))
}
}
@@ -294,7 +294,7 @@ flags_to_string :: (val: $T) -> string {
i += 1;
}
if result.len == 0 { result = "0"; }
result;
result
}
enum_to_string :: (u: $T) -> string {
@@ -306,7 +306,7 @@ enum_to_string :: (u: $T) -> string {
if pstr.len > 0 {
result = concat(result, concat("(", concat(pstr, ")")));
}
result;
result
}
optional_to_string :: (o: $T) -> string {
@@ -333,7 +333,7 @@ any_to_string :: (val: Any) -> string {
case optional: result = optional_to_string(cast(type) val);
case type: result = type_name(val);
}
result;
result
}
build_format :: (fmt: string) -> string {
@@ -399,12 +399,12 @@ build_format :: (fmt: string) -> string {
code = concat(code, int_to_string(fmt.len - seg_start));
code = concat(code, ")); ");
}
code;
code
}
format :: ($fmt: string, ..$args) -> string {
#insert build_format(fmt);
#insert "result;";
#insert "return result;";
}
print :: ($fmt: string, ..$args) {

View File

@@ -1,4 +1,4 @@
// This file lives in modules/testpkg/ and imports std relative to its directory.
#import "../std.sx";
cwd_greet :: () -> string { format("cwd-import-ok"); }
cwd_greet :: () -> string { format("cwd-import-ok") }

View File

@@ -1 +1 @@
hello :: () -> string { "hello from testpkg"; }
hello :: () -> string { "hello from testpkg" }

View File

@@ -1,2 +1,2 @@
add :: (a: s32, b: s32) -> s32 { a + b; }
mul :: (a: s32, b: s32) -> s32 { a * b; }
add :: (a: s32, b: s32) -> s32 { a + b }
mul :: (a: s32, b: s32) -> s32 { a * b }

View File

@@ -38,7 +38,7 @@ spaces :: (n: s32) -> string {
s = concat(s, " ");
i = i + 1;
}
s;
s
}
// The error-trace buffer C API (library/vendors/sx_trace_runtime/sx_trace.c),
@@ -73,7 +73,7 @@ to_string :: () -> string {
}
i = i + 1;
}
result;
result
}
// Write the current trace to stderr (fd 2). No-op when the buffer is empty.

View File

@@ -9,14 +9,14 @@ Lerpable :: protocol #inline {
// --- Easing Functions ---
ease_linear :: (t: f32) -> f32 { t; }
ease_in_quad :: (t: f32) -> f32 { t * t; }
ease_out_quad :: (t: f32) -> f32 { t * (2.0 - t); }
ease_linear :: (t: f32) -> f32 { t }
ease_in_quad :: (t: f32) -> f32 { t * t }
ease_out_quad :: (t: f32) -> f32 { t * (2.0 - t) }
ease_in_out_quad :: (t: f32) -> f32 {
if t < 0.5 then 2.0 * t * t
else -1.0 + (4.0 - 2.0 * t) * t;
else -1.0 + (4.0 - 2.0 * t) * t
}
ease_out_cubic :: (t: f32) -> f32 { u := t - 1.0; u * u * u + 1.0; }
ease_out_cubic :: (t: f32) -> f32 { u := t - 1.0; u * u * u + 1.0 }
// --- AnimatedFloat — duration-based ---
@@ -38,7 +38,7 @@ AnimatedFloat :: struct {
duration = 0.0,
easing = null,
active = false
};
}
}
animate_to :: (self: *AnimatedFloat, target: f32, dur: f32, ease: Closure(f32) -> f32) {
@@ -54,7 +54,7 @@ AnimatedFloat :: struct {
if !self.active { return; }
self.elapsed += dt;
t := clamp(self.elapsed / self.duration, 0.0, 1.0);
eased := if ease := self.easing { ease(t); } else { t; };
eased := if ease := self.easing { ease(t) } else { t };
self.current = self.from + (self.to - self.from) * eased;
if t >= 1.0 {
self.current = self.to;
@@ -83,7 +83,7 @@ SpringFloat :: struct {
damping = 20.0,
mass = 1.0,
threshold = 0.01
};
}
}
snappy :: (value: f32) -> SpringFloat {
@@ -95,7 +95,7 @@ SpringFloat :: struct {
damping = 25.0,
mass = 1.0,
threshold = 0.01
};
}
}
tick :: (self: *SpringFloat, dt: f32) {
@@ -109,7 +109,7 @@ SpringFloat :: struct {
is_settled :: (self: *SpringFloat) -> bool {
abs(self.current - self.target) < self.threshold
and abs(self.velocity) < self.threshold;
and abs(self.velocity) < self.threshold
}
}
@@ -131,7 +131,7 @@ Animated :: struct ($T: Lerpable) {
elapsed = 0.0,
duration = 0.0,
active = false
};
}
}
// Jump immediately to value (no animation). Used to avoid animating from zero on first layout.
@@ -163,5 +163,5 @@ Animated :: struct ($T: Lerpable) {
}
}
is_animating :: (self: *Animated(T)) -> bool { self.active; }
is_animating :: (self: *Animated(T)) -> bool { self.active }
}

View File

@@ -22,7 +22,7 @@ ButtonStyle :: struct {
pressed_bg = Color.rgb(0, 80, 180),
corner_radius = 6.0,
padding = EdgeInsets.symmetric(16.0, 8.0)
};
}
}
}
@@ -42,7 +42,7 @@ impl View for Button {
Size.{
width = text_size.width + self.style.padding.horizontal(),
height = text_size.height + self.style.padding.vertical()
};
}
}
layout :: (self: *Button, bounds: Frame) {}
@@ -86,6 +86,6 @@ impl View for Button {
}
}
}
false;
false
}
}

View File

@@ -81,7 +81,7 @@ dock_zone_to_alignment :: (zone: DockZone) -> ?Alignment {
}
dock_zone_should_fill :: (zone: DockZone) -> bool {
zone == .fill;
zone == .fill
}
// =============================================================================
@@ -169,7 +169,7 @@ DockInteraction :: struct {
get_animated_size :: (self: *DockInteraction, index: s64) -> Size {
if index >= self.child_count { return Size.zero(); }
(@self.anim_sizes.items[index]).current;
(@self.anim_sizes.items[index]).current
}
tick_animations :: (self: *DockInteraction, dt: f32) {
@@ -184,7 +184,7 @@ DockInteraction :: struct {
get_hovered_dock_zone :: (self: *DockInteraction) -> ?DockZone {
if self.hovered_zone < 0 { return null; }
// Map ordinal back to DockZone
cast(DockZone) self.hovered_zone;
cast(DockZone) self.hovered_zone
}
set_hovered_dock_zone :: (self: *DockInteraction, zone: ?DockZone) {
@@ -242,7 +242,7 @@ find_hovered_zone :: (bounds: Frame, pos: Point, hint_size: f32, enable_corners:
if expanded.contains(pos) { return zone; }
i += 1;
}
null;
null
}
calculate_origin :: (bounds: Frame, child_size: Size, alignment: Alignment) -> Point {
@@ -262,7 +262,7 @@ calculate_origin :: (bounds: Frame, child_size: Size, alignment: Alignment) -> P
y = bounds.origin.y + bounds.size.height - child_size.height;
}
Point.{ x = x, y = y };
Point.{ x = x, y = y }
}
get_size_proposal_for_alignment :: (alignment: Alignment, bounds_size: Size, is_fill: bool) -> ProposedSize {
@@ -280,7 +280,7 @@ get_size_proposal_for_alignment :: (alignment: Alignment, bounds_size: Size, is_
}
}
// Center or corners: natural size
ProposedSize.flexible();
ProposedSize.flexible()
}
get_final_size_for_alignment :: (alignment: Alignment, child_size: Size, bounds_size: Size, is_fill: bool) -> Size {
@@ -299,7 +299,7 @@ get_final_size_for_alignment :: (alignment: Alignment, child_size: Size, bounds_
}
}
// Center or corners: natural size
child_size;
child_size
}
draw_zone_indicator :: (ctx: *RenderContext, frame: Frame, zone: DockZone, color: Color) {
@@ -385,15 +385,15 @@ DockPanel :: struct {
header_height = DockPanel.DEFAULT_HEADER_H,
dock_interaction = null, // set by Dock.add_panel
panel_index = 0
};
}
}
}
impl View for DockPanel {
size_that_fits :: (self: *DockPanel, proposal: ProposedSize) -> Size {
content_size := self.child.view.size_that_fits(ProposedSize.{ width = proposal.width, height = null });
w := if pw := proposal.width { min(content_size.width, pw); } else { content_size.width; };
Size.{ width = w, height = content_size.height + self.header_height };
w := if pw := proposal.width { min(content_size.width, pw) } else { content_size.width };
Size.{ width = w, height = content_size.height + self.header_height }
}
layout :: (self: *DockPanel, bounds: Frame) {
@@ -446,7 +446,7 @@ impl View for DockPanel {
}
// Forward to child content
self.child.view.handle_event(event, self.child.computed_frame);
self.child.view.handle_event(event, self.child.computed_frame)
}
}
@@ -484,7 +484,7 @@ Dock :: struct {
d.preview_color = Color.rgba(77, 153, 255, 64);
d.enable_corners = true;
d.on_dock = null;
d;
d
}
add_panel :: (self: *Dock, panel: DockPanel) {
@@ -508,7 +508,7 @@ impl View for Dock {
Size.{
width = proposal.width ?? 800.0,
height = proposal.height ?? 600.0
};
}
}
layout :: (self: *Dock, bounds: Frame) {
@@ -687,6 +687,6 @@ impl View for Dock {
}
i -= 1;
}
false;
false
}
}

View File

@@ -52,7 +52,7 @@ event_position :: (e: *Event) -> ?Point {
case .mouse_moved: (d) { return d.position; }
case .mouse_wheel: (d) { return d.position; }
}
null;
null
}
// Map a platform (SDL) keycode to the neutral `Keycode`. Only the keys the app
@@ -72,7 +72,7 @@ keycode_from_sdl :: (k: SDL_Keycode) -> Keycode {
case .up: return .up;
case .down: return .down;
}
.unknown;
.unknown
}
// Translate SDL_Event → our Event type
@@ -129,5 +129,5 @@ translate_sdl_event :: (sdl: *SDL_Event) -> Event {
});
}
}
.none;
.none
}

View File

@@ -16,5 +16,5 @@ measure_text :: (text: string, font_size: f32) -> Size {
scale := font_size / 16.0;
return Size.{ width = xx text.len * 8.0 * scale, height = font_size };
}
g_font.measure_text(text, font_size);
g_font.measure_text(text, font_size)
}

View File

@@ -58,7 +58,7 @@ TapGesture :: struct {
return true;
}
}
false;
false
}
}
@@ -83,7 +83,7 @@ DragGesture :: struct {
location = self.current_location,
start_location = self.start_location,
translation = self.current_location.sub(self.start_location)
};
}
}
handle_event :: (self: *DragGesture, event: *Event, frame: Frame) -> bool {
@@ -123,6 +123,6 @@ DragGesture :: struct {
self.phase = .possible;
}
}
false;
false
}
}

View File

@@ -28,16 +28,16 @@ GlyphEntry :: struct {
// Quantize font size to half-point increments to limit cache entries.
// e.g., 13.0 -> 26, 13.5 -> 27, 14.0 -> 28
quantize_size :: (font_size: f32) -> u16 {
xx (font_size * 2.0 + 0.5);
xx (font_size * 2.0 + 0.5)
}
dequantize_size :: (q: u16) -> f32 {
xx q / 2.0;
xx q / 2.0
}
// Pack (glyph_index, size_quantized) into a single u32 for fast comparison
make_glyph_key :: (glyph_index: u16, size_quantized: u16) -> u32 {
(xx glyph_index << 16) | xx size_quantized;
(xx glyph_index << 16) | xx size_quantized
}
// Shaped glyph — output of text shaping (positioned glyph with index)
@@ -54,7 +54,7 @@ is_ascii :: (text: string) -> bool {
if text[i] >= 128 { return false; }
i += 1;
}
true;
true
}
// kbts constants (C enum values)
@@ -480,7 +480,7 @@ GlyphCache :: struct {
}
// No space
PackResult.{ x = 0 - 1, y = 0 - 1 };
PackResult.{ x = 0 - 1, y = 0 - 1 }
}
// Grow the atlas by doubling dimensions
@@ -547,18 +547,18 @@ GlyphCache :: struct {
// Get the scale factor for a logical font size
scale_for_size :: (self: *GlyphCache, font_size: f32) -> f32 {
stbtt_ScaleForPixelHeight(self.font_info, font_size);
stbtt_ScaleForPixelHeight(self.font_info, font_size)
}
// Get scaled ascent for a logical font size
get_ascent :: (self: *GlyphCache, font_size: f32) -> f32 {
self.ascent * self.scale_for_size(font_size);
self.ascent * self.scale_for_size(font_size)
}
// Get scaled line height for a logical font size
get_line_height :: (self: *GlyphCache, font_size: f32) -> f32 {
s := self.scale_for_size(font_size);
(self.ascent - self.descent + self.line_gap) * s;
(self.ascent - self.descent + self.line_gap) * s
}
// Shape text into positioned glyphs.
@@ -654,6 +654,6 @@ GlyphCache :: struct {
width += self.shaped_buf.items[i].advance;
i += 1;
}
Size.{ width = width, height = self.get_line_height(font_size) };
Size.{ width = width, height = self.get_line_height(font_size) }
}
}

View File

@@ -18,9 +18,9 @@ impl View for ImageView {
// Maintain aspect ratio: fit within proposal
aspect := self.width / self.height;
if pw / ph > aspect {
Size.{ width = ph * aspect, height = ph };
Size.{ width = ph * aspect, height = ph }
} else {
Size.{ width = pw, height = pw / aspect };
Size.{ width = pw, height = pw / aspect }
}
}
@@ -31,6 +31,6 @@ impl View for ImageView {
}
handle_event :: (self: *ImageView, event: *Event, frame: Frame) -> bool {
false;
false
}
}

View File

@@ -15,13 +15,13 @@ Label :: struct {
text = text,
font_size = 14.0,
color = COLOR_WHITE
};
}
}
}
impl View for Label {
size_that_fits :: (self: *Label, proposal: ProposedSize) -> Size {
measure_text(self.text, self.font_size);
measure_text(self.text, self.font_size)
}
layout :: (self: *Label, bounds: Frame) {
@@ -33,6 +33,6 @@ impl View for Label {
}
handle_event :: (self: *Label, event: *Event, frame: Frame) -> bool {
false;
false
}
}

View File

@@ -105,7 +105,7 @@ measure_vstack :: (children: *List(ViewChild), proposal: ProposedSize, spacing:
total_height = total_height + spacing * xx (n - 1);
result_width := min(proposal.width ?? max_width, max_width);
Size.{ width = result_width, height = total_height };
Size.{ width = result_width, height = total_height }
}
measure_hstack :: (children: *List(ViewChild), proposal: ProposedSize, spacing: f32) -> Size {
@@ -129,7 +129,7 @@ measure_hstack :: (children: *List(ViewChild), proposal: ProposedSize, spacing:
total_width = total_width + spacing * xx (n - 1);
result_height := min(proposal.height ?? max_height, max_height);
Size.{ width = total_width, height = result_height };
Size.{ width = total_width, height = result_height }
}
measure_zstack :: (children: *List(ViewChild), proposal: ProposedSize) -> Size {
@@ -148,5 +148,5 @@ measure_zstack :: (children: *List(ViewChild), proposal: ProposedSize) -> Size {
i += 1;
}
Size.{ width = max_width, height = max_height };
Size.{ width = max_width, height = max_height }
}

View File

@@ -24,7 +24,7 @@ impl View for PaddingModifier {
Size.{
width = child_size.width + self.insets.horizontal(),
height = child_size.height + self.insets.vertical()
};
}
}
layout :: (self: *PaddingModifier, bounds: Frame) {
@@ -37,7 +37,7 @@ impl View for PaddingModifier {
}
handle_event :: (self: *PaddingModifier, event: *Event, frame: Frame) -> bool {
self.child.view.handle_event(event, self.child.computed_frame);
self.child.view.handle_event(event, self.child.computed_frame)
}
}
@@ -58,7 +58,7 @@ impl View for FrameModifier {
Size.{
width = self.width ?? child_size.width,
height = self.height ?? child_size.height
};
}
}
layout :: (self: *FrameModifier, bounds: Frame) {
@@ -78,7 +78,7 @@ impl View for FrameModifier {
}
handle_event :: (self: *FrameModifier, event: *Event, frame: Frame) -> bool {
self.child.view.handle_event(event, self.child.computed_frame);
self.child.view.handle_event(event, self.child.computed_frame)
}
}
@@ -92,7 +92,7 @@ BackgroundModifier :: struct {
impl View for BackgroundModifier {
size_that_fits :: (self: *BackgroundModifier, proposal: ProposedSize) -> Size {
self.child.view.size_that_fits(proposal);
self.child.view.size_that_fits(proposal)
}
layout :: (self: *BackgroundModifier, bounds: Frame) {
@@ -110,7 +110,7 @@ impl View for BackgroundModifier {
}
handle_event :: (self: *BackgroundModifier, event: *Event, frame: Frame) -> bool {
self.child.view.handle_event(event, self.child.computed_frame);
self.child.view.handle_event(event, self.child.computed_frame)
}
}
@@ -123,7 +123,7 @@ OpacityModifier :: struct {
impl View for OpacityModifier {
size_that_fits :: (self: *OpacityModifier, proposal: ProposedSize) -> Size {
self.child.view.size_that_fits(proposal);
self.child.view.size_that_fits(proposal)
}
layout :: (self: *OpacityModifier, bounds: Frame) {
@@ -139,7 +139,7 @@ impl View for OpacityModifier {
}
handle_event :: (self: *OpacityModifier, event: *Event, frame: Frame) -> bool {
self.child.view.handle_event(event, self.child.computed_frame);
self.child.view.handle_event(event, self.child.computed_frame)
}
}
@@ -152,7 +152,7 @@ ClipModifier :: struct {
impl View for ClipModifier {
size_that_fits :: (self: *ClipModifier, proposal: ProposedSize) -> Size {
self.child.view.size_that_fits(proposal);
self.child.view.size_that_fits(proposal)
}
layout :: (self: *ClipModifier, bounds: Frame) {
@@ -167,7 +167,7 @@ impl View for ClipModifier {
}
handle_event :: (self: *ClipModifier, event: *Event, frame: Frame) -> bool {
self.child.view.handle_event(event, self.child.computed_frame);
self.child.view.handle_event(event, self.child.computed_frame)
}
}
@@ -181,7 +181,7 @@ HiddenModifier :: struct {
impl View for HiddenModifier {
size_that_fits :: (self: *HiddenModifier, proposal: ProposedSize) -> Size {
if self.is_hidden { return Size.zero(); }
self.child.view.size_that_fits(proposal);
self.child.view.size_that_fits(proposal)
}
layout :: (self: *HiddenModifier, bounds: Frame) {
@@ -197,7 +197,7 @@ impl View for HiddenModifier {
handle_event :: (self: *HiddenModifier, event: *Event, frame: Frame) -> bool {
if self.is_hidden { return false; }
self.child.view.handle_event(event, self.child.computed_frame);
self.child.view.handle_event(event, self.child.computed_frame)
}
}
@@ -210,7 +210,7 @@ TapGestureModifier :: struct {
impl View for TapGestureModifier {
size_that_fits :: (self: *TapGestureModifier, proposal: ProposedSize) -> Size {
self.child.view.size_that_fits(proposal);
self.child.view.size_that_fits(proposal)
}
layout :: (self: *TapGestureModifier, bounds: Frame) {
@@ -224,7 +224,7 @@ impl View for TapGestureModifier {
handle_event :: (self: *TapGestureModifier, event: *Event, frame: Frame) -> bool {
if self.gesture.handle_event(event, frame) { return true; }
self.child.view.handle_event(event, self.child.computed_frame);
self.child.view.handle_event(event, self.child.computed_frame)
}
}
@@ -237,7 +237,7 @@ DragGestureModifier :: struct {
impl View for DragGestureModifier {
size_that_fits :: (self: *DragGestureModifier, proposal: ProposedSize) -> Size {
self.child.view.size_that_fits(proposal);
self.child.view.size_that_fits(proposal)
}
layout :: (self: *DragGestureModifier, bounds: Frame) {
@@ -251,46 +251,46 @@ impl View for DragGestureModifier {
handle_event :: (self: *DragGestureModifier, event: *Event, frame: Frame) -> bool {
if self.gesture.handle_event(event, frame) { return true; }
self.child.view.handle_event(event, self.child.computed_frame);
self.child.view.handle_event(event, self.child.computed_frame)
}
}
// --- Convenience functions ---
padding :: (view: View, insets: EdgeInsets) -> PaddingModifier {
PaddingModifier.{ child = .{ view = view }, insets = insets };
PaddingModifier.{ child = .{ view = view }, insets = insets }
}
fixed_frame :: (view: View, width: ?f32, height: ?f32) -> FrameModifier {
FrameModifier.{ child = .{ view = view }, width = width, height = height };
FrameModifier.{ child = .{ view = view }, width = width, height = height }
}
background :: (view: View, color: Color, corner_radius: f32) -> BackgroundModifier {
BackgroundModifier.{ child = .{ view = view }, color = color, corner_radius = corner_radius };
BackgroundModifier.{ child = .{ view = view }, color = color, corner_radius = corner_radius }
}
with_opacity :: (view: View, alpha: f32) -> OpacityModifier {
OpacityModifier.{ child = .{ view = view }, alpha = alpha };
OpacityModifier.{ child = .{ view = view }, alpha = alpha }
}
clip :: (view: View, corner_radius: f32) -> ClipModifier {
ClipModifier.{ child = .{ view = view }, corner_radius = corner_radius };
ClipModifier.{ child = .{ view = view }, corner_radius = corner_radius }
}
hidden :: (view: View, is_hidden: bool) -> HiddenModifier {
HiddenModifier.{ child = .{ view = view }, is_hidden = is_hidden };
HiddenModifier.{ child = .{ view = view }, is_hidden = is_hidden }
}
on_tap :: (view: View, handler: Closure()) -> TapGestureModifier {
TapGestureModifier.{
child = .{ view = view },
gesture = TapGesture.{ count = 1, on_tap = handler }
};
}
}
on_drag :: (view: View, on_changed: ?Closure(DragValue), on_ended: ?Closure(DragValue)) -> DragGestureModifier {
DragGestureModifier.{
child = .{ view = view },
gesture = DragGesture.{ min_distance = 10.0, on_changed = on_changed, on_ended = on_ended }
};
}
}

View File

@@ -104,7 +104,7 @@ UIPipeline :: struct {
// Process a single event through the view tree
dispatch_event :: (self: *UIPipeline, event: *Event) -> bool {
if self.has_root == false { return false; }
self.root.view.handle_event(event, self.root.computed_frame);
self.root.view.handle_event(event, self.root.computed_frame)
}
// Run one frame: layout → render → commit

View File

@@ -43,7 +43,7 @@ RenderTree :: struct {
generation: s64;
init :: () -> RenderTree {
RenderTree.{ generation = 0 };
RenderTree.{ generation = 0 }
}
clear :: (self: *RenderTree) {
@@ -54,7 +54,7 @@ RenderTree :: struct {
add :: (self: *RenderTree, node: RenderNode) -> s64 {
idx := self.nodes.len;
self.nodes.append(node);
idx;
idx
}
}
@@ -72,7 +72,7 @@ RenderContext :: struct {
clip_depth = 0,
opacity = 1.0,
depth = 0
};
}
}
add_rect :: (self: *RenderContext, frame: Frame, fill: Color) {

View File

@@ -413,7 +413,7 @@ create_white_texture :: () -> u32 {
glTexImage2D(GL_TEXTURE_2D, 0, xx GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, @pixel);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, xx GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, xx GL_NEAREST);
tex;
tex
}
// --- UI Shaders ---

View File

@@ -51,7 +51,7 @@ impl View for ScrollView {
Size.{
width = proposal.width ?? 200.0,
height = proposal.height ?? 200.0
};
}
}
layout :: (self: *ScrollView, bounds: Frame) {
@@ -144,6 +144,6 @@ impl View for ScrollView {
return self.child.view.handle_event(event, self.child.computed_frame);
}
}
self.child.view.handle_event(event, self.child.computed_frame);
self.child.view.handle_event(event, self.child.computed_frame)
}
}

View File

@@ -18,7 +18,7 @@ VStack :: struct {
impl View for VStack {
size_that_fits :: (self: *VStack, proposal: ProposedSize) -> Size {
measure_vstack(@self.children, proposal, self.spacing);
measure_vstack(@self.children, proposal, self.spacing)
}
layout :: (self: *VStack, bounds: Frame) {
@@ -44,7 +44,7 @@ impl View for VStack {
}
i -= 1;
}
false;
false
}
}
@@ -60,7 +60,7 @@ HStack :: struct {
impl View for HStack {
size_that_fits :: (self: *HStack, proposal: ProposedSize) -> Size {
measure_hstack(@self.children, proposal, self.spacing);
measure_hstack(@self.children, proposal, self.spacing)
}
layout :: (self: *HStack, bounds: Frame) {
@@ -85,7 +85,7 @@ impl View for HStack {
}
i -= 1;
}
false;
false
}
}
@@ -100,7 +100,7 @@ ZStack :: struct {
impl View for ZStack {
size_that_fits :: (self: *ZStack, proposal: ProposedSize) -> Size {
measure_zstack(@self.children, proposal);
measure_zstack(@self.children, proposal)
}
layout :: (self: *ZStack, bounds: Frame) {
@@ -127,7 +127,7 @@ impl View for ZStack {
}
i -= 1;
}
false;
false
}
}
@@ -142,12 +142,12 @@ impl View for Spacer {
size_that_fits :: (self: *Spacer, proposal: ProposedSize) -> Size {
w := proposal.width ?? self.min_length;
h := proposal.height ?? self.min_length;
Size.{ width = max(w, self.min_length), height = max(h, self.min_length) };
Size.{ width = max(w, self.min_length), height = max(h, self.min_length) }
}
layout :: (self: *Spacer, bounds: Frame) {}
render :: (self: *Spacer, ctx: *RenderContext, frame: Frame) {}
handle_event :: (self: *Spacer, event: *Event, frame: Frame) -> bool { false; }
handle_event :: (self: *Spacer, event: *Event, frame: Frame) -> bool { false }
}
// Rect — simple colored rectangle view
@@ -164,7 +164,7 @@ impl View for RectView {
size_that_fits :: (self: *RectView, proposal: ProposedSize) -> Size {
w := proposal.width ?? self.preferred_width;
h := proposal.height ?? self.preferred_height;
Size.{ width = w, height = h };
Size.{ width = w, height = h }
}
layout :: (self: *RectView, bounds: Frame) {}
@@ -177,5 +177,5 @@ impl View for RectView {
}
}
handle_event :: (self: *RectView, event: *Event, frame: Frame) -> bool { false; }
handle_event :: (self: *RectView, event: *Event, frame: Frame) -> bool { false }
}

View File

@@ -5,7 +5,7 @@
State :: struct ($T: Type) {
ptr: *T;
get :: (self: State(T)) -> T { self.ptr.*; }
get :: (self: State(T)) -> T { self.ptr.* }
set :: (self: State(T), val: T) { self.ptr.* = val; }
}
@@ -52,7 +52,7 @@ StateStore :: struct {
size = size_of(T),
generation = self.current_generation
}, self.parent_allocator);
State(T).{ ptr = xx data };
State(T).{ ptr = xx data }
}
next_frame :: (self: *StateStore) {

View File

@@ -24,7 +24,7 @@ impl View for StatsPanel {
fps_size := measure_text("FPS: 0000", StatsPanel.VALUE_SIZE);
w := max(title_size.width, fps_size.width) + StatsPanel.PADDING * 2.0;
h := title_size.height + StatsPanel.LINE_SPACING + fps_size.height + StatsPanel.PADDING * 2.0;
Size.{ width = w, height = h };
Size.{ width = w, height = h }
}
layout :: (self: *StatsPanel, bounds: Frame) {}
@@ -58,6 +58,6 @@ impl View for StatsPanel {
}
handle_event :: (self: *StatsPanel, event: *Event, frame: Frame) -> bool {
false;
false
}
}

View File

@@ -7,21 +7,21 @@ Point :: struct {
zero :: () -> Point => .{ x = 0.0, y = 0.0 };
add :: (self: Point, b: Point) -> Point {
Point.{ x = self.x + b.x, y = self.y + b.y };
Point.{ x = self.x + b.x, y = self.y + b.y }
}
sub :: (self: Point, b: Point) -> Point {
Point.{ x = self.x - b.x, y = self.y - b.y };
Point.{ x = self.x - b.x, y = self.y - b.y }
}
scale :: (self: Point, s: f32) -> Point {
Point.{ x = self.x * s, y = self.y * s };
Point.{ x = self.x * s, y = self.y * s }
}
distance :: (self: Point, b: Point) -> f32 {
dx := self.x - b.x;
dy := self.y - b.y;
sqrt(dx * dx + dy * dy);
sqrt(dx * dx + dy * dy)
}
}
@@ -31,7 +31,7 @@ Size :: struct {
zero :: () -> Size => .{ width = 0.0, height = 0.0 };
contains :: (self: Size, point: Point) -> bool {
point.x >= 0.0 and point.x <= self.width and point.y >= 0.0 and point.y <= self.height;
point.x >= 0.0 and point.x <= self.width and point.y >= 0.0 and point.y <= self.height
}
}
@@ -39,20 +39,20 @@ Frame :: struct {
origin: Point;
size: Size;
zero :: () -> Frame { Frame.{ origin = Point.zero(), size = Size.zero() }; }
zero :: () -> Frame { Frame.{ origin = Point.zero(), size = Size.zero() } }
make :: (x: f32, y: f32, w: f32, h: f32) -> Frame {
Frame.{ origin = Point.{ x = x, y = y }, size = Size.{ width = w, height = h } };
Frame.{ origin = Point.{ x = x, y = y }, size = Size.{ width = w, height = h } }
}
max_x :: (self: Frame) -> f32 { self.origin.x + self.size.width; }
max_y :: (self: Frame) -> f32 { self.origin.y + self.size.height; }
mid_x :: (self: Frame) -> f32 { self.origin.x + self.size.width * 0.5; }
mid_y :: (self: Frame) -> f32 { self.origin.y + self.size.height * 0.5; }
max_x :: (self: Frame) -> f32 { self.origin.x + self.size.width }
max_y :: (self: Frame) -> f32 { self.origin.y + self.size.height }
mid_x :: (self: Frame) -> f32 { self.origin.x + self.size.width * 0.5 }
mid_y :: (self: Frame) -> f32 { self.origin.y + self.size.height * 0.5 }
contains :: (self: Frame, point: Point) -> bool {
point.x >= self.origin.x and point.x <= self.max_x()
and point.y >= self.origin.y and point.y <= self.max_y();
and point.y >= self.origin.y and point.y <= self.max_y()
}
intersection :: (self: Frame, other: Frame) -> Frame {
@@ -62,7 +62,7 @@ Frame :: struct {
y2 := min(self.max_y(), other.max_y());
if x2 <= x1 or y2 <= y1
then .zero()
else .make(x1, y1, x2 - x1, y2 - y1);
else .make(x1, y1, x2 - x1, y2 - y1)
}
inset :: (self: Frame, insets: EdgeInsets) -> Frame {
@@ -71,7 +71,7 @@ Frame :: struct {
self.origin.y + insets.top,
self.size.width - insets.left - insets.right,
self.size.height - insets.top - insets.bottom
);
)
}
expand :: (self: Frame, amount: f32) -> Frame {
@@ -80,45 +80,45 @@ Frame :: struct {
self.origin.y - amount,
self.size.width + amount * 2.0,
self.size.height + amount * 2.0
);
)
}
}
EdgeInsets :: struct {
top, left, bottom, right: f32;
zero :: () -> EdgeInsets { EdgeInsets.{ top = 0.0, left = 0.0, bottom = 0.0, right = 0.0 }; }
zero :: () -> EdgeInsets { EdgeInsets.{ top = 0.0, left = 0.0, bottom = 0.0, right = 0.0 } }
all :: (v: f32) -> EdgeInsets {
EdgeInsets.{ top = v, left = v, bottom = v, right = v };
EdgeInsets.{ top = v, left = v, bottom = v, right = v }
}
symmetric :: (h: f32, v: f32) -> EdgeInsets {
EdgeInsets.{ top = v, left = h, bottom = v, right = h };
EdgeInsets.{ top = v, left = h, bottom = v, right = h }
}
horizontal :: (self: EdgeInsets) -> f32 { self.left + self.right; }
vertical :: (self: EdgeInsets) -> f32 { self.top + self.bottom; }
horizontal :: (self: EdgeInsets) -> f32 { self.left + self.right }
vertical :: (self: EdgeInsets) -> f32 { self.top + self.bottom }
}
Color :: struct {
r, g, b, a: u8;
rgba :: (r: u8, g: u8, b: u8, a: u8) -> Color {
Color.{ r = r, g = g, b = b, a = a };
Color.{ r = r, g = g, b = b, a = a }
}
rgb :: (r: u8, g: u8, b: u8) -> Color {
Color.{ r = r, g = g, b = b, a = 255 };
Color.{ r = r, g = g, b = b, a = 255 }
}
rf :: (self: Color) -> f32 { xx self.r / 255.0; }
gf :: (self: Color) -> f32 { xx self.g / 255.0; }
bf :: (self: Color) -> f32 { xx self.b / 255.0; }
af :: (self: Color) -> f32 { xx self.a / 255.0; }
rf :: (self: Color) -> f32 { xx self.r / 255.0 }
gf :: (self: Color) -> f32 { xx self.g / 255.0 }
bf :: (self: Color) -> f32 { xx self.b / 255.0 }
af :: (self: Color) -> f32 { xx self.a / 255.0 }
with_alpha :: (self: Color, a: u8) -> Color {
Color.{ r = self.r, g = self.g, b = self.b, a = a };
Color.{ r = self.r, g = self.g, b = self.b, a = a }
}
lerp :: (self: Color, b: Color, t: f32) -> Color {
@@ -127,7 +127,7 @@ Color :: struct {
g = xx (self.g + (b.g - self.g) * t),
b = xx (self.b + (b.b - self.b) * t),
a = xx (self.a + (b.a - self.a) * t),
};
}
}
}
@@ -150,11 +150,11 @@ ProposedSize :: struct {
height: ?f32;
fixed :: (w: f32, h: f32) -> ProposedSize {
ProposedSize.{ width = w, height = h };
ProposedSize.{ width = w, height = h }
}
flexible :: () -> ProposedSize {
ProposedSize.{ width = null, height = null };
ProposedSize.{ width = null, height = null }
}
}
@@ -189,7 +189,7 @@ ALIGN_BOTTOM_TRAILING :: Alignment.{ h = .trailing, v = .bottom };
align_h :: (alignment: HAlignment, child_width: f32, container_width: f32) -> f32 {
if alignment == {
case .leading: 0.0;
case .center: { (container_width - child_width) * 0.5; }
case .center: { (container_width - child_width) * 0.5 }
case .trailing: container_width - child_width;
}
}
@@ -198,7 +198,7 @@ align_h :: (alignment: HAlignment, child_width: f32, container_width: f32) -> f3
align_v :: (alignment: VAlignment, child_height: f32, container_height: f32) -> f32 {
if alignment == {
case .top: 0.0;
case .center: { (container_height - child_height) * 0.5; }
case .center: { (container_height - child_height) * 0.5 }
case .bottom: container_height - child_height;
}
}
@@ -209,12 +209,12 @@ align_v :: (alignment: VAlignment, child_height: f32, container_height: f32) ->
impl Lerpable for Point {
lerp :: (self: Point, b: Point, t: f32) -> Point {
Point.{ x = self.x + (b.x - self.x) * t, y = self.y + (b.y - self.y) * t };
Point.{ x = self.x + (b.x - self.x) * t, y = self.y + (b.y - self.y) * t }
}
}
impl Lerpable for Size {
lerp :: (self: Size, b: Size, t: f32) -> Size {
Size.{ width = self.width + (b.width - self.width) * t, height = self.height + (b.height - self.height) * t };
Size.{ width = self.width + (b.width - self.width) * t, height = self.height + (b.height - self.height) * t }
}
}