comptime format

This commit is contained in:
agra
2026-02-18 18:57:51 +02:00
parent 383f09a305
commit fbf8a62362
5 changed files with 435 additions and 29 deletions

View File

@@ -131,6 +131,8 @@ pub const CodeGen = struct {
scope_stack: std.ArrayList(Scope),
// Compile-time globals: maps name to global variable info for #run results
comptime_globals: std.StringHashMap(ComptimeGlobal),
// Local compile-time constant values (for :: decls with known values)
local_comptime_constants: std.StringHashMap(comptime_mod.Value),
// Top-level #run expressions for side effects only
comptime_side_effects: std.ArrayList(*Node),
// Generic function templates: maps name to AST for deferred monomorphization
@@ -384,6 +386,7 @@ pub const CodeGen = struct {
.current_function = null,
.scope_stack = std.ArrayList(Scope).empty,
.comptime_globals = std.StringHashMap(ComptimeGlobal).init(allocator),
.local_comptime_constants = std.StringHashMap(comptime_mod.Value).init(allocator),
.comptime_side_effects = std.ArrayList(*Node).empty,
.generic_templates = std.StringHashMap(ast.FnDecl).init(allocator),
.generic_instances = std.StringHashMap(c.LLVMValueRef).init(allocator),
@@ -1256,6 +1259,94 @@ pub const CodeGen = struct {
};
}
/// Try to evaluate a :: call expression entirely at compile time.
/// Works for any function where all args are comptime-known.
/// Returns the result string if successful, null to fall through to runtime codegen.
fn tryComptimeCallEval(self: *CodeGen, cd: ast.ConstDecl) ?comptime_mod.Value {
const call_node = cd.value.data.call;
// Resolve callee name
const callee_name = if (call_node.callee.data == .identifier)
call_node.callee.data.identifier.name
else if (call_node.callee.data == .field_access) blk: {
const fa = call_node.callee.data.field_access;
if (fa.object.data == .identifier) {
const qualified = std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ fa.object.data.identifier.name, fa.field }) catch return null;
break :blk qualified;
}
break :blk @as(?[]const u8, null);
} else null;
const cn = callee_name orelse return null;
// Look up the function — either generic template or regular fn_decl
const fd = self.findFnDecl(cn) orelse return null;
// Resolve all args to comptime values
var arg_values = self.allocator.alloc(comptime_mod.Value, call_node.args.len) catch return null;
for (call_node.args, 0..) |arg, i| {
arg_values[i] = self.resolveComptimeArg(arg) orelse return null;
}
// Set up VM and push all args onto the stack
var vm = comptime_mod.VM.init(self.allocator, if (self.sema_result) |sr| sr else null, self.root_decls, self);
for (arg_values) |val| {
vm.push(val) catch return null;
}
// Compile and invoke the function — the VM handles #insert, variadics, etc.
vm.compileFunctionAndInvoke(cn, fd, @intCast(arg_values.len)) catch return null;
// Run the VM to completion
const result = vm.run() catch return null;
return result;
}
/// Find a function declaration by name in generic_templates or root_decls.
fn findFnDecl(self: *CodeGen, name: []const u8) ?ast.FnDecl {
// Check generic templates first
if (self.generic_templates.get(name)) |fd| return fd;
// Search root_decls
for (self.root_decls) |decl| {
switch (decl.data) {
.fn_decl => |fd| {
if (std.mem.eql(u8, fd.name, name)) return fd;
},
.namespace_decl => |ns| {
for (ns.decls) |d| {
if (d.data == .fn_decl and std.mem.eql(u8, d.data.fn_decl.name, name))
return d.data.fn_decl;
}
},
else => {},
}
}
return null;
}
/// Resolve an AST node to a comptime Value using local_comptime_constants.
/// Handles literals, identifiers, and field accesses like `body.len`.
fn resolveComptimeArg(self: *CodeGen, node: *Node) ?comptime_mod.Value {
return switch (node.data) {
.string_literal => |sl| .{ .string_val = if (sl.is_raw) sl.raw else unescape.unescapeString(self.allocator, sl.raw) catch return null },
.int_literal => |il| .{ .int_val = il.value },
.identifier => |id| self.local_comptime_constants.get(id.name),
.field_access => |fa| {
const base = self.resolveComptimeArg(fa.object) orelse return null;
if (std.mem.eql(u8, fa.field, "len")) {
if (base == .string_val) {
return .{ .int_val = @intCast(base.string_val.len) };
}
if (base == .array_val) {
return .{ .int_val = @intCast(base.array_val.elements.len) };
}
}
return null;
},
else => null,
};
}
/// Substitute comptime param identifiers in an AST expression with their literal nodes.
/// Used before comptimeEval in #insert to resolve comptime function params.
fn substituteComptimeNodes(self: *CodeGen, node: *Node) !*Node {
@@ -1325,7 +1416,7 @@ pub const CodeGen = struct {
.void_val => self.constInt32(0),
.pointer_val => c.LLVMConstNull(self.ptrType()),
.null_val => c.LLVMConstNull(self.ptrType()),
.struct_val, .array_val, .type_val, .function_val, .byte_ptr_val, .union_val => unreachable,
.struct_val, .array_val, .type_val, .function_val, .byte_ptr_val, .union_val, .any_val => unreachable,
};
}
@@ -2849,6 +2940,28 @@ pub const CodeGen = struct {
return null;
}
// Try comptime evaluation for :: call expressions (all args must be comptime-known)
if (cd.value.data == .call) {
if (self.tryComptimeCallEval(cd)) |result| {
if (result == .string_val) {
const llvm_val = self.comptimeValueToLLVM(result, .string_type);
const llvm_ty = self.getStringStructType();
const alloca = try self.buildNamedAlloca(llvm_ty, cd.name);
_ = c.LLVMBuildStore(self.builder, llvm_val, alloca);
try self.registerVariable(cd.name, alloca, .string_type);
try self.local_comptime_constants.put(cd.name, result);
return null;
} else if (result == .int_val) {
const llvm_val = self.constInt64(@bitCast(result.int_val));
const alloca = try self.buildNamedAlloca(self.i64Type(), cd.name);
_ = c.LLVMBuildStore(self.builder, llvm_val, alloca);
try self.registerVariable(cd.name, alloca, Type.s(64));
try self.local_comptime_constants.put(cd.name, result);
return null;
}
}
}
var sx_ty: Type = Type.s(64);
if (cd.type_annotation) |ta| {
@@ -2894,6 +3007,16 @@ pub const CodeGen = struct {
const alloca = try self.buildNamedAlloca(llvm_ty, cd.name);
_ = c.LLVMBuildStore(self.builder, init_val, alloca);
try self.registerVariable(cd.name, alloca, sx_ty);
// Track comptime value for :: string/int literals (for comptime format evaluation)
if (cd.value.data == .string_literal) {
const sl = cd.value.data.string_literal;
const content = if (sl.is_raw) sl.raw else unescape.unescapeString(self.allocator, sl.raw) catch return null;
try self.local_comptime_constants.put(cd.name, .{ .string_val = content });
} else if (cd.value.data == .int_literal) {
try self.local_comptime_constants.put(cd.name, .{ .int_val = cd.value.data.int_literal.value });
}
return null;
}