// support — the Zig interop shim for the self-hosted compiler. // // This is the only selfhost module that may import Zig's std. Every other // module under selfhost/ is pure Flash and imports only `support` and its // sibling modules, so the toolchain dependency stays confined to this one // file. The surface below is the enumerated bill of materials the compiler // actually uses — additions land together with the module that needs them, // never speculatively. use std use core // Memory: the one allocator handle threaded through every stage, the // arena that backs the parser's node store (and the test harnesses that // drive it), and the only container family the compiler uses. The arena // and the list are re-routed to the pure-Flash core module; the facade // surface is unchanged. pub const Allocator = std.mem.Allocator pub const ArenaAllocator = core.arena.ArenaAllocator pub const List = core.list.List // Strings and slices. Re-routed to the pure-Flash core module; callers // pass the element type exactly as before — the facade surface is // unchanged, only the implementation behind it moved. pub const eql = core.mem.eql pub const indexOf = core.mem.indexOf pub const indexOfScalar = core.mem.indexOfScalar pub const sort = core.mem.sort pub const lessThan = core.mem.lessThan // Diagnostic formatting. Re-routed to the pure-Flash core module; the // facade surface is unchanged. pub const allocPrint = core.fmt.allocPrint // Integer parsing and bounds (the evaluator's literal folding). Re-routed // to the pure-Flash core module; the facade surface is unchanged. pub const parseInt = core.fmt.parseInt pub const minInt = core.math.minInt // Invariant assertion (sema's `locate` guards the anchor-in-buffer invariant). pub const assert = std.debug.assert // File IO and the process surface. Used by the driver module only; the // pipeline stages never touch a file or the environment. pub const Io = std.Io pub const File = std.Io.File pub const Init = std.process.Init pub const exit = std.process.exit pub fn readFile(io Io, alloc Allocator, path []u8) ![]u8 { return std.Io.Dir.cwd().readFileAlloc(io, path, alloc, .limited(1 << 20)) } pub fn writeFile(io Io, path []u8, data []u8) !void { try std.Io.Dir.cwd().writeFile(io, .{ .sub_path = path, .data = data }) } pub fn makeDirPath(io Io, path []u8) !void { try std.Io.Dir.cwd().createDirPath(io, path) } // Every `.flash` file under `root`, as paths relative to `root`, sorted so // the build driver visits them in a deterministic order. The scan walks // subdirectories with a worklist rather than recursion; visit order within // the walk does not matter because the result is sorted at the end. pub fn findFlashFiles(io Io, alloc Allocator, root []u8) ![][]u8 { var pending List([]u8) = .empty try pending.append(alloc, "") var found List([]u8) = .empty while pending.items.len > 0 { rel := pending.swapRemove(pending.items.len - 1) var dir_path []u8 = root if rel.len > 0 { dir_path = try allocPrint(alloc, "{s}/{s}", .{ root, rel }) } var dir = try std.Io.Dir.cwd().openDir(io, dir_path, .{ .iterate = true }) defer dir.close(io) var it = dir.iterate() while true { entry := try it.next(io) if entry == null { break } e := entry.? // The iterator reuses its name buffer; allocPrint copies the // bytes into the caller's arena while joining the path. var child []u8 = undefined if rel.len > 0 { child = try allocPrint(alloc, "{s}/{s}", .{ rel, e.name }) } else { child = try allocPrint(alloc, "{s}", .{e.name}) } if e.kind == .directory { try pending.append(alloc, child) } else if e.kind == .file && endsWith(child, ".flash") { try found.append(alloc, child) } } } files := try found.toOwnedSlice(alloc) const ctx i32 = 0 sort([]u8, files, ctx, pathLess) return files } fn endsWith(path []u8, suffix []u8) bool { if path.len < suffix.len { return false } return eql(u8, path[path.len - suffix.len ..], suffix) } fn pathLess(ctx i32, a []u8, b []u8) bool { _ = ctx return lessThan(u8, a, b) } // Test hooks, so the selfhost test suites need no std import of their own. pub const expect = std.testing.expect pub const expectEqual = std.testing.expectEqual pub const expectEqualSlices = std.testing.expectEqualSlices pub const expectEqualStrings = std.testing.expectEqualStrings pub const expectError = std.testing.expectError pub const testAlloc = std.testing.allocator fn ascending(ctx i32, a i32, b i32) bool { _ = ctx return a < b } test "an arena allocates through the shim" { var arena = ArenaAllocator.init(testAlloc) defer arena.deinit() s := try arena.allocator().alloc(u8, 4) try expectEqual(4, s.len) } test "a list grows through the shim" { var xs List(u8) = .empty defer xs.deinit(testAlloc) try xs.append(testAlloc, 7) try xs.appendSlice(testAlloc, "ab") try expectEqual(3, xs.items.len) try expectEqual(7, xs.items[0]) } test "string equality and search reach std" { try expect(eql(u8, "flash", "flash")) try expect(!eql(u8, "flash", "flasc")) try expectEqual(2, indexOf(u8, "hello", "ll")) try expectEqual(1, indexOfScalar(u8, "abc", 'b')) try expect(lessThan(u8, "abc", "abd")) } test "sort orders a slice through the shim" { var xs [3]i32 = .{ 3, 1, 2 } const ctx i32 = 0 sort(i32, xs[0..], ctx, ascending) try expectEqual(1, xs[0]) try expectEqual(3, xs[2]) } test "allocPrint formats through the shim" { s := try allocPrint(testAlloc, "n={d}", .{7}) defer testAlloc.free(s) try expectEqualStrings("n=7", s) } test "endsWith matches suffixes without over-reading" { try expect(endsWith("a.flash", ".flash")) try expect(!endsWith("a.zig", ".flash")) try expect(!endsWith("sh", ".flash")) } test "pathLess orders paths lexicographically" { const ctx i32 = 0 try expect(pathLess(ctx, "a.flash", "sub/b.flash")) try expect(!pathLess(ctx, "sub/b.flash", "a.flash")) } test "the driver surface is reachable" { _ = Io _ = File _ = Init _ = &exit _ = &readFile _ = &writeFile _ = &makeDirPath _ = &findFlashFiles }