diff --git a/editors/vscode/package.json b/editors/vscode/package.json index 224e88f..39f789d 100644 --- a/editors/vscode/package.json +++ b/editors/vscode/package.json @@ -10,6 +10,9 @@ "categories": [ "Programming Languages" ], + "activationEvents": [ + "onLanguage:sx" + ], "main": "./out/extension.js", "contributes": { "languages": [ diff --git a/editors/vscode/sx-lang-0.0.1.vsix b/editors/vscode/sx-lang-0.0.1.vsix index 9a01694..a8406ee 100644 Binary files a/editors/vscode/sx-lang-0.0.1.vsix and b/editors/vscode/sx-lang-0.0.1.vsix differ diff --git a/examples/19-varargs.sx b/examples/19-varargs.sx index 02fedfe..d92e244 100644 --- a/examples/19-varargs.sx +++ b/examples/19-varargs.sx @@ -10,26 +10,26 @@ sum :: (args: ..s32) -> s32 { print_all :: (args: ..s32) { for args: (it) { - write(int_to_string(it)); - write(" "); + out(int_to_string(it)); + out(" "); } - write("\n"); + out("\n"); } main :: () -> s32 { - write(int_to_string(sum(10, 20, 30))); - write("\n"); + out(int_to_string(sum(10, 20, 30))); + out("\n"); print_all(1, 2, 3, 4, 5); arr : [3]s32 = .[10, 20, 30]; - write(int_to_string(sum(..arr))); - write("\n"); + out(int_to_string(sum(..arr))); + out("\n"); for arr: (it) { - write(int_to_string(it)); - write(" "); + out(int_to_string(it)); + out(" "); } - write("\n"); + out("\n"); 0; } diff --git a/examples/20-any-varargs.sx b/examples/20-any-varargs.sx index c62da65..dd6ac35 100644 --- a/examples/20-any-varargs.sx +++ b/examples/20-any-varargs.sx @@ -10,22 +10,22 @@ print_any :: (args: ..Any) { for args: (it) { type := type_of(it); if type == { - case int: write(int_to_string(cast(s32) it)); - case string: write(cast(string) it); - case bool: write(bool_to_string(cast(bool) it)); - case float: write(float_to_string(cast(f64) it)); + case int: out(int_to_string(cast(s32) it)); + case string: out(cast(string) it); + case bool: out(bool_to_string(cast(bool) it)); + case float: out(float_to_string(cast(f64) it)); case Point: { p := cast(Point) it; - write("("); - write(int_to_string(p.x)); - write(","); - write(int_to_string(p.y)); - write(")"); + out("("); + out(int_to_string(p.x)); + out(","); + out(int_to_string(p.y)); + out(")"); } } - write(" "); + out(" "); } - write("\n"); + out("\n"); } count :: (args: ..Any) -> s32 { @@ -40,8 +40,8 @@ main :: () -> s32 { print_any("point:", p, 99); // Test count - write(int_to_string(count(1, 2, 3))); - write("\n"); + out(int_to_string(count(1, 2, 3))); + out("\n"); 0; } diff --git a/examples/32-http-server.sx b/examples/32-http-server.sx new file mode 100644 index 0000000..c77dc00 --- /dev/null +++ b/examples/32-http-server.sx @@ -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 := "

Hello from sx!

"; + 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; +} diff --git a/examples/50-smoke.sx b/examples/50-smoke.sx index a45fb30..8d41f14 100644 --- a/examples/50-smoke.sx +++ b/examples/50-smoke.sx @@ -91,6 +91,10 @@ gen_val :: () -> string { return "print(\"insert-gen: {}\\n\", 42);"; } +// --- Foreign function binding --- +libc :: #library "c"; +c_abs :: (n: s32) -> s32 #foreign libc "abs"; + // ============================================================ main :: { @@ -634,76 +638,76 @@ END; // For loop basic farr : [4]s32 = .[10, 20, 30, 40]; - write("for:"); + out("for:"); for farr: (it) { - write(" "); - write(int_to_string(it)); + out(" "); + out(int_to_string(it)); } - write("\n"); + out("\n"); // For with print - write("for-print:"); + out("for-print:"); for farr: (it) { print(" {}", it); } - write("\n"); + out("\n"); // For with index - write("for-idx:"); + out("for-idx:"); for farr: (_, ix) { - write(" "); - write(int_to_string(ix)); + out(" "); + out(int_to_string(ix)); } - write("\n"); + out("\n"); // For with print two args - write("for-2arg:"); + out("for-2arg:"); for farr: (it, ix) { print(" {}@{}", it, ix); } - write("\n"); + out("\n"); // For with break - write("for-break:"); + out("for-break:"); for farr: (it) { if it == 30 { break; } print(" {}", it); } - write("\n"); + out("\n"); // For with continue - write("for-continue:"); + out("for-continue:"); for farr: (it) { if it == 20 { continue; } print(" {}", it); } - write("\n"); + out("\n"); // For on slice fsl : []s32 = .[10, 20, 30]; - write("for-slice:"); + out("for-slice:"); for fsl: (it) { print(" {}", it); } - write("\n"); + out("\n"); // For on slice with index - write("for-slice-idx:"); + out("for-slice-idx:"); for fsl: (it, ix) { print(" {}:{}", ix, it); } - write("\n"); + out("\n"); // Nested for nf_a : [2]s32 = .[0, 1]; nf_b : [2]s32 = .[0, 1]; - write("for-nested:"); + out("for-nested:"); for nf_a: (oa) { for nf_b: (ob) { print(" ({},{})", oa, ob); } } - write("\n"); + out("\n"); // For with break preserving index fbi : [5]s32 = .[10, 20, 30, 40, 50]; @@ -857,8 +861,8 @@ END; // ======================================================== print("=== 7. Builtins ===\n"); - // write - write("write-ok\n"); + // out + out("out-ok\n"); // sqrt print("sqrt: {}\n", sqrt(9.0)); @@ -932,12 +936,12 @@ END; // field_value (use any_to_string to avoid sext-on-Any bug) fv_pt := Point.{ 11, 22 }; - write("fieldval0: "); - write(any_to_string(field_value(fv_pt, 0))); - write("\n"); - write("fieldval1: "); - write(any_to_string(field_value(fv_pt, 1))); - write("\n"); + out("fieldval0: "); + out(any_to_string(field_value(fv_pt, 0))); + out("\n"); + out("fieldval1: "); + out(any_to_string(field_value(fv_pt, 1))); + out("\n"); // field_index on plain enum fi_c : Color = .green; @@ -1039,5 +1043,13 @@ END; 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"); } diff --git a/examples/modules/raylib.sx b/examples/modules/raylib.sx index 3cc838c..80e0d3d 100644 --- a/examples/modules/raylib.sx +++ b/examples/modules/raylib.sx @@ -1,4 +1,4 @@ -#library "raylib"; +raylib :: #library "raylib"; Color :: struct { r, g, b, a: u8; @@ -8,10 +8,10 @@ Vector2 :: struct { x, y: f32; } -InitWindow :: (width: s32, height: s32, title: [:0]u8) -> void #foreign; -CloseWindow :: () -> void #foreign; -WindowShouldClose :: () -> bool #foreign; -BeginDrawing :: () -> void #foreign; -EndDrawing :: () -> void #foreign; -ClearBackground :: (color: Color) -> void #foreign; -DrawTriangle :: (v1: Vector2, v2: Vector2, v3: Vector2, color: Color) -> void #foreign; +InitWindow :: (width: s32, height: s32, title: [:0]u8) -> void #foreign raylib; +CloseWindow :: () -> void #foreign raylib; +WindowShouldClose :: () -> bool #foreign raylib; +BeginDrawing :: () -> void #foreign raylib; +EndDrawing :: () -> void #foreign raylib; +ClearBackground :: (color: Color) -> void #foreign raylib; +DrawTriangle :: (v1: Vector2, v2: Vector2, v3: Vector2, color: Color) -> void #foreign raylib; diff --git a/examples/modules/sdl3.sx b/examples/modules/sdl3.sx index 1e261c3..77c0465 100644 --- a/examples/modules/sdl3.sx +++ b/examples/modules/sdl3.sx @@ -1,4 +1,4 @@ -#library "SDL3"; +sdl3 :: #library "SDL3"; // SDL_InitFlags SDL_INIT_VIDEO :u32: 0x20; @@ -317,17 +317,17 @@ SDL_Event :: enum struct { tag: u32; _: u32; payload: [30]u32; } { } // Functions -SDL_Init :: (flags: u32) -> bool #foreign; -SDL_Quit :: () -> void #foreign; -SDL_CreateWindow :: (title: [:0]u8, w: s32, h: s32, flags: u64) -> *void #foreign; -SDL_DestroyWindow :: (window: *void) -> void #foreign; -SDL_GL_SetAttribute :: (attr: s32, value: s32) -> bool #foreign; -SDL_GL_CreateContext :: (window: *void) -> *void #foreign; -SDL_GL_DestroyContext :: (context: *void) -> bool #foreign; -SDL_GL_MakeCurrent :: (window: *void, context: *void) -> bool #foreign; -SDL_GL_SwapWindow :: (window: *void) -> bool #foreign; -SDL_GL_SetSwapInterval :: (interval: s32) -> bool #foreign; -SDL_GL_GetProcAddress :: (proc: [:0]u8) -> *void #foreign; -SDL_PollEvent :: (event: *SDL_Event) -> bool #foreign; -SDL_GetTicks :: () -> u64 #foreign; -SDL_Delay :: (ms: u32) -> void #foreign; +SDL_Init :: (flags: u32) -> bool #foreign sdl3; +SDL_Quit :: () -> void #foreign sdl3; +SDL_CreateWindow :: (title: [:0]u8, w: s32, h: s32, flags: u64) -> *void #foreign sdl3; +SDL_DestroyWindow :: (window: *void) -> void #foreign sdl3; +SDL_GL_SetAttribute :: (attr: s32, value: s32) -> bool #foreign sdl3; +SDL_GL_CreateContext :: (window: *void) -> *void #foreign sdl3; +SDL_GL_DestroyContext :: (context: *void) -> bool #foreign sdl3; +SDL_GL_MakeCurrent :: (window: *void, context: *void) -> bool #foreign sdl3; +SDL_GL_SwapWindow :: (window: *void) -> bool #foreign sdl3; +SDL_GL_SetSwapInterval :: (interval: s32) -> bool #foreign sdl3; +SDL_GL_GetProcAddress :: (proc: [:0]u8) -> *void #foreign sdl3; +SDL_PollEvent :: (event: *SDL_Event) -> bool #foreign sdl3; +SDL_GetTicks :: () -> u64 #foreign sdl3; +SDL_Delay :: (ms: u32) -> void #foreign sdl3; diff --git a/examples/modules/socket.sx b/examples/modules/socket.sx new file mode 100644 index 0000000..fcacaae --- /dev/null +++ b/examples/modules/socket.sx @@ -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)); +} diff --git a/examples/modules/std.sx b/examples/modules/std.sx index 7927fd3..f4d3df2 100644 --- a/examples/modules/std.sx +++ b/examples/modules/std.sx @@ -1,5 +1,5 @@ Vector :: ($N: int, $T: Type) -> Type #builtin; -write :: (str: string) -> void #builtin; +out :: (str: string) -> void #builtin; sqrt :: (x: $T) -> T #builtin; sin :: (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, ")); "); } - code = concat(code, "write(result);"); + code = concat(code, "out(result);"); 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) { #insert build_print(fmt); } diff --git a/specs.md b/specs.md index abf3da9..0f753c1 100644 --- a/specs.md +++ b/specs.md @@ -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. +### 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 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. ### 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). ### Math diff --git a/src/ast.zig b/src/ast.zig index bd42a71..682b990 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -61,7 +61,7 @@ pub const Node = struct { continue_expr: void, undef_literal: void, builtin_expr: void, - foreign_expr: void, + foreign_expr: ForeignExpr, library_decl: LibraryDecl, function_type_expr: FunctionTypeExpr, @@ -368,8 +368,14 @@ pub const NamespaceDecl = struct { 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 { lib_name: []const u8, + name: []const u8, // sx-side constant name }; pub const FunctionTypeExpr = struct { diff --git a/src/codegen.zig b/src/codegen.zig index 1e33d97..b8716f9 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -186,6 +186,10 @@ pub const CodeGen = struct { foreign_libraries: std.ArrayList([]const u8), // Set of foreign function names (for ABI lowering at call sites) 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_vars: std.StringHashMap(NamedValue), // 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, .foreign_libraries = std.ArrayList([]const u8).empty, .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), .function_return_types = std.StringHashMap(Type).init(allocator), .target_config = target_config, @@ -426,6 +432,8 @@ pub const CodeGen = struct { self.deferred_fn_bodies.deinit(self.allocator); self.foreign_libraries.deinit(self.allocator); self.foreign_fns.deinit(); + self.library_constants.deinit(); + self.foreign_name_map.deinit(); c.LLVMDisposeBuilder(self.builder); if (self.target_machine) |tm| c.LLVMDisposeTargetMachine(tm); if (self.module_owned) { @@ -1038,6 +1046,27 @@ pub const CodeGen = struct { // Initialize built-in function declarations (printf, etc.) 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) for (root.data.root.decls) |decl| { switch (decl.data) { @@ -1904,11 +1933,34 @@ pub const CodeGen = struct { fn registerFnDecl(self: *CodeGen, fd: ast.FnDecl, llvm_name: []const u8) !void { 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 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); - // Track foreign functions for ABI lowering at call sites - if (is_foreign) try self.foreign_fns.put(llvm_name, {}); + // 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, {}); + // 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 var param_types = std.ArrayList(Type).empty; 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 }); 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); // Also track qualified name as foreign for ABI lowering at call sites 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 var param_types = std.ArrayList(Type).empty; for (fd.params) |param| { @@ -2054,8 +2113,16 @@ pub const CodeGen = struct { if (expr.data == .call) { if (self.resolveCalleeName(expr.data.call)) |callee_name| { var cnbuf: [256]u8 = undefined; - const callee_fn = c.LLVMGetNamedFunction(self.module, self.nameToCStr(callee_name, &cnbuf)) orelse return Type.s(64); - const fn_type = c.LLVMGlobalGetValueType(callee_fn); + var callee_fn = c.LLVMGetNamedFunction(self.module, self.nameToCStr(callee_name, &cnbuf)); + // 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); return self.llvmTypeToSxType(ret_llvm); } @@ -3160,6 +3227,13 @@ pub const CodeGen = struct { 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.?; } 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)); } } + // 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 if (callee_fn == null) { 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 { // Extract base name (strip namespace prefix) 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, "sin")) return self.genMathIntrinsic(call_node, "sin"); 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}); } - fn genWriteCall(self: *CodeGen, args: []const *Node) !c.LLVMValueRef { - if (args.len != 1) return self.emitError("write expects exactly 1 argument"); + fn genOutCall(self: *CodeGen, args: []const *Node) !c.LLVMValueRef { + if (args.len != 1) return self.emitError("out expects exactly 1 argument"); const builtins = try self.requireBuiltins(); const val = try self.genExpr(args[0]); // Extract ptr and len from string slice @@ -7311,7 +7392,7 @@ pub const CodeGen = struct { } 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 prefix = c.LLVMOrcLLJITGetGlobalPrefix(jit); var gen: c.LLVMOrcDefinitionGeneratorRef = null; diff --git a/src/comptime.zig b/src/comptime.zig index bb18d7a..48dc6df 100644 --- a/src/comptime.zig +++ b/src/comptime.zig @@ -217,7 +217,7 @@ pub const Instruction = union(enum) { 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. pub const Chunk = struct { @@ -1354,8 +1354,8 @@ pub const VM = struct { fn callBuiltin(self: *VM, id: BuiltinId, arg_count: u8) !void { switch (id) { - .write => { - // write(str) — raw string output + .out => { + // out(str) — raw string output if (arg_count >= 1) { const val = try self.pop(); const str = try val.format(self.allocator); diff --git a/src/lsp/server.zig b/src/lsp/server.zig index 9dadc12..f35f204 100644 --- a/src/lsp/server.zig +++ b/src/lsp/server.zig @@ -879,7 +879,7 @@ pub const Server = struct { .{ .name = "alloc", .label = "alloc(size: s32) -> string", .params = &.{"size: s32"} }, .{ .name = "sqrt", .label = "sqrt(x: $T) -> T", .params = &.{"x: $T"} }, .{ .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| { const matches = std.mem.eql(u8, call_ctx.name, b.name) or diff --git a/src/parser.zig b/src/parser.zig index 27f9510..0663784 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -62,19 +62,6 @@ pub const Parser = struct { 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 if (self.current.tag == .hash_run) { self.advance(); @@ -131,6 +118,19 @@ pub const Parser = struct { 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; if (self.current.tag == .hash_run) { 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 } }); } - // 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) { const fi_start = self.current.loc.start; 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); - 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 } }); } @@ -754,8 +769,23 @@ pub const Parser = struct { } else if (self.current.tag == .hash_foreign) blk: { const fi_start = self.current.loc.start; 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); - 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: { is_arrow = true; self.advance(); @@ -1826,7 +1856,7 @@ test "parse namespaced import" { } test "parse library declaration" { - const source = "#library \"raylib\";"; + const source = "rl :: #library \"raylib\";"; var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); var parser = Parser.init(arena.allocator(), source); @@ -1835,6 +1865,7 @@ test "parse library declaration" { const decl = root.data.root.decls[0]; try std.testing.expect(decl.data == .library_decl); 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" { @@ -1851,7 +1882,7 @@ test "parse void function with builtin 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); defer arena.deinit(); 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.expectEqualStrings("InitWindow", decl.data.fn_decl.name); 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); } diff --git a/tests/expected/50-smoke.txt b/tests/expected/50-smoke.txt index dd5ee13..0b1b62e 100644 --- a/tests/expected/50-smoke.txt +++ b/tests/expected/50-smoke.txt @@ -195,7 +195,7 @@ outer-defer defer-in-if: body defer-in-if: deferred === 7. Builtins === -write-ok +out-ok sqrt: 3.000000 sqrt-f64: 4.000000 sizeof-s32: 4 @@ -240,4 +240,6 @@ flags-explicit-raw: 68 var swap: 20 10 arr swap: 3 1 3-way: 3 1 2 +=== 15. Foreign === +foreign-rename: 42 === DONE ===