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

@@ -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 {

View File

@@ -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;

View File

@@ -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);

View File

@@ -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

View File

@@ -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);
}