http server

This commit is contained in:
agra
2026-02-17 16:57:12 +02:00
parent 66034b4fec
commit 4fd87309d9
17 changed files with 437 additions and 113 deletions

View File

@@ -10,6 +10,9 @@
"categories": [ "categories": [
"Programming Languages" "Programming Languages"
], ],
"activationEvents": [
"onLanguage:sx"
],
"main": "./out/extension.js", "main": "./out/extension.js",
"contributes": { "contributes": {
"languages": [ "languages": [

Binary file not shown.

View File

@@ -10,26 +10,26 @@ sum :: (args: ..s32) -> s32 {
print_all :: (args: ..s32) { print_all :: (args: ..s32) {
for args: (it) { for args: (it) {
write(int_to_string(it)); out(int_to_string(it));
write(" "); out(" ");
} }
write("\n"); out("\n");
} }
main :: () -> s32 { main :: () -> s32 {
write(int_to_string(sum(10, 20, 30))); out(int_to_string(sum(10, 20, 30)));
write("\n"); out("\n");
print_all(1, 2, 3, 4, 5); print_all(1, 2, 3, 4, 5);
arr : [3]s32 = .[10, 20, 30]; arr : [3]s32 = .[10, 20, 30];
write(int_to_string(sum(..arr))); out(int_to_string(sum(..arr)));
write("\n"); out("\n");
for arr: (it) { for arr: (it) {
write(int_to_string(it)); out(int_to_string(it));
write(" "); out(" ");
} }
write("\n"); out("\n");
0; 0;
} }

View File

@@ -10,22 +10,22 @@ print_any :: (args: ..Any) {
for args: (it) { for args: (it) {
type := type_of(it); type := type_of(it);
if type == { if type == {
case int: write(int_to_string(cast(s32) it)); case int: out(int_to_string(cast(s32) it));
case string: write(cast(string) it); case string: out(cast(string) it);
case bool: write(bool_to_string(cast(bool) it)); case bool: out(bool_to_string(cast(bool) it));
case float: write(float_to_string(cast(f64) it)); case float: out(float_to_string(cast(f64) it));
case Point: { case Point: {
p := cast(Point) it; p := cast(Point) it;
write("("); out("(");
write(int_to_string(p.x)); out(int_to_string(p.x));
write(","); out(",");
write(int_to_string(p.y)); out(int_to_string(p.y));
write(")"); out(")");
} }
} }
write(" "); out(" ");
} }
write("\n"); out("\n");
} }
count :: (args: ..Any) -> s32 { count :: (args: ..Any) -> s32 {
@@ -40,8 +40,8 @@ main :: () -> s32 {
print_any("point:", p, 99); print_any("point:", p, 99);
// Test count // Test count
write(int_to_string(count(1, 2, 3))); out(int_to_string(count(1, 2, 3)));
write("\n"); out("\n");
0; 0;
} }

View File

@@ -0,0 +1,63 @@
// HTTP server example (macOS only)
#import "modules/std.sx";
#import "modules/socket.sx";
main :: () -> s32 {
PORT :: 8080;
fd := socket(AF_INET, SOCK_STREAM, 0);
if fd < 0 {
print("error: socket()\n");
return 1;
}
opt : s32 = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, @opt, 4);
addr := SockAddr.{
sin_len = 16,
sin_family = 2,
sin_port = htons(PORT),
sin_addr = 0,
sin_zero = 0
};
if bind(fd, @addr, 16) < 0 {
print("error: bind()\n");
return 1;
}
if listen(fd, 10) < 0 {
print("error: listen()\n");
return 1;
}
print("listening on http://localhost:{}\n", PORT);
while true {
client := accept(fd, null, null);
if client < 0 { continue; }
// Read request
buf := alloc(4096);
read(client, buf, 4096);
// Send response
body := "<html><body><h1>Hello from sx!</h1></body></html>";
response := format("HTTP/1.1 200 OK\r
Content-Type: text/html\r
Connection: close\r
Content-Length: {}\r
\r
{}", body.len, body);
write(client, response, response.len);
close(client);
print(" served request\n");
}
close(fd);
0;
}

View File

@@ -91,6 +91,10 @@ gen_val :: () -> string {
return "print(\"insert-gen: {}\\n\", 42);"; return "print(\"insert-gen: {}\\n\", 42);";
} }
// --- Foreign function binding ---
libc :: #library "c";
c_abs :: (n: s32) -> s32 #foreign libc "abs";
// ============================================================ // ============================================================
main :: { main :: {
@@ -634,76 +638,76 @@ END;
// For loop basic // For loop basic
farr : [4]s32 = .[10, 20, 30, 40]; farr : [4]s32 = .[10, 20, 30, 40];
write("for:"); out("for:");
for farr: (it) { for farr: (it) {
write(" "); out(" ");
write(int_to_string(it)); out(int_to_string(it));
} }
write("\n"); out("\n");
// For with print // For with print
write("for-print:"); out("for-print:");
for farr: (it) { for farr: (it) {
print(" {}", it); print(" {}", it);
} }
write("\n"); out("\n");
// For with index // For with index
write("for-idx:"); out("for-idx:");
for farr: (_, ix) { for farr: (_, ix) {
write(" "); out(" ");
write(int_to_string(ix)); out(int_to_string(ix));
} }
write("\n"); out("\n");
// For with print two args // For with print two args
write("for-2arg:"); out("for-2arg:");
for farr: (it, ix) { for farr: (it, ix) {
print(" {}@{}", it, ix); print(" {}@{}", it, ix);
} }
write("\n"); out("\n");
// For with break // For with break
write("for-break:"); out("for-break:");
for farr: (it) { for farr: (it) {
if it == 30 { break; } if it == 30 { break; }
print(" {}", it); print(" {}", it);
} }
write("\n"); out("\n");
// For with continue // For with continue
write("for-continue:"); out("for-continue:");
for farr: (it) { for farr: (it) {
if it == 20 { continue; } if it == 20 { continue; }
print(" {}", it); print(" {}", it);
} }
write("\n"); out("\n");
// For on slice // For on slice
fsl : []s32 = .[10, 20, 30]; fsl : []s32 = .[10, 20, 30];
write("for-slice:"); out("for-slice:");
for fsl: (it) { for fsl: (it) {
print(" {}", it); print(" {}", it);
} }
write("\n"); out("\n");
// For on slice with index // For on slice with index
write("for-slice-idx:"); out("for-slice-idx:");
for fsl: (it, ix) { for fsl: (it, ix) {
print(" {}:{}", ix, it); print(" {}:{}", ix, it);
} }
write("\n"); out("\n");
// Nested for // Nested for
nf_a : [2]s32 = .[0, 1]; nf_a : [2]s32 = .[0, 1];
nf_b : [2]s32 = .[0, 1]; nf_b : [2]s32 = .[0, 1];
write("for-nested:"); out("for-nested:");
for nf_a: (oa) { for nf_a: (oa) {
for nf_b: (ob) { for nf_b: (ob) {
print(" ({},{})", oa, ob); print(" ({},{})", oa, ob);
} }
} }
write("\n"); out("\n");
// For with break preserving index // For with break preserving index
fbi : [5]s32 = .[10, 20, 30, 40, 50]; fbi : [5]s32 = .[10, 20, 30, 40, 50];
@@ -857,8 +861,8 @@ END;
// ======================================================== // ========================================================
print("=== 7. Builtins ===\n"); print("=== 7. Builtins ===\n");
// write // out
write("write-ok\n"); out("out-ok\n");
// sqrt // sqrt
print("sqrt: {}\n", sqrt(9.0)); print("sqrt: {}\n", sqrt(9.0));
@@ -932,12 +936,12 @@ END;
// field_value (use any_to_string to avoid sext-on-Any bug) // field_value (use any_to_string to avoid sext-on-Any bug)
fv_pt := Point.{ 11, 22 }; fv_pt := Point.{ 11, 22 };
write("fieldval0: "); out("fieldval0: ");
write(any_to_string(field_value(fv_pt, 0))); out(any_to_string(field_value(fv_pt, 0)));
write("\n"); out("\n");
write("fieldval1: "); out("fieldval1: ");
write(any_to_string(field_value(fv_pt, 1))); out(any_to_string(field_value(fv_pt, 1)));
write("\n"); out("\n");
// field_index on plain enum // field_index on plain enum
fi_c : Color = .green; fi_c : Color = .green;
@@ -1039,5 +1043,13 @@ END;
print("3-way: {} {} {}\n", ra, rb, rc); print("3-way: {} {} {}\n", ra, rb, rc);
} }
// ========================================================
// 15. FOREIGN FUNCTION BINDING
// ========================================================
print("=== 15. Foreign ===\n");
// Symbol rename: c_abs maps to C's abs()
print("foreign-rename: {}\n", c_abs(xx -42));
print("=== DONE ===\n"); print("=== DONE ===\n");
} }

View File

@@ -1,4 +1,4 @@
#library "raylib"; raylib :: #library "raylib";
Color :: struct { Color :: struct {
r, g, b, a: u8; r, g, b, a: u8;
@@ -8,10 +8,10 @@ Vector2 :: struct {
x, y: f32; x, y: f32;
} }
InitWindow :: (width: s32, height: s32, title: [:0]u8) -> void #foreign; InitWindow :: (width: s32, height: s32, title: [:0]u8) -> void #foreign raylib;
CloseWindow :: () -> void #foreign; CloseWindow :: () -> void #foreign raylib;
WindowShouldClose :: () -> bool #foreign; WindowShouldClose :: () -> bool #foreign raylib;
BeginDrawing :: () -> void #foreign; BeginDrawing :: () -> void #foreign raylib;
EndDrawing :: () -> void #foreign; EndDrawing :: () -> void #foreign raylib;
ClearBackground :: (color: Color) -> void #foreign; ClearBackground :: (color: Color) -> void #foreign raylib;
DrawTriangle :: (v1: Vector2, v2: Vector2, v3: Vector2, color: Color) -> void #foreign; DrawTriangle :: (v1: Vector2, v2: Vector2, v3: Vector2, color: Color) -> void #foreign raylib;

View File

@@ -1,4 +1,4 @@
#library "SDL3"; sdl3 :: #library "SDL3";
// SDL_InitFlags // SDL_InitFlags
SDL_INIT_VIDEO :u32: 0x20; SDL_INIT_VIDEO :u32: 0x20;
@@ -317,17 +317,17 @@ SDL_Event :: enum struct { tag: u32; _: u32; payload: [30]u32; } {
} }
// Functions // Functions
SDL_Init :: (flags: u32) -> bool #foreign; SDL_Init :: (flags: u32) -> bool #foreign sdl3;
SDL_Quit :: () -> void #foreign; SDL_Quit :: () -> void #foreign sdl3;
SDL_CreateWindow :: (title: [:0]u8, w: s32, h: s32, flags: u64) -> *void #foreign; SDL_CreateWindow :: (title: [:0]u8, w: s32, h: s32, flags: u64) -> *void #foreign sdl3;
SDL_DestroyWindow :: (window: *void) -> void #foreign; SDL_DestroyWindow :: (window: *void) -> void #foreign sdl3;
SDL_GL_SetAttribute :: (attr: s32, value: s32) -> bool #foreign; SDL_GL_SetAttribute :: (attr: s32, value: s32) -> bool #foreign sdl3;
SDL_GL_CreateContext :: (window: *void) -> *void #foreign; SDL_GL_CreateContext :: (window: *void) -> *void #foreign sdl3;
SDL_GL_DestroyContext :: (context: *void) -> bool #foreign; SDL_GL_DestroyContext :: (context: *void) -> bool #foreign sdl3;
SDL_GL_MakeCurrent :: (window: *void, context: *void) -> bool #foreign; SDL_GL_MakeCurrent :: (window: *void, context: *void) -> bool #foreign sdl3;
SDL_GL_SwapWindow :: (window: *void) -> bool #foreign; SDL_GL_SwapWindow :: (window: *void) -> bool #foreign sdl3;
SDL_GL_SetSwapInterval :: (interval: s32) -> bool #foreign; SDL_GL_SetSwapInterval :: (interval: s32) -> bool #foreign sdl3;
SDL_GL_GetProcAddress :: (proc: [:0]u8) -> *void #foreign; SDL_GL_GetProcAddress :: (proc: [:0]u8) -> *void #foreign sdl3;
SDL_PollEvent :: (event: *SDL_Event) -> bool #foreign; SDL_PollEvent :: (event: *SDL_Event) -> bool #foreign sdl3;
SDL_GetTicks :: () -> u64 #foreign; SDL_GetTicks :: () -> u64 #foreign sdl3;
SDL_Delay :: (ms: u32) -> void #foreign; SDL_Delay :: (ms: u32) -> void #foreign sdl3;

View File

@@ -0,0 +1,33 @@
// POSIX socket module (macOS only)
// sockaddr_in layout and constants are platform-specific.
libc :: #library "c";
// POSIX socket API
socket :: (domain: s32, kind: s32, protocol: s32) -> s32 #foreign libc;
setsockopt :: (fd: s32, level: s32, optname: s32, optval: *s32, optlen: u32) -> s32 #foreign libc;
bind :: (fd: s32, addr: *SockAddr, addrlen: u32) -> s32 #foreign libc;
listen :: (fd: s32, backlog: s32) -> s32 #foreign libc;
accept :: (fd: s32, addr: *SockAddr, addrlen: *u32) -> s32 #foreign libc;
read :: (fd: s32, buf: [:0]u8, count: s64) -> s64 #foreign libc;
write :: (fd: s32, buf: [:0]u8, count: s64) -> s64 #foreign libc;
close :: (fd: s32) -> s32 #foreign libc;
// Constants (macOS)
AF_INET :s32: 2;
SOCK_STREAM :s32: 1;
SOL_SOCKET :s32: 0xFFFF;
SO_REUSEADDR :s32: 0x4;
// macOS sockaddr_in (16 bytes, has sin_len field)
SockAddr :: struct {
sin_len: u8;
sin_family: u8;
sin_port: u16;
sin_addr: u32;
sin_zero: u64;
}
htons :: (port: s64) -> u16 {
cast(u16) ((port / 256) | ((port % 256) * 256));
}

View File

@@ -1,5 +1,5 @@
Vector :: ($N: int, $T: Type) -> Type #builtin; Vector :: ($N: int, $T: Type) -> Type #builtin;
write :: (str: string) -> void #builtin; out :: (str: string) -> void #builtin;
sqrt :: (x: $T) -> T #builtin; 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;
@@ -315,10 +315,81 @@ build_print :: (fmt: string) -> string {
code = concat(code, int_to_string(fmt.len - seg_start)); code = concat(code, int_to_string(fmt.len - seg_start));
code = concat(code, ")); "); code = concat(code, ")); ");
} }
code = concat(code, "write(result);"); code = concat(code, "out(result);");
code; code;
} }
build_format :: (fmt: string) -> string {
code := "result := \"\"; ";
seg_start := 0;
i := 0;
arg_idx := 0;
while i < fmt.len {
if fmt[i] == 123 {
if i + 1 < fmt.len {
if fmt[i + 1] == 125 {
if i > seg_start {
code = concat(code, "result = concat(result, substr(fmt, ");
code = concat(code, int_to_string(seg_start));
code = concat(code, ", ");
code = concat(code, int_to_string(i - seg_start));
code = concat(code, ")); ");
}
code = concat(code, "result = concat(result, any_to_string(args[");
code = concat(code, int_to_string(arg_idx));
code = concat(code, "])); ");
arg_idx += 1;
i += 2;
seg_start = i;
} else if fmt[i + 1] == 123 {
code = concat(code, "result = concat(result, substr(fmt, ");
code = concat(code, int_to_string(seg_start));
code = concat(code, ", ");
code = concat(code, int_to_string(i - seg_start + 1));
code = concat(code, ")); ");
i += 2;
seg_start = i;
} else {
i += 1;
}
} else {
i += 1;
}
} else if fmt[i] == 125 {
if i + 1 < fmt.len {
if fmt[i + 1] == 125 {
code = concat(code, "result = concat(result, substr(fmt, ");
code = concat(code, int_to_string(seg_start));
code = concat(code, ", ");
code = concat(code, int_to_string(i - seg_start + 1));
code = concat(code, ")); ");
i += 2;
seg_start = i;
} else {
i += 1;
}
} else {
i += 1;
}
} else {
i += 1;
}
}
if seg_start < fmt.len {
code = concat(code, "result = concat(result, substr(fmt, ");
code = concat(code, int_to_string(seg_start));
code = concat(code, ", ");
code = concat(code, int_to_string(fmt.len - seg_start));
code = concat(code, ")); ");
}
code = concat(code, "result;");
code;
}
format :: ($fmt: string, args: ..Any) -> string {
#insert build_format(fmt);
}
print :: ($fmt: string, args: ..Any) { print :: ($fmt: string, args: ..Any) {
#insert build_print(fmt); #insert build_print(fmt);
} }

View File

@@ -343,6 +343,27 @@ val := mp[2]; // 30
**Fat pointer layout**: `[:0]u8`, `string`, and `[]T` are `{ptr, i64}` structs. The raw pointer is always the first field at offset 0. This means `*[:0]u8` works as C's `char**` — a C function dereferences through the outer pointer and reads the raw `char*` from offset 0. **Fat pointer layout**: `[:0]u8`, `string`, and `[]T` are `{ptr, i64}` structs. The raw pointer is always the first field at offset 0. This means `*[:0]u8` works as C's `char**` — a C function dereferences through the outer pointer and reads the raw `char*` from offset 0.
### Foreign Function Interface (C Interop)
To call C functions, declare a library constant with `#library` and bind functions with `#foreign`:
```sx
// Declare a named library constant
libc :: #library "c";
sdl :: #library "SDL3";
// Bind foreign functions — library ref is required
socket :: (domain: s32, type: s32, protocol: s32) -> s32 #foreign libc;
SDL_Init :: (flags: u32) -> bool #foreign sdl;
// Symbol renaming — optional second argument gives the C symbol name
write_fd :: (fd: s32, buf: [*]u8, count: u64) -> s64 #foreign libc "write";
```
- `#library "name"` must be assigned to a named constant. The library is passed to the linker (`-lname` on Unix, `name.lib` on Windows).
- `#foreign lib_ref` declares a function as external C. The library reference is mandatory.
- `#foreign lib_ref "c_symbol"` renames the binding: the sx function name differs from the C symbol. This avoids name collisions (e.g. POSIX `write` vs an sx builtin).
### C Interop Type Mapping ### C Interop Type Mapping
| C type | sx type | Notes | | C type | sx type | Notes |
@@ -906,7 +927,7 @@ A variable declaration (`name :=`) inside an inner scope shadows any variable wi
Built-in functions are declared in `std.sx` with the `#builtin` suffix, which tells the compiler to generate the implementation internally rather than looking for a function body. Built-in functions are declared in `std.sx` with the `#builtin` suffix, which tells the compiler to generate the implementation internally rather than looking for a function body.
### I/O ### I/O
- `write(str: string) -> void` — write a string to standard output - `out(str: string) -> void` — write a string to standard output
- `print(fmt: string, args: ..Any)` — formatted print. Parses `{}` placeholders in the format string and substitutes arguments. When all argument types are statically known, the compiler specializes the call at compile time (no `Any` boxing). - `print(fmt: string, args: ..Any)` — formatted print. Parses `{}` placeholders in the format string and substitutes arguments. When all argument types are statically known, the compiler specializes the call at compile time (no `Any` boxing).
### Math ### Math

View File

@@ -61,7 +61,7 @@ pub const Node = struct {
continue_expr: void, continue_expr: void,
undef_literal: void, undef_literal: void,
builtin_expr: void, builtin_expr: void,
foreign_expr: void, foreign_expr: ForeignExpr,
library_decl: LibraryDecl, library_decl: LibraryDecl,
function_type_expr: FunctionTypeExpr, function_type_expr: FunctionTypeExpr,
@@ -368,8 +368,14 @@ pub const NamespaceDecl = struct {
decls: []const *Node, decls: []const *Node,
}; };
pub const ForeignExpr = struct {
library_ref: ?[]const u8 = null, // identifier name of library constant
c_name: ?[]const u8 = null, // C symbol name override
};
pub const LibraryDecl = struct { pub const LibraryDecl = struct {
lib_name: []const u8, lib_name: []const u8,
name: []const u8, // sx-side constant name
}; };
pub const FunctionTypeExpr = struct { pub const FunctionTypeExpr = struct {

View File

@@ -186,6 +186,10 @@ pub const CodeGen = struct {
foreign_libraries: std.ArrayList([]const u8), foreign_libraries: std.ArrayList([]const u8),
// Set of foreign function names (for ABI lowering at call sites) // Set of foreign function names (for ABI lowering at call sites)
foreign_fns: std.StringHashMap(void), foreign_fns: std.StringHashMap(void),
// Named library constants: sx name → lib filename (e.g. "libc" → "c")
library_constants: std.StringHashMap([]const u8),
// Foreign function rename map: sx name → C symbol name (e.g. "write_fd" → "write")
foreign_name_map: std.StringHashMap([]const u8),
// Global mutable variables (from top-level var_decl, e.g. function pointers loaded at runtime) // Global mutable variables (from top-level var_decl, e.g. function pointers loaded at runtime)
global_mutable_vars: std.StringHashMap(NamedValue), global_mutable_vars: std.StringHashMap(NamedValue),
// Declared return types for non-generic functions (preserves signedness lost by LLVM round-trip) // Declared return types for non-generic functions (preserves signedness lost by LLVM round-trip)
@@ -394,6 +398,8 @@ pub const CodeGen = struct {
.deferred_fn_bodies = std.ArrayList(DeferredFn).empty, .deferred_fn_bodies = std.ArrayList(DeferredFn).empty,
.foreign_libraries = std.ArrayList([]const u8).empty, .foreign_libraries = std.ArrayList([]const u8).empty,
.foreign_fns = std.StringHashMap(void).init(allocator), .foreign_fns = std.StringHashMap(void).init(allocator),
.library_constants = std.StringHashMap([]const u8).init(allocator),
.foreign_name_map = std.StringHashMap([]const u8).init(allocator),
.global_mutable_vars = std.StringHashMap(NamedValue).init(allocator), .global_mutable_vars = std.StringHashMap(NamedValue).init(allocator),
.function_return_types = std.StringHashMap(Type).init(allocator), .function_return_types = std.StringHashMap(Type).init(allocator),
.target_config = target_config, .target_config = target_config,
@@ -426,6 +432,8 @@ pub const CodeGen = struct {
self.deferred_fn_bodies.deinit(self.allocator); self.deferred_fn_bodies.deinit(self.allocator);
self.foreign_libraries.deinit(self.allocator); self.foreign_libraries.deinit(self.allocator);
self.foreign_fns.deinit(); self.foreign_fns.deinit();
self.library_constants.deinit();
self.foreign_name_map.deinit();
c.LLVMDisposeBuilder(self.builder); c.LLVMDisposeBuilder(self.builder);
if (self.target_machine) |tm| c.LLVMDisposeTargetMachine(tm); if (self.target_machine) |tm| c.LLVMDisposeTargetMachine(tm);
if (self.module_owned) { if (self.module_owned) {
@@ -1038,6 +1046,27 @@ pub const CodeGen = struct {
// Initialize built-in function declarations (printf, etc.) // Initialize built-in function declarations (printf, etc.)
self.builtins = Builtins.init(self.module, self.context); self.builtins = Builtins.init(self.module, self.context);
// Pre-scan: collect named library constants (handles forward references)
for (root.data.root.decls) |decl| {
switch (decl.data) {
.library_decl => |ld| {
try self.library_constants.put(ld.name, ld.lib_name);
},
.namespace_decl => |ns| {
for (ns.decls) |nd| {
switch (nd.data) {
.library_decl => |nld| {
const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, nld.name });
try self.library_constants.put(qualified, nld.lib_name);
},
else => {},
}
}
},
else => {},
}
}
// Pass 1: Register all declarations (signatures only, no bodies) // Pass 1: Register all declarations (signatures only, no bodies)
for (root.data.root.decls) |decl| { for (root.data.root.decls) |decl| {
switch (decl.data) { switch (decl.data) {
@@ -1904,11 +1933,34 @@ pub const CodeGen = struct {
fn registerFnDecl(self: *CodeGen, fd: ast.FnDecl, llvm_name: []const u8) !void { fn registerFnDecl(self: *CodeGen, fd: ast.FnDecl, llvm_name: []const u8) !void {
const is_foreign = fd.body.data == .foreign_expr; const is_foreign = fd.body.data == .foreign_expr;
// For foreign functions: resolve C symbol name (rename) and validate library ref
const actual_llvm_name = if (is_foreign) blk: {
const fe = fd.body.data.foreign_expr;
// Validate library reference
if (fe.library_ref) |lib_ref| {
if (!self.library_constants.contains(lib_ref)) {
return self.emitErrorFmt("unknown library '{s}' in #foreign", .{lib_ref});
}
}
// Use C symbol name if provided, otherwise use the sx name
const c_name = fe.c_name orelse llvm_name;
// Track rename mapping for call resolution
if (fe.c_name != null and !std.mem.eql(u8, c_name, llvm_name)) {
try self.foreign_name_map.put(llvm_name, c_name);
}
break :blk c_name;
} else llvm_name;
const fn_type = try self.buildFnType(fd.params, fd.return_type, fd.name, is_foreign); const fn_type = try self.buildFnType(fd.params, fd.return_type, fd.name, is_foreign);
const name_z = try self.allocator.dupeZ(u8, llvm_name); const name_z = try self.allocator.dupeZ(u8, actual_llvm_name);
_ = c.LLVMAddFunction(self.module, name_z.ptr, fn_type); _ = c.LLVMAddFunction(self.module, name_z.ptr, fn_type);
// Track foreign functions for ABI lowering at call sites // Track foreign functions for ABI lowering at call sites (use sx name for call-site lookup)
if (is_foreign) try self.foreign_fns.put(llvm_name, {}); if (is_foreign) {
try self.foreign_fns.put(llvm_name, {});
// Also track under the C name for direct lookups
if (!std.mem.eql(u8, actual_llvm_name, llvm_name)) {
try self.foreign_fns.put(actual_llvm_name, {});
}
}
// Track resolved parameter types for accurate call-site conversion // Track resolved parameter types for accurate call-site conversion
var param_types = std.ArrayList(Type).empty; var param_types = std.ArrayList(Type).empty;
for (fd.params) |param| { for (fd.params) |param| {
@@ -1949,10 +2001,17 @@ pub const CodeGen = struct {
} }
const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, fd.name }); const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, fd.name });
if (fd.body.data == .foreign_expr) { if (fd.body.data == .foreign_expr) {
// External C function in namespace — register LLVM declaration with C name only // External C function in namespace — register LLVM declaration
try self.registerFnDecl(fd, fd.name); try self.registerFnDecl(fd, fd.name);
// Also track qualified name as foreign for ABI lowering at call sites // Also track qualified name as foreign for ABI lowering at call sites
try self.foreign_fns.put(qualified, {}); try self.foreign_fns.put(qualified, {});
// Track qualified rename mapping if C name differs
const fe = fd.body.data.foreign_expr;
if (fe.c_name) |c_name| {
if (!std.mem.eql(u8, c_name, fd.name)) {
try self.foreign_name_map.put(qualified, c_name);
}
}
// Store param types under qualified name so call-site type resolution works // Store param types under qualified name so call-site type resolution works
var param_types = std.ArrayList(Type).empty; var param_types = std.ArrayList(Type).empty;
for (fd.params) |param| { for (fd.params) |param| {
@@ -2054,8 +2113,16 @@ pub const CodeGen = struct {
if (expr.data == .call) { if (expr.data == .call) {
if (self.resolveCalleeName(expr.data.call)) |callee_name| { if (self.resolveCalleeName(expr.data.call)) |callee_name| {
var cnbuf: [256]u8 = undefined; var cnbuf: [256]u8 = undefined;
const callee_fn = c.LLVMGetNamedFunction(self.module, self.nameToCStr(callee_name, &cnbuf)) orelse return Type.s(64); var callee_fn = c.LLVMGetNamedFunction(self.module, self.nameToCStr(callee_name, &cnbuf));
const fn_type = c.LLVMGlobalGetValueType(callee_fn); // Foreign rename fallback
if (callee_fn == null) {
if (self.foreign_name_map.get(callee_name)) |c_name| {
var rbuf: [256]u8 = undefined;
callee_fn = c.LLVMGetNamedFunction(self.module, self.nameToCStr(c_name, &rbuf));
}
}
const resolved_fn = callee_fn orelse return Type.s(64);
const fn_type = c.LLVMGlobalGetValueType(resolved_fn);
const ret_llvm = c.LLVMGetReturnType(fn_type); const ret_llvm = c.LLVMGetReturnType(fn_type);
return self.llvmTypeToSxType(ret_llvm); return self.llvmTypeToSxType(ret_llvm);
} }
@@ -3160,6 +3227,13 @@ pub const CodeGen = struct {
fn_val = c.LLVMGetNamedFunction(self.module, self.nameToCStr(qualified, &qbuf)); fn_val = c.LLVMGetNamedFunction(self.module, self.nameToCStr(qualified, &qbuf));
} }
} }
// Foreign rename fallback: sx name → C symbol name
if (fn_val == null) {
if (self.foreign_name_map.get(ident.name)) |c_name| {
var rbuf: [256]u8 = undefined;
fn_val = c.LLVMGetNamedFunction(self.module, self.nameToCStr(c_name, &rbuf));
}
}
if (fn_val != null) return fn_val.?; if (fn_val != null) return fn_val.?;
} }
return self.emitErrorFmt("undefined identifier '{s}'", .{ident.name}); return self.emitErrorFmt("undefined identifier '{s}'", .{ident.name});
@@ -5386,6 +5460,13 @@ pub const CodeGen = struct {
callee_fn = c.LLVMGetNamedFunction(self.module, self.nameToCStr(qualified2, &qbuf)); callee_fn = c.LLVMGetNamedFunction(self.module, self.nameToCStr(qualified2, &qbuf));
} }
} }
// Foreign rename fallback: sx name → C symbol name (e.g. "write_fd" → "write")
if (callee_fn == null) {
if (self.foreign_name_map.get(callee_name)) |c_name| {
var rbuf: [256]u8 = undefined;
callee_fn = c.LLVMGetNamedFunction(self.module, self.nameToCStr(c_name, &rbuf));
}
}
// Function pointer indirect call: callee is a variable with function_type // Function pointer indirect call: callee is a variable with function_type
if (callee_fn == null) { if (callee_fn == null) {
if (self.lookupValue(callee_name)) |v| { if (self.lookupValue(callee_name)) |v| {
@@ -6795,7 +6876,7 @@ pub const CodeGen = struct {
fn dispatchBuiltin(self: *CodeGen, name: []const u8, call_node: ast.Call) !c.LLVMValueRef { fn dispatchBuiltin(self: *CodeGen, name: []const u8, call_node: ast.Call) !c.LLVMValueRef {
// Extract base name (strip namespace prefix) // Extract base name (strip namespace prefix)
const base = baseName(name); const base = baseName(name);
if (std.mem.eql(u8, base, "write")) return self.genWriteCall(call_node.args); if (std.mem.eql(u8, base, "out")) return self.genOutCall(call_node.args);
if (std.mem.eql(u8, base, "sqrt")) return self.genMathIntrinsic(call_node, "sqrt"); if (std.mem.eql(u8, base, "sqrt")) return self.genMathIntrinsic(call_node, "sqrt");
if (std.mem.eql(u8, base, "sin")) return self.genMathIntrinsic(call_node, "sin"); if (std.mem.eql(u8, base, "sin")) return self.genMathIntrinsic(call_node, "sin");
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");
@@ -6816,8 +6897,8 @@ pub const CodeGen = struct {
return self.emitErrorFmt("unknown builtin function '{s}'", .{name}); return self.emitErrorFmt("unknown builtin function '{s}'", .{name});
} }
fn genWriteCall(self: *CodeGen, args: []const *Node) !c.LLVMValueRef { fn genOutCall(self: *CodeGen, args: []const *Node) !c.LLVMValueRef {
if (args.len != 1) return self.emitError("write expects exactly 1 argument"); if (args.len != 1) return self.emitError("out expects exactly 1 argument");
const builtins = try self.requireBuiltins(); const builtins = try self.requireBuiltins();
const val = try self.genExpr(args[0]); const val = try self.genExpr(args[0]);
// Extract ptr and len from string slice // Extract ptr and len from string slice
@@ -7311,7 +7392,7 @@ pub const CodeGen = struct {
} }
defer _ = c.LLVMOrcDisposeLLJIT(jit); defer _ = c.LLVMOrcDisposeLLJIT(jit);
// Add process symbols so JIT can find libc (printf, write, etc.) // Add process symbols so JIT can find libc (printf, etc.)
const jd = c.LLVMOrcLLJITGetMainJITDylib(jit); const jd = c.LLVMOrcLLJITGetMainJITDylib(jit);
const prefix = c.LLVMOrcLLJITGetGlobalPrefix(jit); const prefix = c.LLVMOrcLLJITGetGlobalPrefix(jit);
var gen: c.LLVMOrcDefinitionGeneratorRef = null; var gen: c.LLVMOrcDefinitionGeneratorRef = null;

View File

@@ -217,7 +217,7 @@ pub const Instruction = union(enum) {
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, write, sqrt, size_of, cast, alloc }; pub const BuiltinId = enum { print, out, sqrt, size_of, cast, alloc };
/// 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 {
@@ -1354,8 +1354,8 @@ pub const VM = struct {
fn callBuiltin(self: *VM, id: BuiltinId, arg_count: u8) !void { fn callBuiltin(self: *VM, id: BuiltinId, arg_count: u8) !void {
switch (id) { switch (id) {
.write => { .out => {
// write(str) — raw string output // out(str) — raw string output
if (arg_count >= 1) { if (arg_count >= 1) {
const val = try self.pop(); const val = try self.pop();
const str = try val.format(self.allocator); const str = try val.format(self.allocator);

View File

@@ -879,7 +879,7 @@ pub const Server = struct {
.{ .name = "alloc", .label = "alloc(size: s32) -> string", .params = &.{"size: s32"} }, .{ .name = "alloc", .label = "alloc(size: s32) -> string", .params = &.{"size: s32"} },
.{ .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 = "write", .label = "write(str: string) -> void", .params = &.{"str: string"} }, .{ .name = "out", .label = "out(str: string) -> void", .params = &.{"str: string"} },
}; };
for (&builtin_sigs) |b| { for (&builtin_sigs) |b| {
const matches = std.mem.eql(u8, call_ctx.name, b.name) or const matches = std.mem.eql(u8, call_ctx.name, b.name) or

View File

@@ -62,19 +62,6 @@ pub const Parser = struct {
return try self.createNode(start, .{ .import_decl = .{ .path = path, .name = null } }); return try self.createNode(start, .{ .import_decl = .{ .path = path, .name = null } });
} }
// Top-level #library directive: #library "libname";
if (self.current.tag == .hash_library) {
self.advance();
if (self.current.tag != .string_literal) {
return self.fail("expected string after '#library'");
}
const raw = self.tokenSlice(self.current);
const lib_name = raw[1 .. raw.len - 1];
self.advance();
try self.expect(.semicolon);
return try self.createNode(start, .{ .library_decl = .{ .lib_name = lib_name } });
}
// Top-level #run directive // Top-level #run directive
if (self.current.tag == .hash_run) { if (self.current.tag == .hash_run) {
self.advance(); self.advance();
@@ -131,6 +118,19 @@ pub const Parser = struct {
return try self.createNode(start_pos, .{ .import_decl = .{ .path = path, .name = name } }); return try self.createNode(start_pos, .{ .import_decl = .{ .path = path, .name = name } });
} }
// Named library: name :: #library "libname";
if (self.current.tag == .hash_library) {
self.advance();
if (self.current.tag != .string_literal) {
return self.fail("expected string after '#library'");
}
const raw = self.tokenSlice(self.current);
const lib_name = raw[1 .. raw.len - 1];
self.advance();
try self.expect(.semicolon);
return try self.createNode(start_pos, .{ .library_decl = .{ .lib_name = lib_name, .name = name } });
}
// Compile-time evaluation: name :: #run expr; // Compile-time evaluation: name :: #run expr;
if (self.current.tag == .hash_run) { if (self.current.tag == .hash_run) {
const run_start = self.current.loc.start; const run_start = self.current.loc.start;
@@ -192,12 +192,27 @@ pub const Parser = struct {
return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = value, .value = bi } }); return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = value, .value = bi } });
} }
// name :: type_expr #foreign; — foreign with type annotation // name :: type_expr #foreign lib ["c_name"]; — foreign with type annotation
if (self.current.tag == .hash_foreign) { if (self.current.tag == .hash_foreign) {
const fi_start = self.current.loc.start; const fi_start = self.current.loc.start;
self.advance(); self.advance();
// Required: library reference (identifier)
if (self.current.tag != .identifier)
return self.fail("expected library name after '#foreign'");
const lib_ref = self.tokenSlice(self.current);
self.advance();
// Optional: C symbol name (string literal)
var c_name: ?[]const u8 = null;
if (self.current.tag == .string_literal) {
const raw = self.tokenSlice(self.current);
c_name = raw[1 .. raw.len - 1];
self.advance();
}
try self.expect(.semicolon); try self.expect(.semicolon);
const fi = try self.createNode(fi_start, .{ .foreign_expr = {} }); const fi = try self.createNode(fi_start, .{ .foreign_expr = .{
.library_ref = lib_ref,
.c_name = c_name,
} });
return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = value, .value = fi } }); return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = value, .value = fi } });
} }
@@ -754,8 +769,23 @@ pub const Parser = struct {
} else if (self.current.tag == .hash_foreign) blk: { } else if (self.current.tag == .hash_foreign) blk: {
const fi_start = self.current.loc.start; const fi_start = self.current.loc.start;
self.advance(); self.advance();
// Required: library reference (identifier)
if (self.current.tag != .identifier)
return self.fail("expected library name after '#foreign'");
const lib_ref = self.tokenSlice(self.current);
self.advance();
// Optional: C symbol name (string literal)
var c_name: ?[]const u8 = null;
if (self.current.tag == .string_literal) {
const raw = self.tokenSlice(self.current);
c_name = raw[1 .. raw.len - 1];
self.advance();
}
try self.expect(.semicolon); try self.expect(.semicolon);
break :blk try self.createNode(fi_start, .{ .foreign_expr = {} }); break :blk try self.createNode(fi_start, .{ .foreign_expr = .{
.library_ref = lib_ref,
.c_name = c_name,
} });
} else if (self.current.tag == .fat_arrow) blk: { } else if (self.current.tag == .fat_arrow) blk: {
is_arrow = true; is_arrow = true;
self.advance(); self.advance();
@@ -1826,7 +1856,7 @@ test "parse namespaced import" {
} }
test "parse library declaration" { test "parse library declaration" {
const source = "#library \"raylib\";"; const source = "rl :: #library \"raylib\";";
var arena = std.heap.ArenaAllocator.init(std.testing.allocator); var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit(); defer arena.deinit();
var parser = Parser.init(arena.allocator(), source); var parser = Parser.init(arena.allocator(), source);
@@ -1835,6 +1865,7 @@ test "parse library declaration" {
const decl = root.data.root.decls[0]; const decl = root.data.root.decls[0];
try std.testing.expect(decl.data == .library_decl); try std.testing.expect(decl.data == .library_decl);
try std.testing.expectEqualStrings("raylib", decl.data.library_decl.lib_name); try std.testing.expectEqualStrings("raylib", decl.data.library_decl.lib_name);
try std.testing.expectEqualStrings("rl", decl.data.library_decl.name);
} }
test "parse void function with builtin body" { test "parse void function with builtin body" {
@@ -1851,7 +1882,7 @@ test "parse void function with builtin body" {
} }
test "parse void function with foreign body" { test "parse void function with foreign body" {
const source = "InitWindow :: (width: s32, height: s32, title: *u8) -> void #foreign;"; const source = "InitWindow :: (width: s32, height: s32, title: *u8) -> void #foreign rl;";
var arena = std.heap.ArenaAllocator.init(std.testing.allocator); var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit(); defer arena.deinit();
var parser = Parser.init(arena.allocator(), source); var parser = Parser.init(arena.allocator(), source);
@@ -1861,6 +1892,7 @@ test "parse void function with foreign body" {
try std.testing.expect(decl.data == .fn_decl); try std.testing.expect(decl.data == .fn_decl);
try std.testing.expectEqualStrings("InitWindow", decl.data.fn_decl.name); try std.testing.expectEqualStrings("InitWindow", decl.data.fn_decl.name);
try std.testing.expect(decl.data.fn_decl.body.data == .foreign_expr); try std.testing.expect(decl.data.fn_decl.body.data == .foreign_expr);
try std.testing.expectEqualStrings("rl", decl.data.fn_decl.body.data.foreign_expr.library_ref.?);
try std.testing.expectEqual(@as(usize, 3), decl.data.fn_decl.params.len); try std.testing.expectEqual(@as(usize, 3), decl.data.fn_decl.params.len);
} }

View File

@@ -195,7 +195,7 @@ outer-defer
defer-in-if: body defer-in-if: body
defer-in-if: deferred defer-in-if: deferred
=== 7. Builtins === === 7. Builtins ===
write-ok out-ok
sqrt: 3.000000 sqrt: 3.000000
sqrt-f64: 4.000000 sqrt-f64: 4.000000
sizeof-s32: 4 sizeof-s32: 4
@@ -240,4 +240,6 @@ flags-explicit-raw: 68
var swap: 20 10 var swap: 20 10
arr swap: 3 1 arr swap: 3 1
3-way: 3 1 2 3-way: 3 1 2
=== 15. Foreign ===
foreign-rename: 42
=== DONE === === DONE ===