comptime format
This commit is contained in:
125
src/codegen.zig
125
src/codegen.zig
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user