mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-14 13:49:08 +00:00
247 lines
7.7 KiB
Zig
247 lines
7.7 KiB
Zig
//! Build-time compiler for Roc builtin modules (Dict.roc and Set.roc).
|
|
//!
|
|
//! This executable runs during `zig build` on the host machine to:
|
|
//! 1. Parse and type-check Dict.roc and Set.roc
|
|
//! 2. Serialize the resulting ModuleEnvs to binary files
|
|
//! 3. Output .bin files to zig-out/builtins/ (which get embedded in the roc binary)
|
|
|
|
const std = @import("std");
|
|
const base = @import("base");
|
|
const parse = @import("parse");
|
|
const can = @import("can");
|
|
const check = @import("check");
|
|
const collections = @import("collections");
|
|
const types = @import("types");
|
|
|
|
const ModuleEnv = can.ModuleEnv;
|
|
const Can = can.Can;
|
|
const Check = check.Check;
|
|
const Allocator = std.mem.Allocator;
|
|
|
|
/// Build-time compiler that compiles builtin .roc sources into serialized ModuleEnvs.
|
|
/// This runs during `zig build` on the host machine to generate .bin files
|
|
/// that get embedded into the final roc executable.
|
|
///
|
|
/// Note: Command-line arguments are ignored. The .roc files are read from fixed paths.
|
|
/// The build system may pass file paths as arguments for cache tracking, but we don't use them.
|
|
pub fn main() !void {
|
|
var gpa_impl = std.heap.GeneralPurposeAllocator(.{}){};
|
|
defer {
|
|
const leaked = gpa_impl.deinit();
|
|
if (leaked == .leak) {
|
|
std.debug.print("WARNING: Memory leaked!\n", .{});
|
|
}
|
|
}
|
|
const gpa = gpa_impl.allocator();
|
|
|
|
// Ignore command-line arguments - they're only used by Zig's build system for cache tracking
|
|
|
|
// Read the .roc source files at runtime
|
|
const bool_roc_source = try std.fs.cwd().readFileAlloc(gpa, "src/build/roc/Bool.roc", 1024 * 1024);
|
|
defer gpa.free(bool_roc_source);
|
|
|
|
const dict_roc_source = try std.fs.cwd().readFileAlloc(gpa, "src/build/roc/Dict.roc", 1024 * 1024);
|
|
defer gpa.free(dict_roc_source);
|
|
|
|
const set_roc_source = try std.fs.cwd().readFileAlloc(gpa, "src/build/roc/Set.roc", 1024 * 1024);
|
|
defer gpa.free(set_roc_source);
|
|
|
|
// Compile Bool.roc without injecting anything (it's completely self-contained)
|
|
const bool_env = try compileModule(
|
|
gpa,
|
|
"Bool",
|
|
bool_roc_source,
|
|
&.{}, // No module dependencies
|
|
.{ .inject_bool = false, .inject_result = false },
|
|
);
|
|
defer {
|
|
bool_env.deinit();
|
|
gpa.destroy(bool_env);
|
|
}
|
|
|
|
// Verify that Bool's type declaration is at the expected index (2)
|
|
// This is critical for the compiler's hardcoded BUILTIN_BOOL constant
|
|
const bool_type_idx = bool_env.all_statements.span.start;
|
|
if (bool_type_idx != 2) {
|
|
const stderr = std.io.getStdErr().writer();
|
|
try stderr.print("WARNING: Expected Bool at index 2, but got {}!\n", .{bool_type_idx});
|
|
return error.UnexpectedBoolIndex;
|
|
}
|
|
|
|
// Compile Dict.roc (needs Bool injected for if expressions, and Result for error handling)
|
|
const dict_env = try compileModule(
|
|
gpa,
|
|
"Dict",
|
|
dict_roc_source,
|
|
&.{}, // No module dependencies
|
|
.{}, // Inject Bool and Result (defaults)
|
|
);
|
|
defer {
|
|
dict_env.deinit();
|
|
gpa.destroy(dict_env);
|
|
}
|
|
|
|
// Compile Set.roc (imports Dict, needs Bool and Result injected)
|
|
const set_env = try compileModule(
|
|
gpa,
|
|
"Set",
|
|
set_roc_source,
|
|
&[_]ModuleDep{
|
|
.{ .name = "Dict", .env = dict_env },
|
|
},
|
|
.{}, // Inject Bool and Result (defaults)
|
|
);
|
|
defer {
|
|
set_env.deinit();
|
|
gpa.destroy(set_env);
|
|
}
|
|
|
|
// Create output directory
|
|
try std.fs.cwd().makePath("zig-out/builtins");
|
|
|
|
// Serialize modules
|
|
try serializeModuleEnv(gpa, bool_env, "zig-out/builtins/Bool.bin");
|
|
try serializeModuleEnv(gpa, dict_env, "zig-out/builtins/Dict.bin");
|
|
try serializeModuleEnv(gpa, set_env, "zig-out/builtins/Set.bin");
|
|
}
|
|
|
|
const ModuleDep = struct {
|
|
name: []const u8,
|
|
env: *const ModuleEnv,
|
|
};
|
|
|
|
fn compileModule(
|
|
gpa: Allocator,
|
|
module_name: []const u8,
|
|
source: []const u8,
|
|
deps: []const ModuleDep,
|
|
can_options: Can.InitOptions,
|
|
) !*ModuleEnv {
|
|
// This follows the pattern from TestEnv.init() in src/check/test/TestEnv.zig
|
|
|
|
// 1. Create ModuleEnv
|
|
var module_env = try gpa.create(ModuleEnv);
|
|
errdefer gpa.destroy(module_env);
|
|
|
|
module_env.* = try ModuleEnv.init(gpa, source);
|
|
errdefer module_env.deinit();
|
|
|
|
module_env.common.source = source;
|
|
module_env.module_name = module_name;
|
|
try module_env.common.calcLineStarts(gpa);
|
|
|
|
// 2. Create common idents (needed for type checking)
|
|
const module_ident = try module_env.insertIdent(base.Ident.for_text(module_name));
|
|
const list_ident = try module_env.insertIdent(base.Ident.for_text("List"));
|
|
const box_ident = try module_env.insertIdent(base.Ident.for_text("Box"));
|
|
|
|
const common_idents: Check.CommonIdents = .{
|
|
.module_name = module_ident,
|
|
.list = list_ident,
|
|
.box = box_ident,
|
|
};
|
|
|
|
// 3. Parse
|
|
var parse_ast = try gpa.create(parse.AST);
|
|
defer {
|
|
parse_ast.deinit(gpa);
|
|
gpa.destroy(parse_ast);
|
|
}
|
|
|
|
parse_ast.* = try parse.parse(&module_env.common, gpa);
|
|
parse_ast.store.emptyScratch();
|
|
|
|
// Check for parse errors
|
|
if (parse_ast.hasErrors()) {
|
|
std.debug.print("Parse errors in {s}:\n", .{module_name});
|
|
for (parse_ast.tokenize_diagnostics.items) |diag| {
|
|
std.debug.print(" Tokenize error: {any}\n", .{diag});
|
|
}
|
|
for (parse_ast.parse_diagnostics.items) |diag| {
|
|
std.debug.print(" Parse error: {any}\n", .{diag});
|
|
}
|
|
return error.ParseError;
|
|
}
|
|
|
|
// 4. Create module imports map (for cross-module references)
|
|
var module_envs = std.StringHashMap(*const ModuleEnv).init(gpa);
|
|
defer module_envs.deinit();
|
|
|
|
// Add dependencies (e.g., Dict for Set)
|
|
for (deps) |dep| {
|
|
try module_envs.put(dep.name, dep.env);
|
|
}
|
|
|
|
// 5. Canonicalize
|
|
try module_env.initCIRFields(gpa, module_name);
|
|
|
|
var can_result = try gpa.create(Can);
|
|
defer {
|
|
can_result.deinit();
|
|
gpa.destroy(can_result);
|
|
}
|
|
|
|
can_result.* = try Can.init(module_env, parse_ast, &module_envs, can_options);
|
|
|
|
try can_result.canonicalizeFile();
|
|
try can_result.validateForChecking();
|
|
|
|
// 6. Type check
|
|
// Build the list of other modules for type checking
|
|
var other_modules = std.ArrayList(*const ModuleEnv).init(gpa);
|
|
defer other_modules.deinit();
|
|
|
|
// Add dependencies
|
|
for (deps) |dep| {
|
|
try other_modules.append(dep.env);
|
|
}
|
|
|
|
var checker = try Check.init(
|
|
gpa,
|
|
&module_env.types,
|
|
module_env,
|
|
other_modules.items,
|
|
&module_env.store.regions,
|
|
common_idents,
|
|
);
|
|
defer checker.deinit();
|
|
|
|
try checker.checkFile();
|
|
|
|
// Check for type errors
|
|
if (checker.problems.problems.items.len > 0) {
|
|
std.debug.print("Type errors found in {s}:\n", .{module_name});
|
|
for (checker.problems.problems.items) |prob| {
|
|
std.debug.print(" - Problem: {any}\n", .{prob});
|
|
}
|
|
return error.TypeCheckError;
|
|
}
|
|
|
|
return module_env;
|
|
}
|
|
|
|
fn serializeModuleEnv(
|
|
gpa: Allocator,
|
|
env: *const ModuleEnv,
|
|
output_path: []const u8,
|
|
) !void {
|
|
// This follows the pattern from module_env_test.zig
|
|
|
|
var arena = std.heap.ArenaAllocator.init(gpa);
|
|
defer arena.deinit();
|
|
const arena_alloc = arena.allocator();
|
|
|
|
// Create output file
|
|
const file = try std.fs.cwd().createFile(output_path, .{ .read = true });
|
|
defer file.close();
|
|
|
|
// Serialize using CompactWriter
|
|
var writer = collections.CompactWriter.init();
|
|
defer writer.deinit(arena_alloc);
|
|
|
|
const serialized = try writer.appendAlloc(arena_alloc, ModuleEnv.Serialized);
|
|
try serialized.serialize(env, arena_alloc, &writer);
|
|
|
|
// Write to file
|
|
try writer.writeGather(arena_alloc, file);
|
|
}
|