P5.7 Step C: delete interp.zig — the comptime VM is the sole evaluator
The legacy tagged-Value Interpreter is gone. Relocate the Value result-DTO
+ decodeVariantElements into a new comptime_value.zig (the VM<->host
materialization boundary); repoint comptime_vm/emit_llvm/ir-barrel Value to
it and BuildConfig to compiler_hooks; delete the dead valueToReg bridge;
slim compiler_lib.zig to just the name registry (BoundFn{sx_name} + bound_fns
+ findFn — weldedCompilerFn only validates names); simplify printInterpBailDiag
to comptime_vm.last_bail_reason; drop the unused interp_mod import in lower.zig.
rm src/ir/interp.zig + interp.test.zig.
Value is relocated (not eliminated): it survives only as the slim result DTO
at the VM->valueToLLVMConst boundary; the execution-time marshaling the VM
pivot targeted is gone. Drop dead Value.asString/reflectTypeId.
706/0 corpus + 476/476 unit.
This commit is contained in:
@@ -1,149 +1,98 @@
|
||||
//! The comptime `compiler` library's function bridge — the curated set of the
|
||||
//! The comptime `compiler` library's name registry — the curated set of the
|
||||
//! compiler's own functions reachable from comptime sx via
|
||||
//! `abi(.zig) extern compiler`. See `current/PLAN-COMPILER-VM.md`.
|
||||
//!
|
||||
//! **This registry IS the safety boundary.** Only the functions registered here
|
||||
//! are bindable from user comptime code; a name not on the export list is
|
||||
//! rejected at declaration (`weldedCompilerFn`), and the interpreter dispatches a
|
||||
//! welded call to the matching Zig handler instead of dlsym.
|
||||
//! **This registry IS the safety boundary.** Only the names registered here are
|
||||
//! bindable from user comptime code; a name not on the export list is rejected
|
||||
//! at declaration (`weldedCompilerFn`). The comptime VM
|
||||
//! (`comptime_vm.callCompilerFn`) services every welded call by name — this file
|
||||
//! only carries the list of recognized names.
|
||||
//!
|
||||
//! **Direction note (2026-06-17 pivot).** The byte-weld of TYPES (sx structs whose
|
||||
//! layout was validated to mirror the compiler's Zig records) was stripped — it
|
||||
//! bolted a parallel layout regime + hand-marshaling onto a comptime value model
|
||||
//! that isn't bytes. The replacement is a comptime VM where values are
|
||||
//! native bytes, so the compiler-API needs no weld/validation/marshaling (Phase 3
|
||||
//! of the plan re-homes the type/function exposure on that VM). `intern`/`text_of`
|
||||
//! survive here as the first compiler-call seed: clean scalar host-calls (string in,
|
||||
//! handle out), no weld involved.
|
||||
//! native bytes, so the compiler-API needs no weld/validation/marshaling.
|
||||
|
||||
const std = @import("std");
|
||||
const types = @import("types.zig");
|
||||
const interp_mod = @import("interp.zig");
|
||||
const Value = interp_mod.Value;
|
||||
const Interpreter = interp_mod.Interpreter;
|
||||
const InterpError = interp_mod.InterpError;
|
||||
const StringId = types.StringId;
|
||||
|
||||
/// The name of the only compiler library. A `fn abi(.zig) extern <lib>` with a
|
||||
/// different `<lib>` is rejected — `compiler` is the sole comptime bind source.
|
||||
pub const lib_name = "compiler";
|
||||
|
||||
// ── Functions (comptime-only, host-call bridged) ────────────────────────────
|
||||
|
||||
/// A welded `compiler` function: dispatched under the comptime interpreter to its
|
||||
/// Zig handler (never dlsym'd). The handler receives the interpreter (for the
|
||||
/// string pool / type table) and the already-evaluated argument `Value`s, and
|
||||
/// returns the result `Value`.
|
||||
pub const FnHandler = *const fn (interp: *Interpreter, args: []const Value) InterpError!Value;
|
||||
// ── Functions (comptime-only, serviced by the comptime VM) ──────────────────
|
||||
|
||||
pub const BoundFn = struct {
|
||||
sx_name: []const u8,
|
||||
handler: FnHandler,
|
||||
};
|
||||
|
||||
/// The compiler-function export list. The `StringId` round-trip readers are the
|
||||
/// seed; the type-table API (lookup / register) is re-homed onto the comptime
|
||||
/// VM in Phase 3 of `PLAN-COMPILER-VM.md`.
|
||||
/// The compiler-function export list. Every entry is serviced by name in
|
||||
/// `comptime_vm.callCompilerFn`; `weldedCompilerFn` consults this list to decide
|
||||
/// whether a `abi(.compiler)` name is a recognized compiler-API function.
|
||||
pub const bound_fns = [_]BoundFn{
|
||||
.{ .sx_name = "intern", .handler = handleIntern },
|
||||
.{ .sx_name = "text_of", .handler = handleTextOf },
|
||||
.{ .sx_name = "find_type", .handler = handleFindType },
|
||||
.{ .sx_name = "type_field_count", .handler = handleTypeFieldCount },
|
||||
.{ .sx_name = "type_nominal_name", .handler = handleTypeNominalName },
|
||||
.{ .sx_name = "type_field_name", .handler = handleTypeFieldName },
|
||||
.{ .sx_name = "type_field_type", .handler = handleTypeFieldType },
|
||||
.{ .sx_name = "type_kind", .handler = handleTypeKind },
|
||||
.{ .sx_name = "type_field_value", .handler = handleTypeFieldValue },
|
||||
.{ .sx_name = "intern" },
|
||||
.{ .sx_name = "text_of" },
|
||||
.{ .sx_name = "find_type" },
|
||||
.{ .sx_name = "type_field_count" },
|
||||
.{ .sx_name = "type_nominal_name" },
|
||||
.{ .sx_name = "type_field_name" },
|
||||
.{ .sx_name = "type_field_type" },
|
||||
.{ .sx_name = "type_kind" },
|
||||
.{ .sx_name = "type_field_value" },
|
||||
// ── write side (lowering-time, mints into the type table) ────────────────
|
||||
.{ .sx_name = "declare_type", .handler = handleDeclareType },
|
||||
.{ .sx_name = "pointer_to", .handler = handlePointerTo },
|
||||
.{ .sx_name = "register_type", .handler = handleRegisterType },
|
||||
.{ .sx_name = "declare_type" },
|
||||
.{ .sx_name = "pointer_to" },
|
||||
.{ .sx_name = "register_type" },
|
||||
// ── BuildOptions (migrated off `#compiler` onto `abi(.compiler)`) ─────────
|
||||
.{ .sx_name = "build_options", .handler = handleBuildOptions },
|
||||
.{ .sx_name = "on_build", .handler = handleOnBuild },
|
||||
.{ .sx_name = "build_options" },
|
||||
.{ .sx_name = "on_build" },
|
||||
// ── build-pipeline metadata queries (Phase 5.2) ──────────────────────────
|
||||
// VM-only: the post-link callback that calls these always runs on the VM
|
||||
// (`core.invokeByFuncId`), so `comptime_vm.callCompilerFn` services them and
|
||||
// these legacy handlers are never reached (they bail loudly rather than
|
||||
// fabricate a silent empty List). Registered here only so `weldedCompilerFn`
|
||||
// recognizes the names as compiler-API functions.
|
||||
.{ .sx_name = "c_object_paths", .handler = handleBuildPipelineQuery },
|
||||
.{ .sx_name = "link_libraries", .handler = handleBuildPipelineQuery },
|
||||
.{ .sx_name = "emit_object", .handler = handleBuildPipelineQuery },
|
||||
.{ .sx_name = "link", .handler = handleBuildPipelineQuery },
|
||||
.{ .sx_name = "build_output", .handler = handleBuildPipelineQuery },
|
||||
.{ .sx_name = "build_target", .handler = handleBuildPipelineQuery },
|
||||
.{ .sx_name = "build_frameworks", .handler = handleBuildPipelineQuery },
|
||||
.{ .sx_name = "build_flags", .handler = handleBuildPipelineQuery },
|
||||
.{ .sx_name = "c_object_paths" },
|
||||
.{ .sx_name = "link_libraries" },
|
||||
.{ .sx_name = "emit_object" },
|
||||
.{ .sx_name = "link" },
|
||||
.{ .sx_name = "build_output" },
|
||||
.{ .sx_name = "build_target" },
|
||||
.{ .sx_name = "build_frameworks" },
|
||||
.{ .sx_name = "build_flags" },
|
||||
// ── BuildOptions accessors (Phase 5.5) ───────────────────────────────────
|
||||
// Migrated off the `struct #compiler` hook surface to free `abi(.compiler)`
|
||||
// functions serviced by `comptime_vm.callCompilerFn`. VM-only: any `#run` /
|
||||
// const-init reaching them is routed to the VM (emit_llvm `entryNeedsVm`), so
|
||||
// these legacy stubs are never reached — registered only so `weldedCompilerFn`
|
||||
// recognizes the names. They bail loudly rather than fabricate a silent result.
|
||||
.{ .sx_name = "add_link_flag", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "add_framework", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "set_output_path", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "set_wasm_shell", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "add_asset_dir", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "asset_dir_count", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "asset_dir_src_at", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "asset_dir_dest_at", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "set_post_link_module", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "binary_path", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "set_bundle_path", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "set_bundle_id", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "set_codesign_identity", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "set_provisioning_profile", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "bundle_path", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "bundle_id", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "codesign_identity", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "provisioning_profile", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "target_triple", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "is_macos", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "is_ios", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "is_ios_device", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "is_ios_simulator", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "is_android", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "framework_count", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "framework_at", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "framework_path_count", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "framework_path_at", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "set_manifest_path", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "set_keystore_path", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "manifest_path", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "keystore_path", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "jni_main_count", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "jni_main_runtime_path_at", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "jni_main_java_source_at", .handler = handleBuildOptionsAccessor },
|
||||
.{ .sx_name = "add_link_flag" },
|
||||
.{ .sx_name = "add_framework" },
|
||||
.{ .sx_name = "set_output_path" },
|
||||
.{ .sx_name = "set_wasm_shell" },
|
||||
.{ .sx_name = "add_asset_dir" },
|
||||
.{ .sx_name = "asset_dir_count" },
|
||||
.{ .sx_name = "asset_dir_src_at" },
|
||||
.{ .sx_name = "asset_dir_dest_at" },
|
||||
.{ .sx_name = "set_post_link_module" },
|
||||
.{ .sx_name = "binary_path" },
|
||||
.{ .sx_name = "set_bundle_path" },
|
||||
.{ .sx_name = "set_bundle_id" },
|
||||
.{ .sx_name = "set_codesign_identity" },
|
||||
.{ .sx_name = "set_provisioning_profile" },
|
||||
.{ .sx_name = "bundle_path" },
|
||||
.{ .sx_name = "bundle_id" },
|
||||
.{ .sx_name = "codesign_identity" },
|
||||
.{ .sx_name = "provisioning_profile" },
|
||||
.{ .sx_name = "target_triple" },
|
||||
.{ .sx_name = "is_macos" },
|
||||
.{ .sx_name = "is_ios" },
|
||||
.{ .sx_name = "is_ios_device" },
|
||||
.{ .sx_name = "is_ios_simulator" },
|
||||
.{ .sx_name = "is_android" },
|
||||
.{ .sx_name = "framework_count" },
|
||||
.{ .sx_name = "framework_at" },
|
||||
.{ .sx_name = "framework_path_count" },
|
||||
.{ .sx_name = "framework_path_at" },
|
||||
.{ .sx_name = "set_manifest_path" },
|
||||
.{ .sx_name = "set_keystore_path" },
|
||||
.{ .sx_name = "manifest_path" },
|
||||
.{ .sx_name = "keystore_path" },
|
||||
.{ .sx_name = "jni_main_count" },
|
||||
.{ .sx_name = "jni_main_runtime_path_at" },
|
||||
.{ .sx_name = "jni_main_java_source_at" },
|
||||
};
|
||||
|
||||
/// Legacy-path stub for the Phase 5.5 BuildOptions accessors — see the `bound_fns`
|
||||
/// comment. Any `#run` / const-init reaching a BuildOptions accessor is routed to
|
||||
/// the VM (`emit_llvm.entryNeedsVm`), so this is never reached; it bails loudly
|
||||
/// rather than fabricate a silent result.
|
||||
fn handleBuildOptionsAccessor(_: *Interpreter, _: []const Value) InterpError!Value {
|
||||
Interpreter.last_bail_detail = "BuildOptions accessor is VM-only (Phase 5.5); not available on the legacy interpreter";
|
||||
return error.CannotEvalComptime;
|
||||
}
|
||||
|
||||
/// Legacy-path stub for the Phase 5 build-pipeline primitives — see the
|
||||
/// `bound_fns` comment. The only caller (the post-link build driver) runs on the
|
||||
/// VM (`core.invokeByFuncId`), so these legacy handlers are never reached; they
|
||||
/// bail loudly instead of fabricating a silent result.
|
||||
fn handleBuildPipelineQuery(_: *Interpreter, _: []const Value) InterpError!Value {
|
||||
Interpreter.last_bail_detail = "build-pipeline primitive (emit_object/c_object_paths/link_libraries) is VM-only (post-link); not available on the legacy interpreter";
|
||||
return error.CannotEvalComptime;
|
||||
}
|
||||
|
||||
// Kind codes accepted by `register_type` — mirror `TypeTable.kindCode`. An
|
||||
// enum-like type is minted as a `tagged_union` (the general payload-carrying
|
||||
// form, as `define` does), so both 2 (`enum`) and 3 (`tagged_union`) are taken.
|
||||
const kind_struct: i64 = 1;
|
||||
const kind_enum: i64 = 2;
|
||||
const kind_tagged_union: i64 = 3;
|
||||
const kind_tuple: i64 = 4;
|
||||
|
||||
/// Look up a compiler function by its sx name. Returns null when the name is not
|
||||
/// on the export list.
|
||||
pub fn findFn(sx_name: []const u8) ?*const BoundFn {
|
||||
@@ -152,265 +101,3 @@ pub fn findFn(sx_name: []const u8) ?*const BoundFn {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// The comptime type table to intern into: the host's mutable mint target when
|
||||
/// set (the metatype-construction path), else the module's table reached through
|
||||
/// a const-cast — the same access the interp's mint path uses (interp.zig). The
|
||||
/// underlying table is genuinely mutable; the interp merely holds it `const`.
|
||||
fn mintTable(interp: *Interpreter) *types.TypeTable {
|
||||
return interp.mint orelse @constCast(&interp.module.types);
|
||||
}
|
||||
|
||||
/// `intern(s: string) -> StringId` — intern `s` into the compiler's string pool
|
||||
/// and return its handle. The inverse of `text_of`.
|
||||
fn handleIntern(interp: *Interpreter, args: []const Value) InterpError!Value {
|
||||
if (args.len != 1 or args[0] != .string) return error.TypeError;
|
||||
const id = mintTable(interp).internString(args[0].string);
|
||||
return Value{ .int = @intFromEnum(id) };
|
||||
}
|
||||
|
||||
/// `text_of(id: StringId) -> string` — resolve a string handle back to its text.
|
||||
/// The inverse of `intern`.
|
||||
fn handleTextOf(interp: *Interpreter, args: []const Value) InterpError!Value {
|
||||
if (args.len != 1 or args[0] != .int) return error.TypeError;
|
||||
if (args[0].int < 0 or args[0].int > std.math.maxInt(u32)) return error.TypeError;
|
||||
const id: StringId = @enumFromInt(@as(u32, @intCast(args[0].int)));
|
||||
return Value{ .string = interp.module.types.getString(id) };
|
||||
}
|
||||
|
||||
/// `find_type(name: StringId) -> TypeId` — look up a named type (struct / enum /
|
||||
/// union / tagged-union / error-set) by its interned name and return its handle.
|
||||
/// A name with no matching type yields the dedicated `unresolved` sentinel (a
|
||||
/// `TypeId` of 0), the codebase-blessed "no type" marker — NOT an `?Type` (a
|
||||
/// `Type` value is `.any`-typed, which the comptime VM does not represent, and
|
||||
/// an optional can't cross the legacy↔VM eval boundary). The caller checks the
|
||||
/// handle against 0 / `unresolved`. The VM mirrors this in `comptime_vm.callCompilerFn`.
|
||||
fn handleFindType(interp: *Interpreter, args: []const Value) InterpError!Value {
|
||||
if (args.len != 1 or args[0] != .int) return error.TypeError;
|
||||
if (args[0].int < 0 or args[0].int > std.math.maxInt(u32)) return error.TypeError;
|
||||
const name: StringId = @enumFromInt(@as(u32, @intCast(args[0].int)));
|
||||
const tid = interp.module.types.findByName(name) orelse types.TypeId.unresolved;
|
||||
return Value{ .int = tid.index() };
|
||||
}
|
||||
|
||||
/// `type_field_count(t: TypeId) -> i64` — the member count of an aggregate type
|
||||
/// (struct/union/tagged-union fields, enum variants, array/vector length), read
|
||||
/// through `TypeTable.memberCount`. A type with no member count (scalar, pointer,
|
||||
/// the `unresolved` sentinel, …) is a loud error — never a silent 0.
|
||||
fn handleTypeFieldCount(interp: *Interpreter, args: []const Value) InterpError!Value {
|
||||
if (args.len != 1 or args[0] != .int) return error.TypeError;
|
||||
if (args[0].int < 0 or args[0].int > std.math.maxInt(u32)) return error.TypeError;
|
||||
const tid: types.TypeId = @enumFromInt(@as(u32, @intCast(args[0].int)));
|
||||
const count = interp.module.types.memberCount(tid) orelse return error.TypeError;
|
||||
return Value{ .int = count };
|
||||
}
|
||||
|
||||
/// Read an integer `Value` arg as a `u32` handle (StringId / TypeId). Errors on a
|
||||
/// non-int or out-of-u32-range value — never a silent clamp.
|
||||
fn handleArg(args: []const Value, i: usize) InterpError!u32 {
|
||||
if (args[i] != .int) return error.TypeError;
|
||||
if (args[i].int < 0 or args[i].int > std.math.maxInt(u32)) return error.TypeError;
|
||||
return @intCast(args[i].int);
|
||||
}
|
||||
|
||||
/// `type_nominal_name(t: TypeId) -> StringId` — the nominal name handle of a named
|
||||
/// type (struct/enum/union/…). Loud error for an unnamed type (no silent default).
|
||||
fn handleTypeNominalName(interp: *Interpreter, args: []const Value) InterpError!Value {
|
||||
if (args.len != 1) return error.TypeError;
|
||||
const tid: types.TypeId = @enumFromInt(try handleArg(args, 0));
|
||||
const sid = interp.module.types.nominalName(tid) orelse return error.TypeError;
|
||||
return Value{ .int = @intFromEnum(sid) };
|
||||
}
|
||||
|
||||
/// `type_field_name(t: TypeId, idx: i64) -> StringId` — name handle of member `idx`
|
||||
/// (struct/union/tagged-union field, enum variant, named-tuple element). Loud
|
||||
/// error for an out-of-range idx or a type with no named members.
|
||||
fn handleTypeFieldName(interp: *Interpreter, args: []const Value) InterpError!Value {
|
||||
if (args.len != 2 or args[1] != .int) return error.TypeError;
|
||||
const tid: types.TypeId = @enumFromInt(try handleArg(args, 0));
|
||||
const sid = interp.module.types.memberName(tid, args[1].int) orelse return error.TypeError;
|
||||
return Value{ .int = @intFromEnum(sid) };
|
||||
}
|
||||
|
||||
/// `type_field_type(t: TypeId, idx: i64) -> TypeId` — type handle of member `idx`
|
||||
/// (struct/union/tagged-union field, tuple/array/vector element). Loud error for
|
||||
/// an out-of-range idx or a type with no member types (e.g. a payloadless enum).
|
||||
fn handleTypeFieldType(interp: *Interpreter, args: []const Value) InterpError!Value {
|
||||
if (args.len != 2 or args[1] != .int) return error.TypeError;
|
||||
const tid: types.TypeId = @enumFromInt(try handleArg(args, 0));
|
||||
const mty = interp.module.types.memberType(tid, args[1].int) orelse return error.TypeError;
|
||||
return Value{ .int = mty.index() };
|
||||
}
|
||||
|
||||
/// `type_kind(t: TypeId) -> i64` — the stable kind discriminant (see
|
||||
/// `TypeTable.kindCode`). Total: an unnamed/non-aggregate type reads `other` (0).
|
||||
fn handleTypeKind(interp: *Interpreter, args: []const Value) InterpError!Value {
|
||||
if (args.len != 1) return error.TypeError;
|
||||
const tid: types.TypeId = @enumFromInt(try handleArg(args, 0));
|
||||
return Value{ .int = interp.module.types.kindCode(tid) };
|
||||
}
|
||||
|
||||
/// `type_field_value(t: TypeId, idx: i64) -> i64` — enum variant `idx`'s integer
|
||||
/// value (explicit or ordinal). Loud error for a non-enum or out-of-range idx.
|
||||
fn handleTypeFieldValue(interp: *Interpreter, args: []const Value) InterpError!Value {
|
||||
if (args.len != 2 or args[1] != .int) return error.TypeError;
|
||||
const tid: types.TypeId = @enumFromInt(try handleArg(args, 0));
|
||||
const v = interp.module.types.memberValue(tid, args[1].int) orelse return error.TypeError;
|
||||
return Value{ .int = v };
|
||||
}
|
||||
|
||||
// ── write side: declare_type / pointer_to / register_type ───────────────────
|
||||
//
|
||||
// These MINT into the type table, so they only make sense at LOWERING time —
|
||||
// where the compiler still resolves references to the new types and the `mint`
|
||||
// target is open (`runComptimeTypeFunc`). They take/return real `Type` values
|
||||
// (`.type_tag`), the comptime-native form, matching meta.sx's `StructField` /
|
||||
// `declare` / `define`. This is the unified re-expression of the metatype:
|
||||
// `declare_type` ≈ `declare`, `register_type` ≈ a single kind-branching `define`,
|
||||
// and `pointer_to` builds `*T` references so a graph of types can refer to each
|
||||
// other (forward handles + pointers) before their bodies are filled.
|
||||
|
||||
/// `declare_type(name: string) -> Type` — mint a NEW empty forward nominal type
|
||||
/// named `name` (or return the existing slot, so a self/sibling reference by name
|
||||
/// resolves to the same one). Mirrors the `declare` builtin: the forward slot is
|
||||
/// an empty `tagged_union` until `register_type` fills it.
|
||||
fn handleDeclareType(interp: *Interpreter, args: []const Value) InterpError!Value {
|
||||
if (args.len != 1 or args[0] != .string) return error.TypeError;
|
||||
const tbl = mintTable(interp);
|
||||
const name_id = tbl.internString(args[0].string);
|
||||
if (tbl.findByName(name_id)) |existing| return Value{ .type_tag = existing };
|
||||
const info: types.TypeInfo = .{ .tagged_union = .{ .name = name_id, .fields = &.{}, .tag_type = .i64 } };
|
||||
return Value{ .type_tag = tbl.internNominal(info, 0) };
|
||||
}
|
||||
|
||||
/// `pointer_to(t: Type) -> Type` — intern `*t`. Lets a member reference a type by
|
||||
/// pointer (e.g. a recursive `*A`) from a `Type` handle.
|
||||
fn handlePointerTo(interp: *Interpreter, args: []const Value) InterpError!Value {
|
||||
if (args.len != 1 or args[0] != .type_tag) return error.TypeError;
|
||||
const tbl = mintTable(interp);
|
||||
return Value{ .type_tag = tbl.intern(.{ .pointer = .{ .pointee = args[0].type_tag } }) };
|
||||
}
|
||||
|
||||
/// `register_type(handle: Type, kind: i64, members: []Member) -> Type` — fill a
|
||||
/// `declare_type`'d forward slot, branching on `kind` IN THE COMPILER (subsuming
|
||||
/// `define`'s per-kind dispatch). `Member` is `{ name: string, ty: Type }`:
|
||||
/// struct → fields `{ name, ty }` (dup names rejected)
|
||||
/// enum/t-union → variants `{ name, payload = ty }` (minted as a tagged_union)
|
||||
/// tuple → positional element types (names ignored)
|
||||
/// Returns the (now completed) handle. Every malformed input is a loud error.
|
||||
fn handleRegisterType(interp: *Interpreter, args: []const Value) InterpError!Value {
|
||||
if (args.len != 3 or args[0] != .type_tag or args[1] != .int) return error.TypeError;
|
||||
const handle = args[0].type_tag;
|
||||
const kind = args[1].int;
|
||||
const elems = interp_mod.decodeVariantElements(args[2]) orelse return error.TypeError;
|
||||
if (elems.len == 0) return error.TypeError; // a type with no members is never valid
|
||||
const tbl = mintTable(interp);
|
||||
// The slot's nominal identity. Accept the forward `tagged_union` from
|
||||
// `declare_type` AND an already-completed nominal of the same name — so
|
||||
// re-evaluating the same type-fn (e.g. a minting module reached via two
|
||||
// import edges) RE-FILLS the slot idempotently instead of erroring. A
|
||||
// non-nominal handle is rejected (not a `declare_type`'d slot).
|
||||
const ident = nominalIdent(tbl.get(handle)) orelse return error.TypeError;
|
||||
|
||||
if (kind == kind_tuple) {
|
||||
var tys = std.ArrayList(types.TypeId).empty;
|
||||
for (elems) |elem| {
|
||||
const m = memberPair(elem) orelse return error.TypeError;
|
||||
tys.append(interp.alloc, m.ty) catch return error.CannotEvalComptime;
|
||||
}
|
||||
tbl.replaceKeyedInfo(handle, .{ .tuple = .{ .fields = tys.items, .names = null } });
|
||||
return Value{ .type_tag = handle };
|
||||
}
|
||||
|
||||
if (kind == kind_enum) {
|
||||
// An ACTUAL (payloadless) enum: members are variant NAMES. A non-void
|
||||
// payload means the caller wants a payload-carrying variant — that's a
|
||||
// tagged_union (kind 3), so reject it loudly rather than dropping it.
|
||||
var variants = std.ArrayList(StringId).empty;
|
||||
for (elems) |elem| {
|
||||
const m = memberPair(elem) orelse return error.TypeError;
|
||||
if (m.ty != .void) return error.TypeError; // payload variant → use kind 3 (tagged_union)
|
||||
const name_id = tbl.internString(m.name);
|
||||
for (variants.items) |existing| if (existing == name_id) return error.TypeError; // dup variant
|
||||
variants.append(interp.alloc, name_id) catch return error.CannotEvalComptime;
|
||||
}
|
||||
tbl.replaceKeyedInfo(handle, .{ .@"enum" = .{ .name = ident.name, .variants = variants.items, .nominal_id = ident.nominal_id } });
|
||||
return Value{ .type_tag = handle };
|
||||
}
|
||||
|
||||
// struct / tagged_union collect `{ name, ty }` fields.
|
||||
var fields = std.ArrayList(types.TypeInfo.StructInfo.Field).empty;
|
||||
for (elems) |elem| {
|
||||
const m = memberPair(elem) orelse return error.TypeError;
|
||||
const name_id = tbl.internString(m.name);
|
||||
for (fields.items) |existing| if (existing.name == name_id) return error.TypeError; // dup member name
|
||||
fields.append(interp.alloc, .{ .name = name_id, .ty = m.ty }) catch return error.CannotEvalComptime;
|
||||
}
|
||||
const full: types.TypeInfo = switch (kind) {
|
||||
kind_struct => .{ .@"struct" = .{ .name = ident.name, .fields = fields.items, .nominal_id = ident.nominal_id } },
|
||||
kind_tagged_union => .{ .tagged_union = .{ .name = ident.name, .fields = fields.items, .tag_type = .i64, .nominal_id = ident.nominal_id } },
|
||||
else => return error.TypeError, // unknown kind code
|
||||
};
|
||||
tbl.replaceKeyedInfo(handle, full);
|
||||
return Value{ .type_tag = handle };
|
||||
}
|
||||
|
||||
/// The nominal identity (`name` + stable `nominal_id`) of a declare_type'd slot —
|
||||
/// from the forward `tagged_union` OR an already-completed nominal (so a re-fill
|
||||
/// preserves identity). A `tuple` is structural (no nominal name); null for a
|
||||
/// non-nominal handle (not a `declare_type` result).
|
||||
fn nominalIdent(info: types.TypeInfo) ?struct { name: StringId, nominal_id: u32 } {
|
||||
return switch (info) {
|
||||
.tagged_union => |u| .{ .name = u.name, .nominal_id = u.nominal_id },
|
||||
.@"enum" => |e| .{ .name = e.name, .nominal_id = e.nominal_id },
|
||||
.@"struct" => |s| .{ .name = s.name, .nominal_id = s.nominal_id },
|
||||
.tuple => .{ .name = StringId.empty, .nominal_id = 0 }, // structural; name vestigial
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// Decode one `Member` value — a `{ name: string, ty: Type }` aggregate.
|
||||
fn memberPair(elem: Value) ?struct { name: []const u8, ty: types.TypeId } {
|
||||
const f = switch (elem) {
|
||||
.aggregate => |a| a,
|
||||
else => return null,
|
||||
};
|
||||
if (f.len != 2) return null;
|
||||
const name = switch (f[0]) {
|
||||
.string => |s| s,
|
||||
else => return null,
|
||||
};
|
||||
const ty = f[1].asTypeId() orelse return null;
|
||||
return .{ .name = name, .ty = ty };
|
||||
}
|
||||
|
||||
// ── BuildOptions handlers (legacy dual-path, gate-OFF) ──────────────────────
|
||||
// The `abi(.compiler)` re-expression of `build_options` + `set_post_link_callback`,
|
||||
// reading the build config off the interpreter (`interp.build_config`). The VM
|
||||
// services the same names in `comptime_vm.callCompilerFn`; both stay in lockstep.
|
||||
|
||||
/// `build_options() -> BuildOptions` — hand back the opaque zero-field handle. The
|
||||
/// state lives on `interp.build_config`; the handle is never dereferenced.
|
||||
fn handleBuildOptions(_: *Interpreter, _: []const Value) InterpError!Value {
|
||||
return .void_val;
|
||||
}
|
||||
|
||||
/// `set_post_link_callback(self, cb)` — record the callback `FuncId` on the build
|
||||
/// config so `main.zig` re-enters the evaluator post-link. The `cb` arg is a
|
||||
/// `.func_ref` value.
|
||||
/// `on_build(cb)` — register the build callback (the Phase 5 form, a free fn; cb
|
||||
/// is arg 0, and `cb: (opt: BuildOptions) -> bool` so the callback is invoked with
|
||||
/// the `BuildOptions` handle). Sets `post_link_takes_options` to distinguish it
|
||||
/// from the legacy `set_post_link_callback` (`() -> bool`).
|
||||
fn handleOnBuild(interp: *Interpreter, args: []const Value) InterpError!Value {
|
||||
if (args.len != 1) return error.TypeError;
|
||||
const bc = interp.build_config orelse return error.CannotEvalComptime;
|
||||
switch (args[0]) {
|
||||
.func_ref => |id| {
|
||||
bc.post_link_callback_fn = id;
|
||||
bc.post_link_takes_options = true;
|
||||
},
|
||||
else => return error.TypeError,
|
||||
}
|
||||
return .void_val;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user