This commit is contained in:
agra
2026-03-02 17:18:47 +02:00
parent ba9c4d69ce
commit 2f4f898d54
20 changed files with 418 additions and 49 deletions

3
.gitignore vendored
View File

@@ -2,4 +2,5 @@
zig-out zig-out
.DS_Store .DS_Store
.vscode/ .vscode/
.sx-cache .sx-cache
current/

View File

@@ -1,5 +1,6 @@
#import "modules/std.sx"; #import "modules/std.sx";
#import "modules/math/math.sx"; #import "modules/math/math.sx";
#import "modules/compiler.sx";
pkg :: #import "modules/testpkg"; pkg :: #import "modules/testpkg";
// ============================================================ // ============================================================
@@ -3064,5 +3065,60 @@ END;
print("opt-if5: {}\n", val2 ?? 0.0); print("opt-if5: {}\n", val2 ?? 0.0);
} }
// --- usize / isize ---
{
a : usize = 42;
b : isize = 0 - 7;
print("usize: {}\n", a);
print("isize: {}\n", b);
// arithmetic
c : usize = a + 8;
print("usize+8: {}\n", c);
// coercion from s32
x : s32 = 10;
y : usize = xx x;
print("s32->usize: {}\n", y);
// coercion to s64
z : s64 = xx a;
print("usize->s64: {}\n", z);
}
// --- inline if (compile-time conditionals) ---
print("=== inline if ===\n");
{
// POINTER_SIZE is 8 on desktop (64-bit)
inline if POINTER_SIZE == 8 {
print("64-bit\n");
} else {
print("32-bit\n");
}
// OS enum comparison
inline if OS == .wasm {
print("wasm\n");
} else {
print("not wasm\n");
}
// != comparison
inline if OS != .unknown {
print("known os\n");
} else {
print("unknown os\n");
}
// nested inline if
inline if POINTER_SIZE != 4 {
inline if OS != .wasm {
print("desktop 64-bit\n");
} else {
print("wasm 64-bit??\n");
}
}
}
print("=== DONE ===\n"); print("=== DONE ===\n");
} }

View File

@@ -0,0 +1,6 @@
OperatingSystem :: enum { macos; linux; windows; wasm; unknown; }
Architecture :: enum { aarch64; x86_64; wasm32; unknown; }
OS : OperatingSystem = .unknown;
ARCH : Architecture = .unknown;
POINTER_SIZE : s64 = 8;

View File

@@ -49,11 +49,11 @@ glGenVertexArrays : (s32, *u32) -> void = ---;
glGenBuffers : (s32, *u32) -> void = ---; glGenBuffers : (s32, *u32) -> void = ---;
glBindVertexArray : (u32) -> void = ---; glBindVertexArray : (u32) -> void = ---;
glBindBuffer : (u32, u32) -> void = ---; glBindBuffer : (u32, u32) -> void = ---;
glBufferData : (u32, s64, *void, u32) -> void = ---; glBufferData : (u32, isize, *void, u32) -> void = ---;
glVertexAttribPointer : (u32, s32, u32, u8, s32, *void) -> void = ---; glVertexAttribPointer : (u32, s32, u32, u8, s32, *void) -> void = ---;
glEnableVertexAttribArray : (u32) -> void = ---; glEnableVertexAttribArray : (u32) -> void = ---;
glGetUniformLocation : (u32, [:0]u8) -> s32 = ---; glGetUniformLocation : (u32, [*]u8) -> s32 = ---;
glUniformMatrix4fv : (s32, s32, u8, [16]f32) -> void = ---; glUniformMatrix4fv : (s32, s32, u8, [*]f32) -> void = ---;
glUniform3f : (s32, f32, f32, f32) -> void = ---; glUniform3f : (s32, f32, f32, f32) -> void = ---;
glDepthFunc : (u32) -> void = ---; glDepthFunc : (u32) -> void = ---;
glUniform1f : (s32, f32) -> void = ---; glUniform1f : (s32, f32) -> void = ---;
@@ -72,10 +72,11 @@ GL_ONE_MINUS_SRC_ALPHA :u32: 0x0303;
GL_TEXTURE0 :u32: 0x84C0; GL_TEXTURE0 :u32: 0x84C0;
GL_LINEAR :u32: 0x2601; GL_LINEAR :u32: 0x2601;
GL_RED :u32: 0x1903; GL_RED :u32: 0x1903;
GL_R8 :u32: 0x8229;
GL_UNPACK_ALIGNMENT :u32: 0x0CF5; GL_UNPACK_ALIGNMENT :u32: 0x0CF5;
glScissor : (s32, s32, s32, s32) -> void = ---; glScissor : (s32, s32, s32, s32) -> void = ---;
glBufferSubData : (u32, s64, s64, *void) -> void = ---; glBufferSubData : (u32, isize, isize, *void) -> void = ---;
glGenTextures : (s32, *u32) -> void = ---; glGenTextures : (s32, *u32) -> void = ---;
glBindTexture : (u32, u32) -> void = ---; glBindTexture : (u32, u32) -> void = ---;
glTexImage2D : (u32, s32, s32, s32, s32, s32, u32, u32, *void) -> void = ---; glTexImage2D : (u32, s32, s32, s32, s32, s32, u32, u32, *void) -> void = ---;
@@ -88,7 +89,7 @@ glPixelStorei : (u32, s32) -> void = ---;
// Loader: call once after creating GL context // Loader: call once after creating GL context
// Pass in a proc loader (e.g. SDL_GL_GetProcAddress) // Pass in a proc loader (e.g. SDL_GL_GetProcAddress)
load_gl :: (get_proc: ([:0]u8) -> *void) { load_gl :: (get_proc: ([*]u8) -> *void) {
glClearColor = xx get_proc("glClearColor"); glClearColor = xx get_proc("glClearColor");
glClear = xx get_proc("glClear"); glClear = xx get_proc("glClear");
glEnable = xx get_proc("glEnable"); glEnable = xx get_proc("glEnable");

View File

@@ -16,6 +16,7 @@ SDL_GL_CONTEXT_PROFILE_MASK :s32: 20;
// SDL_GLProfile // SDL_GLProfile
SDL_GL_CONTEXT_PROFILE_CORE :s32: 0x1; SDL_GL_CONTEXT_PROFILE_CORE :s32: 0x1;
SDL_GL_CONTEXT_PROFILE_ES :s32: 0x4;
// SDL_GLContextFlag // SDL_GLContextFlag
SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG :s32: 0x2; SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG :s32: 0x2;

View File

@@ -9,8 +9,8 @@ setsockopt :: (fd: s32, level: s32, optname: s32, optval: *s32, optlen: u32) ->
bind :: (fd: s32, addr: *SockAddr, addrlen: u32) -> s32 #foreign libc; bind :: (fd: s32, addr: *SockAddr, addrlen: u32) -> s32 #foreign libc;
listen :: (fd: s32, backlog: s32) -> s32 #foreign libc; listen :: (fd: s32, backlog: s32) -> s32 #foreign libc;
accept :: (fd: s32, addr: *SockAddr, addrlen: *u32) -> s32 #foreign libc; accept :: (fd: s32, addr: *SockAddr, addrlen: *u32) -> s32 #foreign libc;
read :: (fd: s32, buf: [*]u8, count: s64) -> s64 #foreign libc; read :: (fd: s32, buf: [*]u8, count: usize) -> isize #foreign libc;
write :: (fd: s32, buf: [*]u8, count: s64) -> s64 #foreign libc; write :: (fd: s32, buf: [*]u8, count: usize) -> isize #foreign libc;
close :: (fd: s32) -> s32 #foreign libc; close :: (fd: s32) -> s32 #foreign libc;
// Constants (macOS) // Constants (macOS)

View File

@@ -208,6 +208,7 @@ pub const IfExpr = struct {
then_branch: *Node, then_branch: *Node,
else_branch: ?*Node, else_branch: ?*Node,
is_inline: bool, // true for `if cond then a else b` is_inline: bool, // true for `if cond then a else b`
is_comptime: bool = false, // true for `inline if` — compile-time branch elimination
binding_name: ?[]const u8 = null, // for `if val := expr { ... }` optional binding binding_name: ?[]const u8 = null, // for `if val := expr { ... }` optional binding
}; };

View File

@@ -303,6 +303,53 @@ pub fn loadCObjectsForJIT(
}; };
} }
/// Compile C sources using emcc for Emscripten/WASM targets.
/// Shells out to `emcc -c` for each source file, returns temp object file paths.
pub fn compileCWithEmcc(
allocator: std.mem.Allocator,
io: std.Io,
infos: []const CImportInfo,
) ![]const []const u8 {
var paths = std.ArrayList([]const u8).empty;
var obj_idx: usize = 0;
for (infos) |info| {
if (info.sources.len == 0) continue;
for (info.sources) |src| {
const out_path = try std.fmt.allocPrint(allocator, "/tmp/sx_emcc_{d}.o", .{obj_idx});
obj_idx += 1;
var argv = std.ArrayList([]const u8).empty;
try argv.appendSlice(allocator, &.{ "emcc", "-c", "-O2", src, "-o", out_path });
// Add include paths
for (info.includes) |inc| {
try argv.append(allocator, try std.fmt.allocPrint(allocator, "-I{s}", .{dirName(inc)}));
}
for (info.defines) |def| {
try argv.append(allocator, try std.fmt.allocPrint(allocator, "-D{s}", .{def}));
}
for (info.flags) |flag| {
try argv.append(allocator, flag);
}
const argv_slice = try argv.toOwnedSlice(allocator);
var child = std.process.spawn(io, .{ .argv = argv_slice }) catch {
std.debug.print("error: failed to spawn emcc for '{s}'\n", .{src});
return error.CompileError;
};
const result = child.wait(io) catch return error.CompileError;
if (result != .exited or result.exited != 0) {
std.debug.print("error: emcc failed for '{s}'\n", .{src});
return error.CompileError;
}
try paths.append(allocator, out_path);
}
}
return try paths.toOwnedSlice(allocator);
}
/// For build mode: write .o buffers to temp files, return paths for the linker. /// For build mode: write .o buffers to temp files, return paths for the linker.
pub fn writeCObjectFiles( pub fn writeCObjectFiles(
allocator: std.mem.Allocator, allocator: std.mem.Allocator,

View File

@@ -116,9 +116,14 @@ pub const Compilation = struct {
pub fn lowerToIR(self: *Compilation) ir.Module { pub fn lowerToIR(self: *Compilation) ir.Module {
const root = self.resolved_root orelse self.root orelse return ir.Module.init(self.allocator); const root = self.resolved_root orelse self.root orelse return ir.Module.init(self.allocator);
var module = ir.Module.init(self.allocator); var module = ir.Module.init(self.allocator);
//TODO: find a better place for this
if (self.target_config.isWasm()) {
module.types.pointer_size = 4;
}
var lowering = ir.Lowering.init(&module); var lowering = ir.Lowering.init(&module);
lowering.main_file = self.file_path; lowering.main_file = self.file_path;
lowering.resolved_root = root; lowering.resolved_root = root;
lowering.target_config = self.target_config;
lowering.lowerRoot(root); lowering.lowerRoot(root);
return module; return module;
} }

View File

@@ -2103,8 +2103,9 @@ pub const LLVMEmitter = struct {
} }
// int→int: SExt, ZExt, or Trunc // int→int: SExt, ZExt, or Trunc
const from_bits = intBits(from); const ptr_bits: u32 = @as(u32, self.ir_mod.types.pointer_size) * 8;
const to_bits = intBits(to); const from_bits = if (intBits(from) == 0) ptr_bits else intBits(from);
const to_bits = if (intBits(to) == 0) ptr_bits else intBits(to);
if (to_bits > from_bits) { if (to_bits > from_bits) {
return if (isSignedType(from)) return if (isSignedType(from))
c.LLVMBuildSExt(self.builder, operand, to_ty, "sext") c.LLVMBuildSExt(self.builder, operand, to_ty, "sext")
@@ -2539,6 +2540,7 @@ pub const LLVMEmitter = struct {
.string => self.getStringStructType(), .string => self.getStringStructType(),
.any => self.getAnyStructType(), .any => self.getAnyStructType(),
.noreturn => self.cached_void, .noreturn => self.cached_void,
.isize, .usize => if (self.target_config.isWasm()) self.cached_i32 else self.cached_i64,
else => self.toLLVMTypeInfo(ty), else => self.toLLVMTypeInfo(ty),
}; };
} }
@@ -2665,6 +2667,7 @@ pub const LLVMEmitter = struct {
// For now, use opaque ptr // For now, use opaque ptr
return self.cached_ptr; return self.cached_ptr;
}, },
.usize, .isize => if (self.target_config.isWasm()) self.cached_i32 else self.cached_i64,
}; };
} }
@@ -2684,12 +2687,11 @@ pub const LLVMEmitter = struct {
if (info == .slice) return self.cached_ptr; if (info == .slice) return self.cached_ptr;
} }
// WASM32: i64 → i32 for C ABI (size_t/ssize_t are 32-bit on wasm32) // WASM32: usize/isize are pointer-sized (i32 on wasm32).
// Other integer types (s64, u64) keep their declared size — they represent
// genuinely 64-bit values (SDL_WindowFlags, timestamps, etc.).
if (self.target_config.isWasm()) { if (self.target_config.isWasm()) {
if (c.LLVMGetTypeKind(llvm_ty) == c.LLVMIntegerTypeKind and c.LLVMGetIntTypeWidth(llvm_ty) == 64) { if (ir_ty == .usize or ir_ty == .isize) return self.cached_i32;
// s64/u64 in extern decls → i32 on wasm32 (matches C's size_t, ssize_t, etc.)
return self.cached_i32;
}
return llvm_ty; return llvm_ty;
} }
@@ -3075,7 +3077,7 @@ fn isFloatOrVecFloat(ty: TypeId, types: *const TypeTable) bool {
fn isSignedType(ty: TypeId) bool { fn isSignedType(ty: TypeId) bool {
return switch (ty) { return switch (ty) {
.s8, .s16, .s32, .s64 => true, .s8, .s16, .s32, .s64, .isize => true,
else => false, else => false,
}; };
} }
@@ -3095,6 +3097,7 @@ fn intBits(ty: TypeId) u32 {
.s32, .u32 => 32, .s32, .u32 => 32,
.s64, .u64 => 64, .s64, .u64 => 64,
.bool => 1, .bool => 1,
.usize, .isize => 0, // target-dependent — caller must query pointer_size
else => 64, else => 64,
}; };
} }

View File

@@ -104,6 +104,13 @@ pub const Lowering = struct {
foreign_name_map: std.StringHashMap([]const u8) = std.StringHashMap([]const u8).init(std.heap.page_allocator), // sx name → C name for #foreign renames foreign_name_map: std.StringHashMap([]const u8) = std.StringHashMap([]const u8).init(std.heap.page_allocator), // sx name → C name for #foreign renames
type_alias_map: std.StringHashMap(TypeId) = std.StringHashMap(TypeId).init(std.heap.page_allocator), // type alias name → target TypeId type_alias_map: std.StringHashMap(TypeId) = std.StringHashMap(TypeId).init(std.heap.page_allocator), // type alias name → target TypeId
ufcs_alias_map: std.StringHashMap([]const u8) = std.StringHashMap([]const u8).init(std.heap.page_allocator), // UFCS alias name → target function name ufcs_alias_map: std.StringHashMap([]const u8) = std.StringHashMap([]const u8).init(std.heap.page_allocator), // UFCS alias name → target function name
target_config: ?@import("../target.zig").TargetConfig = null, // compilation target (for inline if)
comptime_constants: std.StringHashMap(ComptimeValue) = std.StringHashMap(ComptimeValue).init(std.heap.page_allocator), // compile-time known constants (e.g. OS, ARCH)
pub const ComptimeValue = union(enum) {
int_val: i64,
enum_tag: struct { ty: TypeId, tag: u32 },
};
const StructConstInfo = struct { const StructConstInfo = struct {
value: *const Node, value: *const Node,
@@ -165,12 +172,68 @@ pub const Lowering = struct {
}; };
// Pass 1: scan — register all function ASTs, struct types, extern stubs // Pass 1: scan — register all function ASTs, struct types, extern stubs
self.scanDecls(decls); self.scanDecls(decls);
// Pass 1b: inject compile-time constants (OS, ARCH, POINTER_SIZE) from target config
self.injectComptimeConstants();
// Pass 2: lower main (and comptime side-effects) // Pass 2: lower main (and comptime side-effects)
self.lowerMainAndComptime(decls); self.lowerMainAndComptime(decls);
// Pass 3: lower deferred functions (any_to_string etc.) now that all types are registered // Pass 3: lower deferred functions (any_to_string etc.) now that all types are registered
self.lowerDeferredTypeFns(); self.lowerDeferredTypeFns();
} }
/// Inject compile-time constants from target_config into comptime_constants.
/// Called after scanDecls so that enum types (OperatingSystem, Architecture) are registered.
fn injectComptimeConstants(self: *Lowering) void {
const tc = self.target_config orelse return;
// OS: OperatingSystem enum { macos; linux; windows; wasm; unknown; }
const os_name_id = self.module.types.internString("OperatingSystem");
if (self.module.types.findByName(os_name_id)) |os_ty| {
const os_info = self.module.types.get(os_ty);
if (os_info == .@"enum") {
const tag: u32 = if (tc.isWasm())
self.findVariantIndex(os_info.@"enum".variants, "wasm")
else if (tc.isWindows())
self.findVariantIndex(os_info.@"enum".variants, "windows")
else if (tc.isLinux())
self.findVariantIndex(os_info.@"enum".variants, "linux")
else if (tc.isMacOS())
self.findVariantIndex(os_info.@"enum".variants, "macos")
else
self.findVariantIndex(os_info.@"enum".variants, "unknown");
self.comptime_constants.put("OS", .{ .enum_tag = .{ .ty = os_ty, .tag = tag } }) catch {};
}
}
// ARCH: Architecture enum { aarch64; x86_64; wasm32; unknown; }
const arch_name_id = self.module.types.internString("Architecture");
if (self.module.types.findByName(arch_name_id)) |arch_ty| {
const arch_info = self.module.types.get(arch_ty);
if (arch_info == .@"enum") {
const tag: u32 = if (tc.isWasm())
self.findVariantIndex(arch_info.@"enum".variants, "wasm32")
else if (tc.isAarch64())
self.findVariantIndex(arch_info.@"enum".variants, "aarch64")
else if (tc.isX86_64())
self.findVariantIndex(arch_info.@"enum".variants, "x86_64")
else
self.findVariantIndex(arch_info.@"enum".variants, "unknown");
self.comptime_constants.put("ARCH", .{ .enum_tag = .{ .ty = arch_ty, .tag = tag } }) catch {};
}
}
// POINTER_SIZE: s64 (4 for wasm, 8 otherwise)
const ptr_size: i64 = if (tc.isWasm()) 4 else 8;
self.comptime_constants.put("POINTER_SIZE", .{ .int_val = ptr_size }) catch {};
}
fn findVariantIndex(self: *Lowering, variants: []const types.StringId, name: []const u8) u32 {
const name_id = self.module.types.internString(name);
for (variants, 0..) |v, i| {
if (v == name_id) return @intCast(i);
}
return 0; // fallback to first variant
}
/// Lower functions that were deferred because they use type-category matching. /// Lower functions that were deferred because they use type-category matching.
/// At this point, main is fully lowered and all types are in the TypeTable. /// At this point, main is fully lowered and all types are in the TypeTable.
fn lowerDeferredTypeFns(self: *Lowering) void { fn lowerDeferredTypeFns(self: *Lowering) void {
@@ -381,6 +444,8 @@ pub const Lowering = struct {
const init_val: ?inst_mod.ConstantValue = if (vd.value) |v| switch (v.data) { const init_val: ?inst_mod.ConstantValue = if (vd.value) |v| switch (v.data) {
.undef_literal => .zeroinit, .undef_literal => .zeroinit,
.int_literal => |il| .{ .int = il.value }, .int_literal => |il| .{ .int = il.value },
.bool_literal => |bl| .{ .boolean = bl.value },
.float_literal => |fl| .{ .float = fl.value },
.string_literal => |sl| .{ .string = self.module.types.internString(sl.raw) }, .string_literal => |sl| .{ .string = self.module.types.internString(sl.raw) },
else => null, else => null,
} else null; } else null;
@@ -723,6 +788,15 @@ pub const Lowering = struct {
} }
} }
/// Lower an `inline if` branch — block body emits statements, expression returns value.
fn lowerInlineBranch(self: *Lowering, node: *const Node) Ref {
if (node.data == .block) {
self.lowerBlock(node);
return self.builder.constInt(0, .void);
}
return self.lowerExpr(node);
}
/// Lower a block and return the last expression's value (for implicit returns). /// Lower a block and return the last expression's value (for implicit returns).
fn lowerBlockValue(self: *Lowering, node: *const Node) ?Ref { fn lowerBlockValue(self: *Lowering, node: *const Node) ?Ref {
// Set force_block_value so nested if-else expressions produce values // Set force_block_value so nested if-else expressions produce values
@@ -1845,6 +1919,19 @@ pub const Lowering = struct {
// ── Control flow ──────────────────────────────────────────────── // ── Control flow ────────────────────────────────────────────────
fn lowerIfExpr(self: *Lowering, ie: *const ast.IfExpr) Ref { fn lowerIfExpr(self: *Lowering, ie: *const ast.IfExpr) Ref {
// inline if: evaluate condition at compile time, only lower taken branch
if (ie.is_comptime) {
if (self.evalComptimeCondition(ie.condition)) |is_true| {
if (is_true) {
return self.lowerInlineBranch(ie.then_branch);
} else if (ie.else_branch) |eb| {
return self.lowerInlineBranch(eb);
}
return self.builder.constInt(0, .void);
}
// Condition couldn't be evaluated — fall through to runtime
}
// Check for constant-bool conditions (e.g., is_flags(T) → false) to avoid dead-code LLVM errors // Check for constant-bool conditions (e.g., is_flags(T) → false) to avoid dead-code LLVM errors
if (self.tryConstBoolCondition(ie.condition)) |is_true| { if (self.tryConstBoolCondition(ie.condition)) |is_true| {
if (is_true) { if (is_true) {
@@ -1999,6 +2086,46 @@ pub const Lowering = struct {
return null; return null;
} }
/// Evaluate a compile-time condition for `inline if`.
/// Handles: `ident == .variant`, `ident != .variant`, `ident == int`, `ident != int`.
fn evalComptimeCondition(self: *Lowering, node: *const Node) ?bool {
if (node.data != .binary_op) return null;
const bo = &node.data.binary_op;
if (bo.op != .eq and bo.op != .neq) return null;
// LHS must be an identifier that's in comptime_constants
const name = switch (bo.lhs.data) {
.identifier => |id| id.name,
else => return null,
};
const cv = self.comptime_constants.get(name) orelse return null;
switch (cv) {
.enum_tag => |et| {
// RHS must be an enum literal (.variant)
const variant_name = switch (bo.rhs.data) {
.enum_literal => |el| el.name,
else => return null,
};
// Look up variant index in the enum type
const enum_info = self.module.types.get(et.ty);
if (enum_info != .@"enum") return null;
const variant_idx = self.findVariantIndex(enum_info.@"enum".variants, variant_name);
const result = et.tag == variant_idx;
return if (bo.op == .eq) result else !result;
},
.int_val => |iv| {
// RHS must be an integer literal
const rhs_val: i64 = switch (bo.rhs.data) {
.int_literal => |il| il.value,
else => return null,
};
const result = iv == rhs_val;
return if (bo.op == .eq) result else !result;
},
}
}
fn lowerWhile(self: *Lowering, we: *const ast.WhileExpr) Ref { fn lowerWhile(self: *Lowering, we: *const ast.WhileExpr) Ref {
const header_bb = self.freshBlock("while.hdr"); const header_bb = self.freshBlock("while.hdr");
const body_bb = self.freshBlock("while.body"); const body_bb = self.freshBlock("while.body");
@@ -2857,6 +2984,10 @@ pub const Lowering = struct {
const info = self.module.types.get(obj_ty); const info = self.module.types.get(obj_ty);
switch (info) { switch (info) {
.tagged_union => |u| { .tagged_union => |u| {
// .tag → extract the enum tag value with the correct tag type
if (std.mem.eql(u8, field, "tag")) {
return self.builder.emit(.{ .enum_tag = .{ .operand = obj } }, u.tag_type);
}
// Tagged union — use enum_payload // Tagged union — use enum_payload
for (u.fields, 0..) |f, i| { for (u.fields, 0..) |f, i| {
if (f.name == field_name_id) { if (f.name == field_name_id) {
@@ -3727,8 +3858,19 @@ pub const Lowering = struct {
} }
// Method call: obj.method(args) → prepend obj (or &obj for *Self receivers) // Method call: obj.method(args) → prepend obj (or &obj for *Self receivers)
const obj_ty = self.inferExprType(fa.object); // For ptr.*.method(): pass the pointer directly instead of loading + re-addressing.
const obj = self.lowerExpr(fa.object); // This ensures mutations through self: *T are visible after the call.
var obj_ty: TypeId = undefined;
var obj: Ref = undefined;
var effective_obj_node: *const Node = fa.object;
if (fa.object.data == .deref_expr) {
effective_obj_node = fa.object.data.deref_expr.operand;
obj_ty = self.inferExprType(effective_obj_node);
obj = self.lowerExpr(effective_obj_node);
} else {
obj_ty = self.inferExprType(fa.object);
obj = self.lowerExpr(fa.object);
}
// Check if field is a closure type — call as closure, not method // Check if field is a closure type — call as closure, not method
if (!obj_ty.isBuiltin()) { if (!obj_ty.isBuiltin()) {
@@ -3781,7 +3923,7 @@ pub const Lowering = struct {
const func = &self.module.functions.items[@intFromEnum(fid)]; const func = &self.module.functions.items[@intFromEnum(fid)];
const ret_ty = func.ret; const ret_ty = func.ret;
const params = func.params; const params = func.params;
self.fixupMethodReceiver(&method_args, func, fa.object, obj_ty); self.fixupMethodReceiver(&method_args, func, effective_obj_node, obj_ty);
self.coerceCallArgs(method_args.items, params); self.coerceCallArgs(method_args.items, params);
return self.builder.call(fid, method_args.items, ret_ty); return self.builder.call(fid, method_args.items, ret_ty);
} }
@@ -3800,7 +3942,7 @@ pub const Lowering = struct {
const func = &self.module.functions.items[@intFromEnum(fid)]; const func = &self.module.functions.items[@intFromEnum(fid)];
const ret_ty = func.ret; const ret_ty = func.ret;
const params = func.params; const params = func.params;
self.fixupMethodReceiver(&method_args, func, fa.object, obj_ty); self.fixupMethodReceiver(&method_args, func, effective_obj_node, obj_ty);
// Note: coerceCallArgs can trigger protocol thunk creation // Note: coerceCallArgs can trigger protocol thunk creation
// (module.addFunction), invalidating func pointer. // (module.addFunction), invalidating func pointer.
// Use pre-extracted params/ret_ty instead of func.* after this. // Use pre-extracted params/ret_ty instead of func.* after this.
@@ -4005,6 +4147,14 @@ pub const Lowering = struct {
if (!tt.isBuiltin()) { if (!tt.isBuiltin()) {
const tti = self.module.types.get(tt); const tti = self.module.types.get(tt);
if (tti == .closure) break :blk tti.closure.params; if (tti == .closure) break :blk tti.closure.params;
// Unwrap ?Closure(...) → Closure(...)
if (tti == .optional) {
const inner = tti.optional.child;
if (!inner.isBuiltin()) {
const inner_info = self.module.types.get(inner);
if (inner_info == .closure) break :blk inner_info.closure.params;
}
}
} }
break :blk null; break :blk null;
} else null; } else null;
@@ -4031,6 +4181,14 @@ pub const Lowering = struct {
if (!tt.isBuiltin()) { if (!tt.isBuiltin()) {
const tti = self.module.types.get(tt); const tti = self.module.types.get(tt);
if (tti == .closure) break :blk tti.closure.ret; if (tti == .closure) break :blk tti.closure.ret;
// Unwrap ?Closure(...) → Closure(...)
if (tti == .optional) {
const inner = tti.optional.child;
if (!inner.isBuiltin()) {
const inner_info = self.module.types.get(inner);
if (inner_info == .closure) break :blk inner_info.closure.ret;
}
}
} }
} }
// Arrow lambda without explicit return type — infer from body expression // Arrow lambda without explicit return type — infer from body expression
@@ -4388,8 +4546,7 @@ pub const Lowering = struct {
offset = (offset + field_align - 1) & ~(field_align - 1); offset = (offset + field_align - 1) & ~(field_align - 1);
offset += field_size; offset += field_size;
} }
// Align total to struct alignment (max field alignment, minimum 8) // Align total to max field alignment (matches LLVM's struct alignment)
if (max_align < 8) max_align = 8;
return (offset + max_align - 1) & ~(max_align - 1); return (offset + max_align - 1) & ~(max_align - 1);
} }
@@ -5716,6 +5873,8 @@ pub const Lowering = struct {
if (ty == .void) return "void"; if (ty == .void) return "void";
if (ty == .string) return "string"; if (ty == .string) return "string";
if (ty == .any) return "Any"; if (ty == .any) return "Any";
if (ty == .usize) return "usize";
if (ty == .isize) return "isize";
const info = self.module.types.get(ty); const info = self.module.types.get(ty);
return switch (info) { return switch (info) {
@@ -5880,6 +6039,8 @@ pub const Lowering = struct {
if (ty == .void) return "void"; if (ty == .void) return "void";
if (ty == .string) return "string"; if (ty == .string) return "string";
if (ty == .any) return "Any"; if (ty == .any) return "Any";
if (ty == .usize) return "usize";
if (ty == .isize) return "isize";
const info = self.module.types.get(ty); const info = self.module.types.get(ty);
return switch (info) { return switch (info) {
@@ -5932,6 +6093,8 @@ pub const Lowering = struct {
tags.append(self.alloc, TypeId.u16.index()) catch {}; tags.append(self.alloc, TypeId.u16.index()) catch {};
tags.append(self.alloc, TypeId.u32.index()) catch {}; tags.append(self.alloc, TypeId.u32.index()) catch {};
tags.append(self.alloc, TypeId.u64.index()) catch {}; tags.append(self.alloc, TypeId.u64.index()) catch {};
tags.append(self.alloc, TypeId.usize.index()) catch {};
tags.append(self.alloc, TypeId.isize.index()) catch {};
return tags.items; return tags.items;
} }
if (std.mem.eql(u8, name, "float")) { if (std.mem.eql(u8, name, "float")) {
@@ -7220,6 +7383,7 @@ pub const Lowering = struct {
// Build call args: ctx + user args // Build call args: ctx + user args
// Protocol method params use *void for Self-typed params. If the caller passes // Protocol method params use *void for Self-typed params. If the caller passes
// a struct value, we need to alloca+store and pass the pointer instead. // a struct value, we need to alloca+store and pass the pointer instead.
// Also coerce argument types to match declared param types (e.g., s64 → s32).
var call_args = std.ArrayList(Ref).empty; var call_args = std.ArrayList(Ref).empty;
defer call_args.deinit(self.alloc); defer call_args.deinit(self.alloc);
call_args.append(self.alloc, ctx) catch unreachable; call_args.append(self.alloc, ctx) catch unreachable;
@@ -7233,7 +7397,9 @@ pub const Lowering = struct {
self.builder.store(slot, a); self.builder.store(slot, a);
call_args.append(self.alloc, slot) catch unreachable; call_args.append(self.alloc, slot) catch unreachable;
} else { } else {
call_args.append(self.alloc, a) catch unreachable; // Coerce to match declared parameter type (critical for WASM strict signatures)
const coerced = self.coerceToType(a, arg_ty, expected_ty);
call_args.append(self.alloc, coerced) catch unreachable;
} }
} }
const owned = self.alloc.dupe(Ref, call_args.items) catch unreachable; const owned = self.alloc.dupe(Ref, call_args.items) catch unreachable;
@@ -8010,7 +8176,7 @@ pub const Lowering = struct {
fn isInt(ty: TypeId) bool { fn isInt(ty: TypeId) bool {
return switch (ty) { return switch (ty) {
.s8, .s16, .s32, .s64, .u8, .u16, .u32, .u64 => true, .s8, .s16, .s32, .s64, .u8, .u16, .u32, .u64, .usize, .isize => true,
else => false, else => false,
}; };
} }
@@ -8034,6 +8200,7 @@ pub const Lowering = struct {
.s16, .u16 => 16, .s16, .u16 => 16,
.s32, .u32 => 32, .s32, .u32 => 32,
.s64, .u64 => 64, .s64, .u64 => 64,
.usize, .isize => 0, // target-dependent — use typeBitsEx
.f32 => 32, .f32 => 32,
.f64 => 64, .f64 => 64,
else => 0, else => 0,
@@ -8041,6 +8208,7 @@ pub const Lowering = struct {
} }
fn typeBitsEx(self: *Lowering, ty: TypeId) u32 { fn typeBitsEx(self: *Lowering, ty: TypeId) u32 {
if (ty == .usize or ty == .isize) return @as(u32, self.module.types.pointer_size) * 8;
const b = typeBits(ty); const b = typeBits(ty);
if (b > 0) return b; if (b > 0) return b;
if (!ty.isBuiltin()) { if (!ty.isBuiltin()) {

View File

@@ -65,6 +65,8 @@ pub fn bridgeType(ty: sx_types.Type, table: *TypeTable) TypeId {
.boolean => .bool, .boolean => .bool,
.string_type => .string, .string_type => .string,
.any_type => .any, .any_type => .any,
.usize_type => .usize,
.isize_type => .isize,
.enum_type => |name| resolveNamedType(name, .@"enum", table), .enum_type => |name| resolveNamedType(name, .@"enum", table),
.struct_type => |name| resolveNamedType(name, .@"struct", table), .struct_type => |name| resolveNamedType(name, .@"struct", table),
.union_type => |name| resolveNamedType(name, .@"union", table), .union_type => |name| resolveNamedType(name, .@"union", table),
@@ -222,6 +224,8 @@ pub fn resolveTypePrimitive(name: []const u8) ?TypeId {
if (std.mem.eql(u8, name, "Any")) return .any; if (std.mem.eql(u8, name, "Any")) return .any;
if (std.mem.eql(u8, name, "Type")) return .any; // Type-as-value: boxed string if (std.mem.eql(u8, name, "Type")) return .any; // Type-as-value: boxed string
if (std.mem.eql(u8, name, "noreturn")) return .noreturn; if (std.mem.eql(u8, name, "noreturn")) return .noreturn;
if (std.mem.eql(u8, name, "usize")) return .usize;
if (std.mem.eql(u8, name, "isize")) return .isize;
return null; return null;
} }

View File

@@ -21,10 +21,11 @@ pub const TypeId = enum(u32) {
string = 12, // [:0]u8 string = 12, // [:0]u8
any = 13, any = 13,
noreturn = 14, noreturn = 14,
_reserved = 15, isize = 15,
_, // user-defined types start at 16 usize = 16,
_, // user-defined types start at 17
pub const first_user: u32 = 16; pub const first_user: u32 = 17;
pub fn index(self: TypeId) u32 { pub fn index(self: TypeId) u32 {
return @intFromEnum(self); return @intFromEnum(self);
@@ -69,6 +70,8 @@ pub const TypeInfo = union(enum) {
any, any,
protocol: ProtocolInfo, protocol: ProtocolInfo,
noreturn, noreturn,
usize,
isize,
pub const StructInfo = struct { pub const StructInfo = struct {
name: StringId, name: StringId,
@@ -225,6 +228,8 @@ pub const TypeTable = struct {
/// Maps TypeInfo → TypeId for dedup of structural types /// Maps TypeInfo → TypeId for dedup of structural types
intern_map: std.HashMap(TypeKey, TypeId, TypeKeyContext, 80), intern_map: std.HashMap(TypeKey, TypeId, TypeKeyContext, 80),
alloc: Allocator, alloc: Allocator,
/// Target pointer size in bytes (4 for wasm32, 8 for 64-bit targets).
pointer_size: u8 = 8,
pub fn init(alloc: Allocator) TypeTable { pub fn init(alloc: Allocator) TypeTable {
var table = TypeTable{ var table = TypeTable{
@@ -251,7 +256,8 @@ pub const TypeTable = struct {
.string, // 12 .string, // 12
.any, // 13 .any, // 13
.noreturn, // 14 .noreturn, // 14
.void, // 15: reserved (placeholder) .isize, // 15: isize (pointer-sized signed)
.usize, // 16: usize (pointer-sized unsigned)
}; };
for (&builtins) |info| { for (&builtins) |info| {
table.infos.append(alloc, info) catch unreachable; table.infos.append(alloc, info) catch unreachable;
@@ -402,25 +408,27 @@ pub const TypeTable = struct {
/// This is the authoritative size computation used for closure env sizing and /// This is the authoritative size computation used for closure env sizing and
/// verified against LLVMABISizeOfType. /// verified against LLVMABISizeOfType.
pub fn typeSizeBytes(self: *const TypeTable, ty: TypeId) usize { pub fn typeSizeBytes(self: *const TypeTable, ty: TypeId) usize {
const ptr_size: usize = self.pointer_size;
if (ty == .void) return 0; if (ty == .void) return 0;
if (ty == .bool) return 1; if (ty == .bool) return 1;
if (ty == .u8 or ty == .s8) return 1; if (ty == .u8 or ty == .s8) return 1;
if (ty == .u16 or ty == .s16) return 2; if (ty == .u16 or ty == .s16) return 2;
if (ty == .s32 or ty == .u32 or ty == .f32) return 4; if (ty == .s32 or ty == .u32 or ty == .f32) return 4;
if (ty == .s64 or ty == .u64 or ty == .f64) return 8; if (ty == .s64 or ty == .u64 or ty == .f64) return 8;
if (ty == .string) return 16; // {ptr, i64} if (ty == .usize or ty == .isize) return ptr_size;
if (ty.isBuiltin()) return 8; // default for unknown builtins if (ty == .string) return 16; // {ptr, i64} — always 16 (i64 alignment pads on wasm32)
if (ty.isBuiltin()) return ptr_size; // default for unknown builtins
const info = self.get(ty); const info = self.get(ty);
return switch (info) { return switch (info) {
.pointer, .many_pointer, .function => 8, .pointer, .many_pointer, .function => ptr_size,
.slice => 16, // {ptr, i64} .slice => 16, // {ptr, i64} — same layout as string
.closure => 16, // {fn_ptr, env_ptr} .closure => 2 * ptr_size, // {fn_ptr, env_ptr}
.optional => |o| blk: { .optional => |o| blk: {
const child_info = self.get(o.child); const child_info = self.get(o.child);
if (child_info == .pointer or child_info == .many_pointer or child_info == .function) if (child_info == .pointer or child_info == .many_pointer or child_info == .function)
break :blk 8; break :blk ptr_size;
if (child_info == .closure) if (child_info == .closure)
break :blk 16; // {fn_ptr, env_ptr} break :blk 2 * ptr_size;
const cs = self.typeSizeBytes(o.child); const cs = self.typeSizeBytes(o.child);
const ca = self.typeAlignBytes(o.child); const ca = self.typeAlignBytes(o.child);
// { T, i1 } — i1 goes right after T, then pad to struct alignment // { T, i1 } — i1 goes right after T, then pad to struct alignment
@@ -480,8 +488,8 @@ pub const TypeTable = struct {
} }
break :blk if (offset == 0) 0 else (offset + max_a - 1) & ~(max_a - 1); break :blk if (offset == 0) 0 else (offset + max_a - 1) & ~(max_a - 1);
}, },
.any => 16, .any => 2 * ptr_size, // {type_tag, data_ptr}
.protocol => 16, .protocol => 2 * ptr_size, // {ctx, vtable}
.@"enum" => |e| { .@"enum" => |e| {
if (e.backing_type) |bt| return self.typeSizeBytes(bt); if (e.backing_type) |bt| return self.typeSizeBytes(bt);
return 8; return 8;
@@ -492,22 +500,25 @@ pub const TypeTable = struct {
/// Compute the ABI alignment in bytes for a type, matching LLVM's rules. /// Compute the ABI alignment in bytes for a type, matching LLVM's rules.
pub fn typeAlignBytes(self: *const TypeTable, ty: TypeId) usize { pub fn typeAlignBytes(self: *const TypeTable, ty: TypeId) usize {
const ptr_align: usize = self.pointer_size;
if (ty == .void) return 1; if (ty == .void) return 1;
if (ty == .bool) return 1; if (ty == .bool) return 1;
if (ty == .u8 or ty == .s8) return 1; if (ty == .u8 or ty == .s8) return 1;
if (ty == .u16 or ty == .s16) return 2; if (ty == .u16 or ty == .s16) return 2;
if (ty == .s32 or ty == .u32 or ty == .f32) return 4; if (ty == .s32 or ty == .u32 or ty == .f32) return 4;
if (ty == .s64 or ty == .u64 or ty == .f64) return 8; if (ty == .s64 or ty == .u64 or ty == .f64) return 8;
if (ty == .string) return 8; if (ty == .usize or ty == .isize) return ptr_align;
if (ty.isBuiltin()) return 8; if (ty == .string) return 8; // i64 drives alignment
if (ty.isBuiltin()) return ptr_align;
const info = self.get(ty); const info = self.get(ty);
return switch (info) { return switch (info) {
.pointer, .many_pointer, .function => 8, .pointer, .many_pointer, .function => ptr_align,
.slice, .closure => 8, .slice => 8, // i64 drives alignment
.closure => ptr_align, // {ptr, ptr}
.optional => |o| blk: { .optional => |o| blk: {
const child_info = self.get(o.child); const child_info = self.get(o.child);
if (child_info == .pointer or child_info == .many_pointer or child_info == .function or child_info == .closure) if (child_info == .pointer or child_info == .many_pointer or child_info == .function or child_info == .closure)
break :blk 8; break :blk ptr_align;
break :blk self.typeAlignBytes(o.child); break :blk self.typeAlignBytes(o.child);
}, },
.@"struct" => |s| blk: { .@"struct" => |s| blk: {
@@ -566,6 +577,8 @@ pub const TypeTable = struct {
.string => "string", .string => "string",
.any => "Any", .any => "Any",
.noreturn => "noreturn", .noreturn => "noreturn",
.isize => "isize",
.usize => "usize",
else => { else => {
// User types — format from TypeInfo // User types — format from TypeInfo
const info = self.get(id); const info = self.get(id);
@@ -609,7 +622,7 @@ fn hashTypeInfo(h: *std.hash.Wyhash, info: TypeInfo) void {
switch (info) { switch (info) {
.signed => |w| h.update(&.{w}), .signed => |w| h.update(&.{w}),
.unsigned => |w| h.update(&.{w}), .unsigned => |w| h.update(&.{w}),
.f32, .f64, .void, .bool, .string, .any, .noreturn => {}, .f32, .f64, .void, .bool, .string, .any, .noreturn, .usize, .isize => {},
.pointer => |p| h.update(std.mem.asBytes(&p.pointee)), .pointer => |p| h.update(std.mem.asBytes(&p.pointee)),
.many_pointer => |p| h.update(std.mem.asBytes(&p.element)), .many_pointer => |p| h.update(std.mem.asBytes(&p.element)),
.slice => |s| h.update(std.mem.asBytes(&s.element)), .slice => |s| h.update(std.mem.asBytes(&s.element)),
@@ -650,7 +663,7 @@ fn typeInfoEql(a: TypeInfo, b: TypeInfo) bool {
return switch (a) { return switch (a) {
.signed => |w| w == b.signed, .signed => |w| w == b.signed,
.unsigned => |w| w == b.unsigned, .unsigned => |w| w == b.unsigned,
.f32, .f64, .void, .bool, .string, .any, .noreturn => true, .f32, .f64, .void, .bool, .string, .any, .noreturn, .usize, .isize => true,
.pointer => |p| p.pointee == b.pointer.pointee, .pointer => |p| p.pointee == b.pointer.pointee,
.many_pointer => |p| p.element == b.many_pointer.element, .many_pointer => |p| p.element == b.many_pointer.element,
.slice => |s| s.element == b.slice.element, .slice => |s| s.element == b.slice.element,

View File

@@ -1477,6 +1477,7 @@ pub const Server = struct {
.kw_closure, .kw_closure,
.kw_protocol, .kw_protocol,
.kw_impl, .kw_impl,
.kw_inline,
.hash_run, .hash_run,
.hash_import, .hash_import,
.hash_insert, .hash_insert,

View File

@@ -223,6 +223,11 @@ fn compileCForBuild(allocator: std.mem.Allocator, io: std.Io, comp: *sx.core.Com
const c_infos = try comp.collectCImportSources(); const c_infos = try comp.collectCImportSources();
if (c_infos.len == 0) return &.{}; if (c_infos.len == 0) return &.{};
// For Emscripten targets, use emcc to cross-compile C sources
if (comp.target_config.isEmscripten()) {
return try sx.c_import.compileCWithEmcc(allocator, io, c_infos);
}
const obj_bufs = try sx.c_import.compileCToObjects(allocator, c_infos); const obj_bufs = try sx.c_import.compileCToObjects(allocator, c_infos);
return try sx.c_import.writeCObjectFiles(allocator, io, obj_bufs); return try sx.c_import.writeCObjectFiles(allocator, io, obj_bufs);
} }

View File

@@ -82,6 +82,18 @@ pub const Parser = struct {
return self.parseImplBlock(start); return self.parseImplBlock(start);
} }
// Top-level `inline if` — compile-time conditional
if (self.current.tag == .kw_inline) {
if (self.peekNext() == .kw_if) {
self.advance(); // skip 'inline'
const expr = try self.parseIfExpr();
if (expr.data == .if_expr) {
expr.data.if_expr.is_comptime = true;
}
return expr;
}
}
// All top-level declarations start with an identifier // All top-level declarations start with an identifier
if (!self.isIdentLike() and self.current.tag != .kw_Self) { if (!self.isIdentLike() and self.current.tag != .kw_Self) {
return self.fail("expected identifier at top level"); return self.fail("expected identifier at top level");
@@ -1375,6 +1387,19 @@ pub const Parser = struct {
return try self.createNode(start, .{ .insert_expr = .{ .expr = inner } }); return try self.createNode(start, .{ .insert_expr = .{ .expr = inner } });
} }
// inline if — compile-time conditional
if (self.current.tag == .kw_inline) {
if (self.peekNext() == .kw_if) {
self.advance(); // skip 'inline'
const expr = try self.parseIfExpr();
if (expr.data == .if_expr) {
expr.data.if_expr.is_comptime = true;
}
try self.expectSemicolonAfter(expr);
return expr;
}
}
// Block-form if/while/for as statements — parse directly to prevent // Block-form if/while/for as statements — parse directly to prevent
// postfix chaining (e.g. `if cond { ... }.field` being misparsed) // postfix chaining (e.g. `if cond { ... }.field` being misparsed)
if (self.current.tag == .kw_if) { if (self.current.tag == .kw_if) {

View File

@@ -58,6 +58,16 @@ pub const TargetConfig = struct {
return self.tripleHasPrefix("wasm32", "wasm64"); return self.tripleHasPrefix("wasm32", "wasm64");
} }
/// Check if target triple indicates macOS/Darwin.
pub fn isMacOS(self: TargetConfig) bool {
return self.tripleContains("darwin") or self.tripleContains("macos");
}
/// Check if target triple indicates Linux.
pub fn isLinux(self: TargetConfig) bool {
return self.tripleContains("linux");
}
/// Check if target triple indicates Emscripten (contains "emscripten"). /// Check if target triple indicates Emscripten (contains "emscripten").
pub fn isEmscripten(self: TargetConfig) bool { pub fn isEmscripten(self: TargetConfig) bool {
return self.tripleContains("emscripten"); return self.tripleContains("emscripten");
@@ -164,11 +174,10 @@ pub fn link(allocator: std.mem.Allocator, io: std.Io, output_obj: []const u8, ex
for (target_config.lib_paths) |lp| { for (target_config.lib_paths) |lp| {
try argv.append(allocator, try std.fmt.allocPrint(allocator, "-L{s}", .{lp})); try argv.append(allocator, try std.fmt.allocPrint(allocator, "-L{s}", .{lp}));
} }
for (libraries) |lib| { // Skip -l flags for Emscripten: libraries like SDL3 are provided via
try argv.append(allocator, try std.fmt.allocPrint(allocator, "-l{s}", .{lib})); // -sUSE_SDL=3, not -lSDL3. User provides everything via --lflags.
}
// Extra linker flags (e.g. -sUSE_SDL=2, -sUSE_WEBGL2=1, --preload-file) // Extra linker flags (e.g. -sUSE_SDL=3, -sUSE_WEBGL2=1, --preload-file)
for (target_config.extra_link_flags) |flag| { for (target_config.extra_link_flags) |flag| {
try argv.append(allocator, flag); try argv.append(allocator, flag);
} }

View File

@@ -36,6 +36,7 @@ pub const Tag = enum {
kw_protocol, // protocol kw_protocol, // protocol
kw_impl, // impl kw_impl, // impl
kw_Self, // Self (in protocol declarations) kw_Self, // Self (in protocol declarations)
kw_inline, // inline (compile-time if/for/while)
// Symbols // Symbols
colon, // : colon, // :
@@ -224,6 +225,7 @@ pub const keywords = std.StaticStringMap(Tag).initComptime(.{
.{ "protocol", .kw_protocol }, .{ "protocol", .kw_protocol },
.{ "impl", .kw_impl }, .{ "impl", .kw_impl },
.{ "Self", .kw_Self }, .{ "Self", .kw_Self },
.{ "inline", .kw_inline },
}); });
pub fn getKeyword(bytes: []const u8) ?Tag { pub fn getKeyword(bytes: []const u8) ?Tag {

View File

@@ -24,6 +24,8 @@ pub const Type = union(enum) {
function_type: FunctionTypeInfo, function_type: FunctionTypeInfo,
closure_type: ClosureTypeInfo, closure_type: ClosureTypeInfo,
any_type, any_type,
usize_type,
isize_type,
optional_type: OptionalTypeInfo, optional_type: OptionalTypeInfo,
meta_type: MetaTypeInfo, meta_type: MetaTypeInfo,
tuple_type: TupleTypeInfo, tuple_type: TupleTypeInfo,
@@ -82,7 +84,7 @@ pub const Type = union(enum) {
return switch (self) { return switch (self) {
.signed => |w| w == other.signed, .signed => |w| w == other.signed,
.unsigned => |w| w == other.unsigned, .unsigned => |w| w == other.unsigned,
.f32, .f64, .void_type, .boolean, .string_type, .any_type => true, .f32, .f64, .void_type, .boolean, .string_type, .any_type, .usize_type, .isize_type => true,
.enum_type => |n| std.mem.eql(u8, n, other.enum_type), .enum_type => |n| std.mem.eql(u8, n, other.enum_type),
.struct_type => |n| std.mem.eql(u8, n, other.struct_type), .struct_type => |n| std.mem.eql(u8, n, other.struct_type),
.union_type => |n| std.mem.eql(u8, n, other.union_type), .union_type => |n| std.mem.eql(u8, n, other.union_type),
@@ -149,12 +151,17 @@ pub const Type = union(enum) {
return null; return null;
}, },
'u' => { 'u' => {
if (std.mem.eql(u8, name, "usize")) return .usize_type;
if (name.len >= 2) { if (name.len >= 2) {
const width = std.fmt.parseInt(u8, name[1..], 10) catch return null; const width = std.fmt.parseInt(u8, name[1..], 10) catch return null;
if (width >= 1 and width <= 64) return Type.u(width); if (width >= 1 and width <= 64) return Type.u(width);
} }
return null; return null;
}, },
'i' => {
if (std.mem.eql(u8, name, "isize")) return .isize_type;
return null;
},
'b' => if (std.mem.eql(u8, name, "bool")) .boolean else null, 'b' => if (std.mem.eql(u8, name, "bool")) .boolean else null,
'f' => { 'f' => {
if (std.mem.eql(u8, name, "f32")) return .f32; if (std.mem.eql(u8, name, "f32")) return .f32;
@@ -223,6 +230,8 @@ pub const Type = union(enum) {
.boolean => "bool", .boolean => "bool",
.string_type => "string", .string_type => "string",
.void_type => "void", .void_type => "void",
.usize_type => "usize",
.isize_type => "isize",
.struct_type => |n| n, .struct_type => |n| n,
.enum_type => |n| n, .enum_type => |n| n,
.union_type => |n| n, .union_type => |n| n,
@@ -544,6 +553,8 @@ pub const Type = union(enum) {
.string_type => "string", .string_type => "string",
.void_type => "void", .void_type => "void",
.any_type => "Any", .any_type => "Any",
.usize_type => "usize",
.isize_type => "isize",
.enum_type => |name| name, .enum_type => |name| name,
.struct_type => |name| name, .struct_type => |name| name,
.union_type => |name| name, .union_type => |name| name,

View File

@@ -592,4 +592,14 @@ opt-if2: 10.000000
opt-if3: 10.000000 opt-if3: 10.000000
opt-if4: 0.000000 opt-if4: 0.000000
opt-if5: 42.000000 opt-if5: 42.000000
usize: 42
isize: -7
usize+8: 50
s32->usize: 10
usize->s64: 42
=== inline if ===
64-bit
not wasm
known os
desktop 64-bit
=== DONE === === DONE ===