http server
This commit is contained in:
@@ -15,15 +15,9 @@ main :: () -> s32 {
|
|||||||
opt : s32 = 1;
|
opt : s32 = 1;
|
||||||
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, @opt, 4);
|
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, @opt, 4);
|
||||||
|
|
||||||
addr := SockAddr.{
|
addr := SockAddr.{ sin_len = 16, sin_family = 2, sin_port = htons(PORT) };
|
||||||
sin_len = 16,
|
|
||||||
sin_family = 2,
|
|
||||||
sin_port = htons(PORT),
|
|
||||||
sin_addr = 0,
|
|
||||||
sin_zero = 0
|
|
||||||
};
|
|
||||||
|
|
||||||
if bind(fd, @addr, 16) < 0 {
|
if bind(fd, addr, 16) < 0 {
|
||||||
print("error: bind()\n");
|
print("error: bind()\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -40,8 +34,8 @@ main :: () -> s32 {
|
|||||||
if client < 0 { continue; }
|
if client < 0 { continue; }
|
||||||
|
|
||||||
// Read request
|
// Read request
|
||||||
buf := alloc(4096);
|
buf : [4096]u8 = ---;
|
||||||
read(client, buf, 4096);
|
read(client, buf, buf.len);
|
||||||
|
|
||||||
// Send response
|
// Send response
|
||||||
body := "<html><body><h1>Hello from sx!</h1></body></html>";
|
body := "<html><body><h1>Hello from sx!</h1></body></html>";
|
||||||
|
|||||||
@@ -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: [:0]u8, count: s64) -> s64 #foreign libc;
|
read :: (fd: s32, buf: [*]u8, count: s64) -> s64 #foreign libc;
|
||||||
write :: (fd: s32, buf: [:0]u8, count: s64) -> s64 #foreign libc;
|
write :: (fd: s32, buf: [*]u8, count: s64) -> s64 #foreign libc;
|
||||||
close :: (fd: s32) -> s32 #foreign libc;
|
close :: (fd: s32) -> s32 #foreign libc;
|
||||||
|
|
||||||
// Constants (macOS)
|
// Constants (macOS)
|
||||||
@@ -24,8 +24,8 @@ SockAddr :: struct {
|
|||||||
sin_len: u8;
|
sin_len: u8;
|
||||||
sin_family: u8;
|
sin_family: u8;
|
||||||
sin_port: u16;
|
sin_port: u16;
|
||||||
sin_addr: u32;
|
sin_addr: u32 = 0;
|
||||||
sin_zero: u64;
|
sin_zero: u64 = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
htons :: (port: s64) -> u16 {
|
htons :: (port: s64) -> u16 {
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ sqrt :: (x: $T) -> T #builtin;
|
|||||||
sin :: (x: $T) -> T #builtin;
|
sin :: (x: $T) -> T #builtin;
|
||||||
cos :: (x: $T) -> T #builtin;
|
cos :: (x: $T) -> T #builtin;
|
||||||
size_of :: ($T: Type) -> s64 #builtin;
|
size_of :: ($T: Type) -> s64 #builtin;
|
||||||
alloc :: (size: s64) -> string #builtin;
|
|
||||||
malloc :: (size: s64) -> *void #builtin;
|
malloc :: (size: s64) -> *void #builtin;
|
||||||
memcpy :: (dst: *void, src: *void, size: s64) -> *void #builtin;
|
memcpy :: (dst: *void, src: *void, size: s64) -> *void #builtin;
|
||||||
|
memset :: (dst: *void, val: s64, size: s64) -> void #builtin;
|
||||||
free :: (ptr: *void) -> void #builtin;
|
free :: (ptr: *void) -> void #builtin;
|
||||||
type_of :: (val: $T) -> Type #builtin;
|
type_of :: (val: $T) -> Type #builtin;
|
||||||
type_name :: ($T: Type) -> string #builtin;
|
type_name :: ($T: Type) -> string #builtin;
|
||||||
@@ -18,6 +18,20 @@ field_value_int :: ($T: Type, idx: s64) -> s64 #builtin;
|
|||||||
field_index :: ($T: Type, val: T) -> s64 #builtin;
|
field_index :: ($T: Type, val: T) -> s64 #builtin;
|
||||||
string :: []u8 #builtin;
|
string :: []u8 #builtin;
|
||||||
|
|
||||||
|
CString :: union {
|
||||||
|
s: string;
|
||||||
|
struct { ptr: *void; len: s64; };
|
||||||
|
}
|
||||||
|
|
||||||
|
cstring :: (size: s64) -> string {
|
||||||
|
raw := malloc(size + 1);
|
||||||
|
memset(raw, 0, size + 1);
|
||||||
|
rs : CString = ---;
|
||||||
|
rs.ptr = raw;
|
||||||
|
rs.len = size;
|
||||||
|
rs.s;
|
||||||
|
}
|
||||||
|
|
||||||
int_to_string :: (n: s64) -> string {
|
int_to_string :: (n: s64) -> string {
|
||||||
if n == 0 { return "0"; }
|
if n == 0 { return "0"; }
|
||||||
neg := n < 0;
|
neg := n < 0;
|
||||||
@@ -26,7 +40,7 @@ int_to_string :: (n: s64) -> string {
|
|||||||
len := 0;
|
len := 0;
|
||||||
while tmp > 0 { len += 1; tmp = tmp / 10; }
|
while tmp > 0 { len += 1; tmp = tmp / 10; }
|
||||||
total := if neg then len + 1 else len;
|
total := if neg then len + 1 else len;
|
||||||
buf := alloc(total);
|
buf := cstring(total);
|
||||||
i := total - 1;
|
i := total - 1;
|
||||||
while v > 0 {
|
while v > 0 {
|
||||||
buf[i] = (v % 10) + 48;
|
buf[i] = (v % 10) + 48;
|
||||||
@@ -53,18 +67,17 @@ float_to_string :: (f: f64) -> string {
|
|||||||
fl := fstr.len;
|
fl := fstr.len;
|
||||||
prefix := if neg then 1 else 0;
|
prefix := if neg then 1 else 0;
|
||||||
total := prefix + il + 1 + 6;
|
total := prefix + il + 1 + 6;
|
||||||
buf := alloc(total);
|
buf := cstring(total);
|
||||||
pos := 0;
|
pos := 0;
|
||||||
if neg { buf[0] = 45; pos = 1; }
|
if neg { buf[0] = 45; pos = 1; }
|
||||||
i := 0;
|
memcpy(@buf[pos], istr.ptr, il);
|
||||||
while i < il { buf[pos] = istr[i]; pos += 1; i += 1; }
|
pos = pos + il;
|
||||||
buf[pos] = 46;
|
buf[pos] = 46;
|
||||||
pos += 1;
|
pos += 1;
|
||||||
pad := 6 - fl;
|
pad := 6 - fl;
|
||||||
j := 0;
|
memset(@buf[pos], 48, pad);
|
||||||
while j < pad { buf[pos] = 48; pos += 1; j += 1; }
|
pos = pos + pad;
|
||||||
k := 0;
|
memcpy(@buf[pos], fstr.ptr, fl);
|
||||||
while k < fl { buf[pos] = fstr[k]; pos += 1; k += 1; }
|
|
||||||
buf;
|
buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +97,7 @@ int_to_hex_string :: (n: s64) -> string {
|
|||||||
g3 := r3 % 65536;
|
g3 := r3 % 65536;
|
||||||
if g3 < 0 { g3 = g3 + 65536; }
|
if g3 < 0 { g3 = g3 + 65536; }
|
||||||
|
|
||||||
buf := alloc(16);
|
buf := cstring(16);
|
||||||
// Group 3: digits 0-3 (bits 48-63)
|
// Group 3: digits 0-3 (bits 48-63)
|
||||||
i := 3;
|
i := 3;
|
||||||
v := g3;
|
v := g3;
|
||||||
@@ -133,21 +146,15 @@ int_to_hex_string :: (n: s64) -> string {
|
|||||||
concat :: (a: string, b: string) -> string {
|
concat :: (a: string, b: string) -> string {
|
||||||
al := a.len;
|
al := a.len;
|
||||||
bl := b.len;
|
bl := b.len;
|
||||||
buf := alloc(al + bl);
|
buf := cstring(al + bl);
|
||||||
i := 0;
|
memcpy(buf.ptr, a.ptr, al);
|
||||||
while i < al { buf[i] = a[i]; i += 1; }
|
memcpy(@buf[al], b.ptr, bl);
|
||||||
j := 0;
|
|
||||||
while j < bl { buf[al + j] = b[j]; j += 1; }
|
|
||||||
buf;
|
buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
substr :: (s: string, start: s64, len: s64) -> string {
|
substr :: (s: string, start: s64, len: s64) -> string {
|
||||||
buf := alloc(len);
|
buf := cstring(len);
|
||||||
i := 0;
|
memcpy(buf.ptr, @s[start], len);
|
||||||
while i < len {
|
|
||||||
buf[i] = s[start + i];
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
buf;
|
buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
13
specs.md
13
specs.md
@@ -932,9 +932,14 @@ Built-in functions are declared in `std.sx` with the `#builtin` suffix, which te
|
|||||||
|
|
||||||
### Math
|
### Math
|
||||||
- `sqrt(x: $T) -> T` — square root (maps to LLVM intrinsic)
|
- `sqrt(x: $T) -> T` — square root (maps to LLVM intrinsic)
|
||||||
|
- `sin(x: $T) -> T` — sine (maps to LLVM intrinsic)
|
||||||
|
- `cos(x: $T) -> T` — cosine (maps to LLVM intrinsic)
|
||||||
|
|
||||||
### Memory
|
### Memory
|
||||||
- `alloc(size: s64) -> string` — allocate `size` bytes of memory, returned as a string slice
|
- `malloc(size: s64) -> *void` — allocate `size` bytes of heap memory
|
||||||
|
- `free(ptr: *void) -> void` — free previously allocated memory
|
||||||
|
- `memcpy(dst: *void, src: *void, size: s64) -> *void` — copy `size` bytes from `src` to `dst`
|
||||||
|
- `memset(dst: *void, val: s64, size: s64) -> void` — fill `size` bytes at `dst` with `val`
|
||||||
- `size_of($T: Type) -> s64` — size of type `T` in bytes
|
- `size_of($T: Type) -> s64` — size of type `T` in bytes
|
||||||
|
|
||||||
### Type Introspection
|
### Type Introspection
|
||||||
@@ -943,7 +948,9 @@ Built-in functions are declared in `std.sx` with the `#builtin` suffix, which te
|
|||||||
- `field_count($T: Type) -> s64` — returns the number of fields (struct), variants (enum), or elements (vector) in type `T`
|
- `field_count($T: Type) -> s64` — returns the number of fields (struct), variants (enum), or elements (vector) in type `T`
|
||||||
- `field_name($T: Type, idx: s64) -> string` — returns the name of the `idx`-th field (struct) or variant (enum) of type `T`
|
- `field_name($T: Type, idx: s64) -> string` — returns the name of the `idx`-th field (struct) or variant (enum) of type `T`
|
||||||
- `field_value(s: $T, idx: s64) -> Any` — returns the `idx`-th field (struct) or element (vector) of `s`, boxed as `Any`
|
- `field_value(s: $T, idx: s64) -> Any` — returns the `idx`-th field (struct) or element (vector) of `s`, boxed as `Any`
|
||||||
|
- `field_value_int($T: Type, idx: s64) -> s64` — returns the integer value of the `idx`-th enum variant
|
||||||
- `field_index($T: Type, val: T) -> s64` — returns the sequential variant index for an explicit enum value (reverse of `field_value_int`). Returns `-1` if no variant matches.
|
- `field_index($T: Type, val: T) -> s64` — returns the sequential variant index for an explicit enum value (reverse of `field_value_int`). Returns `-1` if no variant matches.
|
||||||
|
- `is_flags($T: Type) -> bool` — returns `true` if `T` is a flags enum (declared with `#flags`)
|
||||||
|
|
||||||
### Type Conversion
|
### Type Conversion
|
||||||
- `cast(Type) expr` — prefix operator that converts `expr` to `Type`. Examples: `cast(s32) 3.14`, `cast(f64) n`. When `Type` is a runtime `Type` value inside a type-category match arm, the compiler generates a dispatch switch over all types in the category, monomorphizing the callee for each concrete type.
|
- `cast(Type) expr` — prefix operator that converts `expr` to `Type`. Examples: `cast(s32) 3.14`, `cast(f64) n`. When `Type` is a runtime `Type` value inside a type-category match arm, the compiler generates a dispatch switch over all types in the category, monomorphizing the callee for each concrete type.
|
||||||
@@ -1031,14 +1038,14 @@ Functions within a namespaced import can call each other without the namespace p
|
|||||||
mul :: (base: $T, exp: T) -> T { base * exp; }
|
mul :: (base: $T, exp: T) -> T { base * exp; }
|
||||||
|
|
||||||
// modules/std/std.sx
|
// modules/std/std.sx
|
||||||
print :: (str: string) -> void #builtin;
|
out :: (str: string) -> void #builtin;
|
||||||
|
|
||||||
// main.sx
|
// main.sx
|
||||||
std :: #import "modules/std.sx";
|
std :: #import "modules/std.sx";
|
||||||
#import "modules/std/math.sx";
|
#import "modules/std/math.sx";
|
||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> s32 {
|
||||||
std.print("hello there");
|
std.out("hello there");
|
||||||
mul(5, 2);
|
mul(5, 2);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ const c = llvm.c;
|
|||||||
|
|
||||||
pub const Builtins = struct {
|
pub const Builtins = struct {
|
||||||
printf_fn: c.LLVMValueRef,
|
printf_fn: c.LLVMValueRef,
|
||||||
calloc_fn: c.LLVMValueRef,
|
|
||||||
malloc_fn: c.LLVMValueRef,
|
malloc_fn: c.LLVMValueRef,
|
||||||
free_fn: c.LLVMValueRef,
|
free_fn: c.LLVMValueRef,
|
||||||
memcpy_fn: c.LLVMValueRef,
|
memcpy_fn: c.LLVMValueRef,
|
||||||
|
memset_fn: c.LLVMValueRef,
|
||||||
|
|
||||||
pub fn init(module: c.LLVMModuleRef, ctx: c.LLVMContextRef) Builtins {
|
pub fn init(module: c.LLVMModuleRef, ctx: c.LLVMContextRef) Builtins {
|
||||||
const ptr_type = c.LLVMPointerTypeInContext(ctx, 0);
|
const ptr_type = c.LLVMPointerTypeInContext(ctx, 0);
|
||||||
@@ -19,11 +19,6 @@ pub const Builtins = struct {
|
|||||||
const printf_type = c.LLVMFunctionType(i32_type, &printf_params, 1, 1);
|
const printf_type = c.LLVMFunctionType(i32_type, &printf_params, 1, 1);
|
||||||
const printf_fn = c.LLVMAddFunction(module, "printf", printf_type);
|
const printf_fn = c.LLVMAddFunction(module, "printf", printf_type);
|
||||||
|
|
||||||
// Declare: void* calloc(size_t count, size_t size)
|
|
||||||
var calloc_params = [_]c.LLVMTypeRef{ i64_type, i64_type };
|
|
||||||
const calloc_type = c.LLVMFunctionType(ptr_type, &calloc_params, 2, 0);
|
|
||||||
const calloc_fn = c.LLVMAddFunction(module, "calloc", calloc_type);
|
|
||||||
|
|
||||||
// Declare: void* malloc(size_t size)
|
// Declare: void* malloc(size_t size)
|
||||||
var malloc_params = [_]c.LLVMTypeRef{i64_type};
|
var malloc_params = [_]c.LLVMTypeRef{i64_type};
|
||||||
const malloc_type = c.LLVMFunctionType(ptr_type, &malloc_params, 1, 0);
|
const malloc_type = c.LLVMFunctionType(ptr_type, &malloc_params, 1, 0);
|
||||||
@@ -39,12 +34,17 @@ pub const Builtins = struct {
|
|||||||
const memcpy_type = c.LLVMFunctionType(ptr_type, &memcpy_params, 3, 0);
|
const memcpy_type = c.LLVMFunctionType(ptr_type, &memcpy_params, 3, 0);
|
||||||
const memcpy_fn = c.LLVMAddFunction(module, "memcpy", memcpy_type);
|
const memcpy_fn = c.LLVMAddFunction(module, "memcpy", memcpy_type);
|
||||||
|
|
||||||
|
// Declare: void* memset(void* s, int c, size_t n)
|
||||||
|
var memset_params = [_]c.LLVMTypeRef{ ptr_type, i32_type, i64_type };
|
||||||
|
const memset_type = c.LLVMFunctionType(ptr_type, &memset_params, 3, 0);
|
||||||
|
const memset_fn = c.LLVMAddFunction(module, "memset", memset_type);
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.printf_fn = printf_fn,
|
.printf_fn = printf_fn,
|
||||||
.calloc_fn = calloc_fn,
|
|
||||||
.malloc_fn = malloc_fn,
|
.malloc_fn = malloc_fn,
|
||||||
.free_fn = free_fn,
|
.free_fn = free_fn,
|
||||||
.memcpy_fn = memcpy_fn,
|
.memcpy_fn = memcpy_fn,
|
||||||
|
.memset_fn = memset_fn,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1319,7 +1319,7 @@ pub const CodeGen = struct {
|
|||||||
.void_val => self.constInt32(0),
|
.void_val => self.constInt32(0),
|
||||||
.pointer_val => c.LLVMConstNull(self.ptrType()),
|
.pointer_val => c.LLVMConstNull(self.ptrType()),
|
||||||
.null_val => c.LLVMConstNull(self.ptrType()),
|
.null_val => c.LLVMConstNull(self.ptrType()),
|
||||||
.struct_val, .array_val, .type_val, .function_val => unreachable,
|
.struct_val, .array_val, .type_val, .function_val, .byte_ptr_val, .union_val => unreachable,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1451,13 +1451,21 @@ pub const CodeGen = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Safety net: inline declarations that should have been hoisted
|
// Inline type declarations: resolve by registered name
|
||||||
if (tn.data == .struct_decl) {
|
if (tn.data == .struct_decl) {
|
||||||
const sn = tn.data.struct_decl.name;
|
const sn = tn.data.struct_decl.name;
|
||||||
if (self.type_registry.get(sn)) |e| {
|
if (self.type_registry.get(sn)) |e| {
|
||||||
if (e == .struct_info) return .{ .struct_type = sn };
|
if (e == .struct_info) return .{ .struct_type = sn };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (tn.data == .union_decl) {
|
||||||
|
const un = tn.data.union_decl.name;
|
||||||
|
if (self.type_registry.get(un)) |e| switch (e) {
|
||||||
|
.union_info => return .{ .union_type = un },
|
||||||
|
.tagged_enum => return .{ .union_type = un },
|
||||||
|
else => {},
|
||||||
|
};
|
||||||
|
}
|
||||||
if (tn.data == .enum_decl) {
|
if (tn.data == .enum_decl) {
|
||||||
const en = tn.data.enum_decl.name;
|
const en = tn.data.enum_decl.name;
|
||||||
if (self.type_registry.get(en)) |e| switch (e) {
|
if (self.type_registry.get(en)) |e| switch (e) {
|
||||||
@@ -3565,17 +3573,14 @@ pub const CodeGen = struct {
|
|||||||
var hoisted = inline_sd;
|
var hoisted = inline_sd;
|
||||||
hoisted.name = synthetic_name;
|
hoisted.name = synthetic_name;
|
||||||
try self.registerStructType(hoisted);
|
try self.registerStructType(hoisted);
|
||||||
type_node.data = .{ .type_expr = .{ .name = synthetic_name } };
|
|
||||||
},
|
},
|
||||||
.union_decl => |inline_ud| {
|
.union_decl => |inline_ud| {
|
||||||
var hoisted_ud = inline_ud;
|
var hoisted_ud = inline_ud;
|
||||||
hoisted_ud.name = synthetic_name;
|
hoisted_ud.name = synthetic_name;
|
||||||
try self.registerUnionType(hoisted_ud);
|
try self.registerUnionType(hoisted_ud);
|
||||||
type_node.data = .{ .type_expr = .{ .name = synthetic_name } };
|
|
||||||
},
|
},
|
||||||
.enum_decl => |inline_ed| {
|
.enum_decl => |inline_ed| {
|
||||||
if (inline_ed.variant_types.len > 0) {
|
if (inline_ed.variant_types.len > 0) {
|
||||||
// Tagged enum with payloads
|
|
||||||
var hoisted = inline_ed;
|
var hoisted = inline_ed;
|
||||||
hoisted.name = synthetic_name;
|
hoisted.name = synthetic_name;
|
||||||
try self.registerTaggedEnum(hoisted);
|
try self.registerTaggedEnum(hoisted);
|
||||||
@@ -3587,7 +3592,6 @@ pub const CodeGen = struct {
|
|||||||
try self.enum_backing_types.put(synthetic_name, self.typeToLLVM(bt));
|
try self.enum_backing_types.put(synthetic_name, self.typeToLLVM(bt));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
type_node.data = .{ .type_expr = .{ .name = synthetic_name } };
|
|
||||||
},
|
},
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
@@ -4927,21 +4931,7 @@ pub const CodeGen = struct {
|
|||||||
return self.convertValue(val, src_ty, target_ty);
|
return self.convertValue(val, src_ty, target_ty);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn genAlloc(self: *CodeGen, args: []const *Node) !c.LLVMValueRef {
|
|
||||||
if (args.len != 1) return self.emitError("alloc expects exactly 1 argument: alloc(size)");
|
|
||||||
const builtins = try self.requireBuiltins();
|
|
||||||
const size_val = try self.genExpr(args[0]);
|
|
||||||
const i64_type = self.i64Type();
|
|
||||||
// calloc(size + 1, 1) — extra byte for null terminator
|
|
||||||
const one_i64 = c.LLVMConstInt(i64_type, 1, 0);
|
|
||||||
const size_plus_one = c.LLVMBuildAdd(self.builder, size_val, one_i64, "szp1");
|
|
||||||
const calloc_fn = builtins.calloc_fn;
|
|
||||||
const calloc_ty = c.LLVMGlobalGetValueType(calloc_fn);
|
|
||||||
var calloc_args = [_]c.LLVMValueRef{ size_plus_one, one_i64 };
|
|
||||||
const ptr = c.LLVMBuildCall2(self.builder, calloc_ty, calloc_fn, &calloc_args, 2, "alloc_ptr");
|
|
||||||
// Build string slice: {ptr, size}
|
|
||||||
return self.buildStringSlice(ptr, size_val);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn genMalloc(self: *CodeGen, args: []const *Node) !c.LLVMValueRef {
|
fn genMalloc(self: *CodeGen, args: []const *Node) !c.LLVMValueRef {
|
||||||
if (args.len != 1) return self.emitError("malloc expects exactly 1 argument: malloc(size)");
|
if (args.len != 1) return self.emitError("malloc expects exactly 1 argument: malloc(size)");
|
||||||
@@ -4974,6 +4964,19 @@ pub const CodeGen = struct {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn genMemset(self: *CodeGen, args: []const *Node) !c.LLVMValueRef {
|
||||||
|
if (args.len != 3) return self.emitError("memset expects 3 arguments: memset(dst, val, size)");
|
||||||
|
const builtins = try self.requireBuiltins();
|
||||||
|
const dst = try self.genExpr(args[0]);
|
||||||
|
const val = try self.genExpr(args[1]);
|
||||||
|
const size_val = try self.genExpr(args[2]);
|
||||||
|
const val_i32 = self.trunc(val, self.i32Type(), "memset_val");
|
||||||
|
const fn_ty = c.LLVMGlobalGetValueType(builtins.memset_fn);
|
||||||
|
var call_args = [_]c.LLVMValueRef{ dst, val_i32, size_val };
|
||||||
|
_ = c.LLVMBuildCall2(self.builder, fn_ty, builtins.memset_fn, &call_args, 3, "");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
fn genVectorExtract(self: *CodeGen, vec_val: c.LLVMValueRef, field: []const u8) !c.LLVMValueRef {
|
fn genVectorExtract(self: *CodeGen, vec_val: c.LLVMValueRef, field: []const u8) !c.LLVMValueRef {
|
||||||
if (field.len == 1) {
|
if (field.len == 1) {
|
||||||
const idx_val = componentToIndex(field[0]) orelse return self.emitErrorFmt("invalid vector component '{c}'", .{field[0]});
|
const idx_val = componentToIndex(field[0]) orelse return self.emitErrorFmt("invalid vector component '{c}'", .{field[0]});
|
||||||
@@ -6882,10 +6885,10 @@ pub const CodeGen = struct {
|
|||||||
if (std.mem.eql(u8, base, "cos")) return self.genMathIntrinsic(call_node, "cos");
|
if (std.mem.eql(u8, base, "cos")) return self.genMathIntrinsic(call_node, "cos");
|
||||||
if (std.mem.eql(u8, base, "size_of")) return self.genSizeOf(call_node);
|
if (std.mem.eql(u8, base, "size_of")) return self.genSizeOf(call_node);
|
||||||
if (std.mem.eql(u8, base, "cast")) return self.genCast(call_node);
|
if (std.mem.eql(u8, base, "cast")) return self.genCast(call_node);
|
||||||
if (std.mem.eql(u8, base, "alloc")) return self.genAlloc(call_node.args);
|
|
||||||
if (std.mem.eql(u8, base, "malloc")) return self.genMalloc(call_node.args);
|
if (std.mem.eql(u8, base, "malloc")) return self.genMalloc(call_node.args);
|
||||||
if (std.mem.eql(u8, base, "free")) return self.genFree(call_node.args);
|
if (std.mem.eql(u8, base, "free")) return self.genFree(call_node.args);
|
||||||
if (std.mem.eql(u8, base, "memcpy")) return self.genMemcpy(call_node.args);
|
if (std.mem.eql(u8, base, "memcpy")) return self.genMemcpy(call_node.args);
|
||||||
|
if (std.mem.eql(u8, base, "memset")) return self.genMemset(call_node.args);
|
||||||
if (std.mem.eql(u8, base, "type_of")) return self.genTypeOf(call_node);
|
if (std.mem.eql(u8, base, "type_of")) return self.genTypeOf(call_node);
|
||||||
if (std.mem.eql(u8, base, "type_name")) return self.genTypeName(call_node);
|
if (std.mem.eql(u8, base, "type_name")) return self.genTypeName(call_node);
|
||||||
if (std.mem.eql(u8, base, "field_count")) return self.genFieldCount(call_node);
|
if (std.mem.eql(u8, base, "field_count")) return self.genFieldCount(call_node);
|
||||||
@@ -7144,11 +7147,10 @@ pub const CodeGen = struct {
|
|||||||
if (call_node.args.len > 0) return self.resolveType(call_node.args[0]);
|
if (call_node.args.len > 0) return self.resolveType(call_node.args[0]);
|
||||||
return Type.s(64);
|
return Type.s(64);
|
||||||
}
|
}
|
||||||
// Built-in: alloc returns string
|
|
||||||
if (std.mem.eql(u8, base_name, "alloc")) return .string_type;
|
|
||||||
if (std.mem.eql(u8, base_name, "malloc")) return .{ .pointer_type = .{ .pointee_name = "void" } };
|
if (std.mem.eql(u8, base_name, "malloc")) return .{ .pointer_type = .{ .pointee_name = "void" } };
|
||||||
if (std.mem.eql(u8, base_name, "free")) return .void_type;
|
if (std.mem.eql(u8, base_name, "free")) return .void_type;
|
||||||
if (std.mem.eql(u8, base_name, "memcpy")) return .void_type;
|
if (std.mem.eql(u8, base_name, "memcpy")) return .{ .pointer_type = .{ .pointee_name = "void" } };
|
||||||
|
if (std.mem.eql(u8, base_name, "memset")) return .void_type;
|
||||||
// Check generic templates — infer return type from widened bindings
|
// Check generic templates — infer return type from widened bindings
|
||||||
const template = self.generic_templates.get(callee_name) orelse blk: {
|
const template = self.generic_templates.get(callee_name) orelse blk: {
|
||||||
// Intra-namespace fallback
|
// Intra-namespace fallback
|
||||||
|
|||||||
280
src/comptime.zig
280
src/comptime.zig
@@ -21,12 +21,19 @@ pub const Value = union(enum) {
|
|||||||
type_val: Type,
|
type_val: Type,
|
||||||
function_val: FunctionVal,
|
function_val: FunctionVal,
|
||||||
pointer_val: PointerValue,
|
pointer_val: PointerValue,
|
||||||
|
byte_ptr_val: BytePtr,
|
||||||
|
union_val: UnionValue,
|
||||||
null_val: void,
|
null_val: void,
|
||||||
|
|
||||||
pub const PointerValue = struct {
|
pub const PointerValue = struct {
|
||||||
target: [*]Value,
|
target: [*]Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const BytePtr = struct {
|
||||||
|
data: []u8,
|
||||||
|
offset: usize,
|
||||||
|
};
|
||||||
|
|
||||||
pub const StructValue = struct {
|
pub const StructValue = struct {
|
||||||
type_name: []const u8,
|
type_name: []const u8,
|
||||||
field_names: []const []const u8,
|
field_names: []const []const u8,
|
||||||
@@ -37,6 +44,11 @@ pub const Value = union(enum) {
|
|||||||
elements: []Value,
|
elements: []Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const UnionValue = struct {
|
||||||
|
type_name: []const u8,
|
||||||
|
words: []Value,
|
||||||
|
};
|
||||||
|
|
||||||
pub const FunctionVal = struct {
|
pub const FunctionVal = struct {
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
param_count: u8,
|
param_count: u8,
|
||||||
@@ -123,6 +135,19 @@ pub const Value = union(enum) {
|
|||||||
const inner = try pv.target[0].format(allocator);
|
const inner = try pv.target[0].format(allocator);
|
||||||
return std.fmt.allocPrint(allocator, "@{s}", .{inner});
|
return std.fmt.allocPrint(allocator, "@{s}", .{inner});
|
||||||
},
|
},
|
||||||
|
.byte_ptr_val => |sp| std.fmt.allocPrint(allocator, "<byte_ptr@{d}>", .{sp.offset}),
|
||||||
|
.union_val => |v| {
|
||||||
|
var buf: std.ArrayList(u8) = .empty;
|
||||||
|
try buf.appendSlice(allocator, v.type_name);
|
||||||
|
try buf.append(allocator, '{');
|
||||||
|
for (v.words, 0..) |w, i| {
|
||||||
|
if (i > 0) try buf.appendSlice(allocator, ", ");
|
||||||
|
const ws = try w.format(allocator);
|
||||||
|
try buf.appendSlice(allocator, ws);
|
||||||
|
}
|
||||||
|
try buf.append(allocator, '}');
|
||||||
|
return buf.items;
|
||||||
|
},
|
||||||
.null_val => allocator.dupe(u8, "null"),
|
.null_val => allocator.dupe(u8, "null"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -208,16 +233,24 @@ pub const Instruction = union(enum) {
|
|||||||
concat,
|
concat,
|
||||||
format_to_string, // convert top-of-stack value to string representation
|
format_to_string, // convert top-of-stack value to string representation
|
||||||
|
|
||||||
|
// Unions
|
||||||
|
make_union: UnionMake,
|
||||||
|
get_union_field: UnionFieldAccess,
|
||||||
|
|
||||||
pub const CastInfo = struct { to: ValueKind };
|
pub const CastInfo = struct { to: ValueKind };
|
||||||
pub const CallInfo = struct { func_name: []const u8, arg_count: u8 };
|
pub const CallInfo = struct { func_name: []const u8, arg_count: u8 };
|
||||||
pub const BuiltinCall = struct { id: BuiltinId, arg_count: u8 };
|
pub const BuiltinCall = struct { id: BuiltinId, arg_count: u8 };
|
||||||
pub const StructMake = struct { type_name: []const u8, field_count: u16, field_names: []const []const u8 };
|
pub const StructMake = struct { type_name: []const u8, field_count: u16, field_names: []const []const u8 };
|
||||||
pub const FnRef = struct { name: []const u8, param_count: u8 };
|
pub const FnRef = struct { name: []const u8, param_count: u8 };
|
||||||
|
pub const UnionMake = struct { type_name: []const u8, word_count: u16 };
|
||||||
|
pub const UnionFieldAccess = struct { word_offset: u16, field_type: UnionFieldType };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const UnionFieldType = enum { int, float, bool_k, pointer, string };
|
||||||
|
|
||||||
pub const ValueKind = enum { int, float, f32_k, bool_k, string };
|
pub const ValueKind = enum { int, float, f32_k, bool_k, string };
|
||||||
|
|
||||||
pub const BuiltinId = enum { print, out, sqrt, size_of, cast, alloc };
|
pub const BuiltinId = enum { print, out, sqrt, size_of, cast, malloc, free, memcpy, memset };
|
||||||
|
|
||||||
/// A compiled function or expression — a flat sequence of instructions.
|
/// A compiled function or expression — a flat sequence of instructions.
|
||||||
pub const Chunk = struct {
|
pub const Chunk = struct {
|
||||||
@@ -273,6 +306,7 @@ pub const Compiler = struct {
|
|||||||
const Local = struct {
|
const Local = struct {
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
depth: u16,
|
depth: u16,
|
||||||
|
type_name: ?[]const u8 = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator, sema_result: ?*const sema.SemaResult, root_decls: []const *Node, cg: ?*codegen_mod.CodeGen) Compiler {
|
pub fn init(allocator: std.mem.Allocator, sema_result: ?*const sema.SemaResult, root_decls: []const *Node, cg: ?*codegen_mod.CodeGen) Compiler {
|
||||||
@@ -347,9 +381,13 @@ pub const Compiler = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Look up a struct field index by name, handling pointer auto-deref.
|
/// Look up a struct field index by name, handling pointer auto-deref.
|
||||||
|
/// Also resolves promoted fields from anonymous struct variants of unions.
|
||||||
fn resolveFieldIndex(self: *Compiler, object: *Node, field: []const u8) ?u16 {
|
fn resolveFieldIndex(self: *Compiler, object: *Node, field: []const u8) ?u16 {
|
||||||
if (self.sema_result) |sr| {
|
if (self.sema_result) |sr| {
|
||||||
const obj_ty = sr.type_map.get(object) orelse return null;
|
const obj_ty = sr.type_map.get(object) orelse {
|
||||||
|
// Sema doesn't have type info — try union fallback
|
||||||
|
return self.resolveFieldViaUnion(object, field);
|
||||||
|
};
|
||||||
const struct_name: ?[]const u8 = if (obj_ty.isStruct())
|
const struct_name: ?[]const u8 = if (obj_ty.isStruct())
|
||||||
obj_ty.struct_type
|
obj_ty.struct_type
|
||||||
else if (obj_ty.isPointer())
|
else if (obj_ty.isPointer())
|
||||||
@@ -365,10 +403,93 @@ pub const Compiler = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Fall through to union fallback
|
||||||
|
return self.resolveFieldViaUnion(object, field);
|
||||||
|
}
|
||||||
|
return self.resolveFieldViaUnion(object, field);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolveFieldViaUnion(self: *Compiler, object: *Node, field: []const u8) ?u16 {
|
||||||
|
const tname = self.getLocalTypeName(object) orelse return null;
|
||||||
|
return self.resolveUnionPromotedField(tname, field);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find a union declaration in root_decls by name and return its word count
|
||||||
|
/// (number of 8-byte slots needed for the largest variant).
|
||||||
|
fn findUnionWordCount(self: *Compiler, type_name: []const u8) ?u16 {
|
||||||
|
for (self.root_decls) |decl| {
|
||||||
|
if (decl.data == .union_decl) {
|
||||||
|
const ud = decl.data.union_decl;
|
||||||
|
if (std.mem.eql(u8, ud.name, type_name)) {
|
||||||
|
var max_words: u16 = 0;
|
||||||
|
for (ud.field_types) |ft| {
|
||||||
|
var words: u16 = 1; // default: single-word variant
|
||||||
|
if (ft.data == .struct_decl) {
|
||||||
|
words = @intCast(ft.data.struct_decl.field_names.len);
|
||||||
|
} else if (ft.data == .type_expr) {
|
||||||
|
if (Type.fromTypeExpr(ft)) |ty| {
|
||||||
|
// string = {ptr, len} = 2 words
|
||||||
|
if (ty == .string_type) words = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (words > max_words) max_words = words;
|
||||||
|
}
|
||||||
|
return max_words;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Find a union declaration in root_decls by name.
|
||||||
|
fn findUnionDecl(self: *Compiler, type_name: []const u8) ?ast.UnionDecl {
|
||||||
|
for (self.root_decls) |decl| {
|
||||||
|
if (decl.data == .union_decl) {
|
||||||
|
const ud = decl.data.union_decl;
|
||||||
|
if (std.mem.eql(u8, ud.name, type_name)) return ud;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolve a promoted field from an anonymous struct variant of a union.
|
||||||
|
/// Returns the field index within the anonymous struct.
|
||||||
|
fn resolveUnionPromotedField(self: *Compiler, type_name: []const u8, field: []const u8) ?u16 {
|
||||||
|
const ud = self.findUnionDecl(type_name) orelse return null;
|
||||||
|
for (ud.field_types) |ft| {
|
||||||
|
if (ft.data == .struct_decl) {
|
||||||
|
const sd = ft.data.struct_decl;
|
||||||
|
for (sd.field_names, 0..) |fname, idx| {
|
||||||
|
if (std.mem.eql(u8, fname, field)) return @intCast(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the local's type name if the object is an identifier.
|
||||||
|
fn getLocalTypeName(self: *Compiler, object: *Node) ?[]const u8 {
|
||||||
|
if (object.data != .identifier) return null;
|
||||||
|
const slot = self.resolveLocal(object.data.identifier.name) orelse return null;
|
||||||
|
return self.locals.items[slot].type_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Map a union variant's type node to a UnionFieldType for get_union_field.
|
||||||
|
fn nodeToUnionFieldType(_: *Compiler, type_node: *Node) UnionFieldType {
|
||||||
|
if (type_node.data == .type_expr) {
|
||||||
|
const name = type_node.data.type_expr.name;
|
||||||
|
if (std.mem.eql(u8, name, "string")) return .string;
|
||||||
|
if (std.mem.eql(u8, name, "s64") or std.mem.eql(u8, name, "s32") or
|
||||||
|
std.mem.eql(u8, name, "u64") or std.mem.eql(u8, name, "u32") or
|
||||||
|
std.mem.eql(u8, name, "s16") or std.mem.eql(u8, name, "u16") or
|
||||||
|
std.mem.eql(u8, name, "s8") or std.mem.eql(u8, name, "u8")) return .int;
|
||||||
|
if (std.mem.eql(u8, name, "f64") or std.mem.eql(u8, name, "f32")) return .float;
|
||||||
|
if (std.mem.eql(u8, name, "bool")) return .bool_k;
|
||||||
|
}
|
||||||
|
// Default to pointer for unknown/complex types
|
||||||
|
return .pointer;
|
||||||
|
}
|
||||||
|
|
||||||
fn resolveLocal(self: *Compiler, name: []const u8) ?u16 {
|
fn resolveLocal(self: *Compiler, name: []const u8) ?u16 {
|
||||||
var i = self.locals.items.len;
|
var i = self.locals.items.len;
|
||||||
while (i > 0) {
|
while (i > 0) {
|
||||||
@@ -534,13 +655,32 @@ pub const Compiler = struct {
|
|||||||
self.scope_depth -= 1;
|
self.scope_depth -= 1;
|
||||||
},
|
},
|
||||||
.var_decl => |vd| {
|
.var_decl => |vd| {
|
||||||
|
// Extract type name from annotation
|
||||||
|
const type_name: ?[]const u8 = if (vd.type_annotation) |ta|
|
||||||
|
(if (ta.data == .type_expr) ta.data.type_expr.name else null)
|
||||||
|
else
|
||||||
|
null;
|
||||||
|
|
||||||
if (vd.value) |val| {
|
if (vd.value) |val| {
|
||||||
try self.compileNode(val);
|
if (val.data == .undef_literal) {
|
||||||
|
// Undefined init — check if type is a union and emit make_union
|
||||||
|
if (type_name) |tname| {
|
||||||
|
if (self.findUnionWordCount(tname)) |wc| {
|
||||||
|
try self.emit(.{ .make_union = .{ .type_name = tname, .word_count = wc } });
|
||||||
|
} else {
|
||||||
|
try self.emit(.push_void);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try self.emit(.push_void);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try self.compileNode(val);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
try self.emit(.push_void);
|
try self.emit(.push_void);
|
||||||
}
|
}
|
||||||
const slot: u16 = @intCast(self.locals.items.len);
|
const slot: u16 = @intCast(self.locals.items.len);
|
||||||
try self.locals.append(self.allocator, .{ .name = vd.name, .depth = self.scope_depth });
|
try self.locals.append(self.allocator, .{ .name = vd.name, .depth = self.scope_depth, .type_name = type_name });
|
||||||
try self.emit(.{ .set_local = slot });
|
try self.emit(.{ .set_local = slot });
|
||||||
},
|
},
|
||||||
.const_decl => |cd| {
|
.const_decl => |cd| {
|
||||||
@@ -725,8 +865,20 @@ pub const Compiler = struct {
|
|||||||
try self.emit(.{ .get_field = field_idx });
|
try self.emit(.{ .get_field = field_idx });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Fallback: use field name for well-known string fields
|
// Check for union variant access (e.g. rs.s where rs is CString union)
|
||||||
// (sema may not have type info for nodes in imported function bodies)
|
if (self.getLocalTypeName(fa.object)) |tname| {
|
||||||
|
if (self.findUnionDecl(tname)) |ud| {
|
||||||
|
for (ud.field_names, ud.field_types) |fname, ftype| {
|
||||||
|
if (std.mem.eql(u8, fname, fa.field)) {
|
||||||
|
const uft = self.nodeToUnionFieldType(ftype);
|
||||||
|
try self.emit(.{ .get_union_field = .{ .word_offset = 0, .field_type = uft } });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fallback for untyped field access (e.g. imported function bodies
|
||||||
|
// without sema info): assume fat pointer layout {ptr=0, len=1}
|
||||||
if (std.mem.eql(u8, fa.field, "len")) {
|
if (std.mem.eql(u8, fa.field, "len")) {
|
||||||
try self.emit(.{ .get_field = 1 });
|
try self.emit(.{ .get_field = 1 });
|
||||||
} else {
|
} else {
|
||||||
@@ -953,6 +1105,9 @@ pub const VM = struct {
|
|||||||
if (arr == .array_val) {
|
if (arr == .array_val) {
|
||||||
if (idx >= arr.array_val.elements.len) return error.IndexOutOfBounds;
|
if (idx >= arr.array_val.elements.len) return error.IndexOutOfBounds;
|
||||||
try self.push(.{ .pointer_val = .{ .target = arr.array_val.elements.ptr + idx } });
|
try self.push(.{ .pointer_val = .{ .target = arr.array_val.elements.ptr + idx } });
|
||||||
|
} else if (arr == .string_val) {
|
||||||
|
if (idx > arr.string_val.len) return error.IndexOutOfBounds;
|
||||||
|
try self.push(.{ .byte_ptr_val = .{ .data = @constCast(arr.string_val), .offset = idx } });
|
||||||
} else {
|
} else {
|
||||||
return error.TypeError;
|
return error.TypeError;
|
||||||
}
|
}
|
||||||
@@ -1088,11 +1243,17 @@ pub const VM = struct {
|
|||||||
try self.push(.{ .void_val = {} });
|
try self.push(.{ .void_val = {} });
|
||||||
}
|
}
|
||||||
} else if (obj == .string_val) {
|
} else if (obj == .string_val) {
|
||||||
// String slice: field 0 = ptr (return string itself), field 1 = len
|
// String slice: field 0 = ptr (byte-level pointer), field 1 = len
|
||||||
if (idx == 1) {
|
if (idx == 1) {
|
||||||
try self.push(.{ .int_val = @intCast(obj.string_val.len) });
|
try self.push(.{ .int_val = @intCast(obj.string_val.len) });
|
||||||
} else {
|
} else {
|
||||||
try self.push(obj); // ptr → return string itself
|
try self.push(.{ .byte_ptr_val = .{ .data = @constCast(obj.string_val), .offset = 0 } });
|
||||||
|
}
|
||||||
|
} else if (obj == .union_val) {
|
||||||
|
if (idx < obj.union_val.words.len) {
|
||||||
|
try self.push(obj.union_val.words[idx]);
|
||||||
|
} else {
|
||||||
|
try self.push(.{ .void_val = {} });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return error.TypeError;
|
return error.TypeError;
|
||||||
@@ -1109,6 +1270,11 @@ pub const VM = struct {
|
|||||||
if (idx < sv.fields.len) {
|
if (idx < sv.fields.len) {
|
||||||
sv.fields[idx] = val;
|
sv.fields[idx] = val;
|
||||||
}
|
}
|
||||||
|
} else if (target[0] == .union_val) {
|
||||||
|
const uv = target[0].union_val;
|
||||||
|
if (idx < uv.words.len) {
|
||||||
|
uv.words[idx] = val;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
try self.push(raw_obj); // push pointer back
|
try self.push(raw_obj); // push pointer back
|
||||||
} else if (raw_obj == .struct_val) {
|
} else if (raw_obj == .struct_val) {
|
||||||
@@ -1116,6 +1282,11 @@ pub const VM = struct {
|
|||||||
raw_obj.struct_val.fields[idx] = val;
|
raw_obj.struct_val.fields[idx] = val;
|
||||||
}
|
}
|
||||||
try self.push(raw_obj);
|
try self.push(raw_obj);
|
||||||
|
} else if (raw_obj == .union_val) {
|
||||||
|
if (idx < raw_obj.union_val.words.len) {
|
||||||
|
raw_obj.union_val.words[idx] = val;
|
||||||
|
}
|
||||||
|
try self.push(raw_obj);
|
||||||
} else {
|
} else {
|
||||||
return error.TypeError;
|
return error.TypeError;
|
||||||
}
|
}
|
||||||
@@ -1201,6 +1372,49 @@ pub const VM = struct {
|
|||||||
try self.push(.{ .string_val = s });
|
try self.push(.{ .string_val = s });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Unions
|
||||||
|
.make_union => |um| {
|
||||||
|
const words = try self.allocator.alloc(Value, um.word_count);
|
||||||
|
@memset(words, .{ .void_val = {} });
|
||||||
|
try self.push(.{ .union_val = .{ .type_name = um.type_name, .words = words } });
|
||||||
|
},
|
||||||
|
.get_union_field => |uf| {
|
||||||
|
const raw_obj = try self.pop();
|
||||||
|
const obj = if (raw_obj == .pointer_val) raw_obj.pointer_val.target[0] else raw_obj;
|
||||||
|
if (obj == .union_val) {
|
||||||
|
const words = obj.union_val.words;
|
||||||
|
switch (uf.field_type) {
|
||||||
|
.string => {
|
||||||
|
// Reconstruct string_val from words[0] (byte_ptr_val) + words[1] (int_val)
|
||||||
|
if (uf.word_offset + 1 < words.len or (uf.word_offset == 0 and words.len >= 2)) {
|
||||||
|
const ptr_word = words[uf.word_offset];
|
||||||
|
const len_word = words[uf.word_offset + 1];
|
||||||
|
if (ptr_word == .byte_ptr_val) {
|
||||||
|
const bp = ptr_word.byte_ptr_val;
|
||||||
|
const len: usize = if (len_word.asInt()) |v| @intCast(@max(0, v)) else 0;
|
||||||
|
const end = @min(bp.offset + len, bp.data.len);
|
||||||
|
try self.push(.{ .string_val = bp.data[bp.offset..end] });
|
||||||
|
} else {
|
||||||
|
try self.push(.{ .string_val = "" });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try self.push(.{ .string_val = "" });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
// Single-word read
|
||||||
|
if (uf.word_offset < words.len) {
|
||||||
|
try self.push(words[uf.word_offset]);
|
||||||
|
} else {
|
||||||
|
try self.push(.{ .void_val = {} });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return error.TypeError;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
.cast => {},
|
.cast => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1288,6 +1502,13 @@ pub const VM = struct {
|
|||||||
}
|
}
|
||||||
return .{ .array_val = .{ .elements = new_elements } };
|
return .{ .array_val = .{ .elements = new_elements } };
|
||||||
},
|
},
|
||||||
|
.union_val => |uv| {
|
||||||
|
const new_words = try self.allocator.alloc(Value, uv.words.len);
|
||||||
|
for (uv.words, 0..) |w, i| {
|
||||||
|
new_words[i] = try self.cloneValue(w);
|
||||||
|
}
|
||||||
|
return .{ .union_val = .{ .type_name = uv.type_name, .words = new_words } };
|
||||||
|
},
|
||||||
else => val,
|
else => val,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1465,18 +1686,53 @@ pub const VM = struct {
|
|||||||
try self.push(.{ .int_val = 0 });
|
try self.push(.{ .int_val = 0 });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.alloc => {
|
.malloc => {
|
||||||
// alloc(size) — allocate zeroed byte buffer, return as string
|
// malloc(size) — allocate byte buffer, return as byte_ptr_val
|
||||||
if (arg_count >= 1) {
|
if (arg_count >= 1) {
|
||||||
const val = try self.pop();
|
const val = try self.pop();
|
||||||
const size: usize = if (val.asInt()) |v| @intCast(@max(0, v)) else 0;
|
const size: usize = if (val.asInt()) |v| @intCast(@max(0, v)) else 0;
|
||||||
const buf = try self.allocator.alloc(u8, size);
|
const buf = try self.allocator.alloc(u8, size);
|
||||||
@memset(buf, 0);
|
@memset(buf, 0);
|
||||||
try self.push(.{ .string_val = buf });
|
try self.push(.{ .byte_ptr_val = .{ .data = buf, .offset = 0 } });
|
||||||
} else {
|
} else {
|
||||||
try self.push(.{ .string_val = "" });
|
try self.push(.{ .byte_ptr_val = .{ .data = &.{}, .offset = 0 } });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
.free => {
|
||||||
|
// free(ptr) — no-op at comptime (arena cleanup)
|
||||||
|
if (arg_count >= 1) _ = try self.pop();
|
||||||
|
try self.push(.{ .void_val = {} });
|
||||||
|
},
|
||||||
|
.memcpy => {
|
||||||
|
// memcpy(dst, src, len) — copy len bytes from src to dst
|
||||||
|
if (arg_count >= 3) {
|
||||||
|
const len_val = try self.pop();
|
||||||
|
const src_val = try self.pop();
|
||||||
|
const dst_val = try self.pop();
|
||||||
|
const len: usize = if (len_val.asInt()) |v| @intCast(@max(0, v)) else 0;
|
||||||
|
if (dst_val == .byte_ptr_val and src_val == .byte_ptr_val) {
|
||||||
|
const dst = dst_val.byte_ptr_val;
|
||||||
|
const src = src_val.byte_ptr_val;
|
||||||
|
@memcpy(dst.data[dst.offset .. dst.offset + len], src.data[src.offset .. src.offset + len]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try self.push(.{ .void_val = {} });
|
||||||
|
},
|
||||||
|
.memset => {
|
||||||
|
// memset(dst, val, len) — fill len bytes at dst with val
|
||||||
|
if (arg_count >= 3) {
|
||||||
|
const len_val = try self.pop();
|
||||||
|
const val = try self.pop();
|
||||||
|
const dst_val = try self.pop();
|
||||||
|
const len: usize = if (len_val.asInt()) |v| @intCast(@max(0, v)) else 0;
|
||||||
|
const byte: u8 = if (val.asInt()) |v| @intCast(v & 0xFF) else 0;
|
||||||
|
if (dst_val == .byte_ptr_val) {
|
||||||
|
const dst = dst_val.byte_ptr_val;
|
||||||
|
@memset(dst.data[dst.offset .. dst.offset + len], byte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try self.push(.{ .void_val = {} });
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -437,7 +437,10 @@ pub const Server = struct {
|
|||||||
.{ .label = "field_value", .detail = "(s: $T, idx: s32) -> Any" },
|
.{ .label = "field_value", .detail = "(s: $T, idx: s32) -> Any" },
|
||||||
.{ .label = "size_of", .detail = "($T: Type) -> s32" },
|
.{ .label = "size_of", .detail = "($T: Type) -> s32" },
|
||||||
.{ .label = "cast", .detail = "(Type) expr — prefix type cast" },
|
.{ .label = "cast", .detail = "(Type) expr — prefix type cast" },
|
||||||
.{ .label = "alloc", .detail = "(size: s32) -> string" },
|
.{ .label = "malloc", .detail = "(size: s64) -> *void" },
|
||||||
|
.{ .label = "free", .detail = "(ptr: *void) -> void" },
|
||||||
|
.{ .label = "memcpy", .detail = "(dst: *void, src: *void, size: s64) -> *void" },
|
||||||
|
.{ .label = "memset", .detail = "(dst: *void, val: s64, size: s64) -> void" },
|
||||||
.{ .label = "sqrt", .detail = "(x: $T) -> T" },
|
.{ .label = "sqrt", .detail = "(x: $T) -> T" },
|
||||||
};
|
};
|
||||||
for (&keywords) |kw| {
|
for (&keywords) |kw| {
|
||||||
@@ -876,7 +879,10 @@ pub const Server = struct {
|
|||||||
.{ .name = "field_value", .label = "field_value(s: $T, idx: s32) -> Any", .params = &.{ "s: $T", "idx: s32" } },
|
.{ .name = "field_value", .label = "field_value(s: $T, idx: s32) -> Any", .params = &.{ "s: $T", "idx: s32" } },
|
||||||
.{ .name = "size_of", .label = "size_of($T: Type) -> s32", .params = &.{"$T: Type"} },
|
.{ .name = "size_of", .label = "size_of($T: Type) -> s32", .params = &.{"$T: Type"} },
|
||||||
.{ .name = "cast", .label = "cast(Type) expr", .params = &.{"Type"} },
|
.{ .name = "cast", .label = "cast(Type) expr", .params = &.{"Type"} },
|
||||||
.{ .name = "alloc", .label = "alloc(size: s32) -> string", .params = &.{"size: s32"} },
|
.{ .name = "malloc", .label = "malloc(size: s64) -> *void", .params = &.{"size: s64"} },
|
||||||
|
.{ .name = "free", .label = "free(ptr: *void) -> void", .params = &.{"ptr: *void"} },
|
||||||
|
.{ .name = "memcpy", .label = "memcpy(dst: *void, src: *void, size: s64) -> *void", .params = &.{ "dst: *void", "src: *void", "size: s64" } },
|
||||||
|
.{ .name = "memset", .label = "memset(dst: *void, val: s64, size: s64) -> void", .params = &.{ "dst: *void", "val: s64", "size: s64" } },
|
||||||
.{ .name = "sqrt", .label = "sqrt(x: $T) -> T", .params = &.{"x: $T"} },
|
.{ .name = "sqrt", .label = "sqrt(x: $T) -> T", .params = &.{"x: $T"} },
|
||||||
.{ .name = "print", .label = "print(fmt: string, args: ..Any)", .params = &.{ "fmt: string", "args: ..Any" } },
|
.{ .name = "print", .label = "print(fmt: string, args: ..Any)", .params = &.{ "fmt: string", "args: ..Any" } },
|
||||||
.{ .name = "out", .label = "out(str: string) -> void", .params = &.{"str: string"} },
|
.{ .name = "out", .label = "out(str: string) -> void", .params = &.{"str: string"} },
|
||||||
|
|||||||
Reference in New Issue
Block a user