http server
This commit is contained in:
@@ -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 {
|
||||
|
||||
101
src/codegen.zig
101
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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user