Flash 191 lines
// 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
}