ajhahn.de
← Flash
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
}