zig/lib/std / zig.zig

Builds of the Zig compiler are distributed partly in source form. That source lives here. These APIs are provided as-is and have absolutely no API guarantees whatsoever.

//! Builds of the Zig compiler are distributed partly in source form. That
//! source lives here. These APIs are provided as-is and have absolutely no API
//! guarantees whatsoever.

ErrorBundle

zig/ErrorBundle.zig

Determine whether stderr is a terminal or not automatically.


const std = @import("std.zig");
const tokenizer = @import("zig/tokenizer.zig");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const Writer = std.Io.Writer;

Server

zig/Server.zig

Assume stderr is not a terminal.


pub const ErrorBundle = @import("zig/ErrorBundle.zig");
pub const Server = @import("zig/Server.zig");

Client

zig/Client.zig

Assume stderr is a terminal.

pub const Client = @import("zig/Client.zig");

Token

There are many assumptions in the entire codebase that Zig source files can be byte-indexed with a u32 integer.

pub const Token = tokenizer.Token;

Tokenizer

Does not include the trailing newline.

pub const Tokenizer = tokenizer.Tokenizer;

string_literal

zig/string_literal.zig

Returns the standard file system basename of a binary generated by the Zig compiler.

pub const string_literal = @import("zig/string_literal.zig");

number_literal

zig/number_literal.zig

Result is byte values, *not* hex-encoded.

pub const number_literal = @import("zig/number_literal.zig");

primitives

zig/primitives.zig

Input is byte values, *not* hex-encoded. Asserts bytes fits inside HexString

pub const primitives = @import("zig/primitives.zig");

isPrimitive

Converts UTF-8 text to a BuildId.

pub const isPrimitive = primitives.isPrimitive;

Ast

zig/Ast.zig

Renders a std.Target.Cpu value into a textual representation that can be parsed via the -mcpu flag passed to the Zig compiler. Appends the result to buffer.

pub const Ast = @import("zig/Ast.zig");

AstGen

zig/AstGen.zig

Return a Formatter for a Zig identifier, escaping it with @"" syntax if needed. See also fmtIdFlags.

pub const AstGen = @import("zig/AstGen.zig");

Zir

zig/Zir.zig

Return a Formatter for a Zig identifier, escaping it with @"" syntax if needed. See also fmtId.

pub const Zir = @import("zig/Zir.zig");

Zoir

zig/Zoir.zig

Print the string as a Zig identifier, escaping it with @"" syntax if needed.

pub const Zoir = @import("zig/Zoir.zig");

ZonGen

zig/ZonGen.zig

Return a formatter for escaping a double quoted Zig string.

pub const ZonGen = @import("zig/ZonGen.zig");

system

zig/system.zig

Return a formatter for escaping a single quoted Zig string.

pub const system = @import("zig/system.zig");

BuiltinFn

zig/BuiltinFn.zig

Print the string as escaped contents of a double quoted string.

pub const BuiltinFn = @import("zig/BuiltinFn.zig");

AstRlAnnotate

zig/AstRlAnnotate.zig

Print as escaped contents of a single-quoted string.

pub const AstRlAnnotate = @import("zig/AstRlAnnotate.zig");

LibCInstallation

zig/LibCInstallation.zig

Collects all the environment variables that Zig could possibly inspect, so that we can do reflection on this and print them with zig env.

pub const LibCInstallation = @import("zig/LibCInstallation.zig");

WindowsSdk

zig/WindowsSdk.zig

Every kind of artifact which the compiler can emit.

pub const WindowsSdk = @import("zig/WindowsSdk.zig");

LibCDirs

zig/LibCDirs.zig

If using Server to communicate with the compiler, it will place requested artifacts in paths under the output directory, where those paths are named according to this function. Returned string is allocated with gpa and owned by the caller.

pub const LibCDirs = @import("zig/LibCDirs.zig");

target

zig/target.zig
pub const target = @import("zig/target.zig");

llvm

zig/llvm.zig
pub const llvm = @import("zig/llvm.zig");

ParsedCharLiteral


// Character literal parsing
pub const ParsedCharLiteral = string_literal.ParsedCharLiteral;

parseCharLiteral

pub const parseCharLiteral = string_literal.parseCharLiteral;

parseNumberLiteral

pub const parseNumberLiteral = number_literal.parseNumberLiteral;

c_builtins

zig/c_builtins.zig

// Files needed by translate-c.
pub const c_builtins = @import("zig/c_builtins.zig");

c_translation

zig/c_translation.zig
pub const c_translation = @import("zig/c_translation.zig");

SrcHasher


pub const SrcHasher = std.crypto.hash.Blake3;

SrcHash

pub const SrcHash = [16]u8;

Color


pub const Color = enum {
    /// Determine whether stderr is a terminal or not automatically.
    auto,
    /// Assume stderr is not a terminal.
    off,
    /// Assume stderr is a terminal.
    on,

get_tty_conf()


    pub fn get_tty_conf(color: Color) std.io.tty.Config {
        return switch (color) {
            .auto => std.io.tty.detectConfig(std.fs.File.stderr()),
            .on => .escape_codes,
            .off => .no_color,
        };
    }

renderOptions()


    pub fn renderOptions(color: Color) std.zig.ErrorBundle.RenderOptions {
        return .{
            .ttyconf = get_tty_conf(color),
        };
    }

LtoMode

};

hashSrc()


/// There are many assumptions in the entire codebase that Zig source files can
/// be byte-indexed with a u32 integer.
pub const max_src_size = std.math.maxInt(u32);

srcHashEql()


pub fn hashSrc(src: []const u8) SrcHash {
    var out: SrcHash = undefined;
    SrcHasher.hash(src, &out, .{});
    return out;

binNameAlloc()

}

Loc


pub fn srcHashEql(a: SrcHash, b: SrcHash) bool {
    return @as(u128, @bitCast(a)) == @as(u128, @bitCast(b));

binNameAlloc()

}

findLineColumn()


pub fn hashName(parent_hash: SrcHash, sep: []const u8, name: []const u8) SrcHash {
    var out: SrcHash = undefined;
    var hasher = SrcHasher.init(.{});
    hasher.update(&parent_hash);
    hasher.update(sep);
    hasher.update(name);
    hasher.final(&out);
    return out;

binNameAlloc()

}

BinNameOptions


pub const Loc = struct {
    line: usize,
    column: usize,
    /// Does not include the trailing newline.
    source_line: []const u8,

binNameAlloc()


    pub fn eql(a: Loc, b: Loc) bool {
        return a.line == b.line and a.column == b.column and std.mem.eql(u8, a.source_line, b.source_line);
    }

LtoMode

};

BuildId


pub fn findLineColumn(source: []const u8, byte_offset: usize) Loc {
    var line: usize = 0;
    var column: usize = 0;
    var line_start: usize = 0;
    var i: usize = 0;
    while (i < byte_offset) : (i += 1) {
        switch (source[i]) {
            '\n' => {
                line += 1;
                column = 0;
                line_start = i + 1;
            },
            else => {
                column += 1;
            },
        }
    }
    while (i < source.len and source[i] != '\n') {
        i += 1;
    }
    return .{
        .line = line,
        .column = column,
        .source_line = source[line_start..i],
    };

LtoMode

}

HexString


pub fn lineDelta(source: []const u8, start: usize, end: usize) isize {
    var line: isize = 0;
    if (end >= start) {
        for (source[start..end]) |byte| switch (byte) {
            '\n' => line += 1,
            else => continue,
        };
    } else {
        for (source[end..start]) |byte| switch (byte) {
            '\n' => line -= 1,
            else => continue,
        };
    }
    return line;

LtoMode

}

initHexString()


pub const BinNameOptions = struct {
    root_name: []const u8,
    target: *const std.Target,
    output_mode: std.builtin.OutputMode,
    link_mode: ?std.builtin.LinkMode = null,
    version: ?std.SemanticVersion = null,

LtoMode

};

Test: parse


/// Returns the standard file system basename of a binary generated by the Zig compiler.
pub fn binNameAlloc(allocator: Allocator, options: BinNameOptions) error{OutOfMemory}![]u8 {
    const root_name = options.root_name;
    const t = options.target;
    switch (t.ofmt) {
        .coff => switch (options.output_mode) {
            .Exe => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, t.exeFileExt() }),
            .Lib => {
                const suffix = switch (options.link_mode orelse .static) {
                    .static => ".lib",
                    .dynamic => ".dll",
                };
                return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, suffix });
            },
            .Obj => return std.fmt.allocPrint(allocator, "{s}.obj", .{root_name}),
        },
        .elf, .goff, .xcoff => switch (options.output_mode) {
            .Exe => return allocator.dupe(u8, root_name),
            .Lib => {
                switch (options.link_mode orelse .static) {
                    .static => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{
                        t.libPrefix(), root_name,
                    }),
                    .dynamic => {
                        if (options.version) |ver| {
                            return std.fmt.allocPrint(allocator, "{s}{s}.so.{d}.{d}.{d}", .{
                                t.libPrefix(), root_name, ver.major, ver.minor, ver.patch,
                            });
                        } else {
                            return std.fmt.allocPrint(allocator, "{s}{s}.so", .{
                                t.libPrefix(), root_name,
                            });
                        }
                    },
                }
            },
            .Obj => return std.fmt.allocPrint(allocator, "{s}.o", .{root_name}),
        },
        .macho => switch (options.output_mode) {
            .Exe => return allocator.dupe(u8, root_name),
            .Lib => {
                switch (options.link_mode orelse .static) {
                    .static => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{
                        t.libPrefix(), root_name,
                    }),
                    .dynamic => {
                        if (options.version) |ver| {
                            return std.fmt.allocPrint(allocator, "{s}{s}.{d}.{d}.{d}.dylib", .{
                                t.libPrefix(), root_name, ver.major, ver.minor, ver.patch,
                            });
                        } else {
                            return std.fmt.allocPrint(allocator, "{s}{s}.dylib", .{
                                t.libPrefix(), root_name,
                            });
                        }
                    },
                }
            },
            .Obj => return std.fmt.allocPrint(allocator, "{s}.o", .{root_name}),
        },
        .wasm => switch (options.output_mode) {
            .Exe => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, t.exeFileExt() }),
            .Lib => {
                switch (options.link_mode orelse .static) {
                    .static => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{
                        t.libPrefix(), root_name,
                    }),
                    .dynamic => return std.fmt.allocPrint(allocator, "{s}.wasm", .{root_name}),
                }
            },
            .Obj => return std.fmt.allocPrint(allocator, "{s}.o", .{root_name}),
        },
        .c => return std.fmt.allocPrint(allocator, "{s}.c", .{root_name}),
        .spirv => return std.fmt.allocPrint(allocator, "{s}.spv", .{root_name}),
        .hex => return std.fmt.allocPrint(allocator, "{s}.ihex", .{root_name}),
        .raw => return std.fmt.allocPrint(allocator, "{s}.bin", .{root_name}),
        .plan9 => switch (options.output_mode) {
            .Exe => return allocator.dupe(u8, root_name),
            .Obj => return std.fmt.allocPrint(allocator, "{s}{s}", .{
                root_name, t.ofmt.fileExt(t.cpu.arch),
            }),
            .Lib => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{
                t.libPrefix(), root_name,
            }),
        },
    }

LtoMode

}

Test: format


pub const SanitizeC = enum {
    off,
    trap,
    full,

LtoMode

};

serializeCpu()


pub const BuildId = union(enum) {
    none,
    fast,
    uuid,
    sha1,
    md5,
    hexstring: HexString,

serializeCpuAlloc()


    pub fn eql(a: BuildId, b: BuildId) bool {
        const Tag = @typeInfo(BuildId).@"union".tag_type.?;
        const a_tag: Tag = a;
        const b_tag: Tag = b;
        if (a_tag != b_tag) return false;
        return switch (a) {
            .none, .fast, .uuid, .sha1, .md5 => true,
            .hexstring => |a_hexstring| std.mem.eql(u8, a_hexstring.toSlice(), b.hexstring.toSlice()),
        };
    }

fmtId()


    pub const HexString = struct {
        bytes: [32]u8,
        len: u8,

fmtIdFlags()


        /// Result is byte values, *not* hex-encoded.
        pub fn toSlice(hs: *const HexString) []const u8 {
            return hs.bytes[0..hs.len];
        }
    };

fmtIdPU()


    /// Input is byte values, *not* hex-encoded.
    /// Asserts `bytes` fits inside `HexString`
    pub fn initHexString(bytes: []const u8) BuildId {
        var result: BuildId = .{ .hexstring = .{
            .bytes = undefined,
            .len = @intCast(bytes.len),
        } };
        @memcpy(result.hexstring.bytes[0..bytes.len], bytes);
        return result;
    }

fmtIdP()


    /// Converts UTF-8 text to a `BuildId`.
    pub fn parse(text: []const u8) !BuildId {
        if (std.mem.eql(u8, text, "none")) {
            return .none;
        } else if (std.mem.eql(u8, text, "fast")) {
            return .fast;
        } else if (std.mem.eql(u8, text, "uuid")) {
            return .uuid;
        } else if (std.mem.eql(u8, text, "sha1") or std.mem.eql(u8, text, "tree")) {
            return .sha1;
        } else if (std.mem.eql(u8, text, "md5")) {
            return .md5;
        } else if (std.mem.startsWith(u8, text, "0x")) {
            var result: BuildId = .{ .hexstring = undefined };
            const slice = try std.fmt.hexToBytes(&result.hexstring.bytes, text[2..]);
            result.hexstring.len = @as(u8, @intCast(slice.len));
            return result;
        }
        return error.InvalidBuildIdStyle;
    }

Test: fmtId


    test parse {
        try std.testing.expectEqual(BuildId.md5, try parse("md5"));
        try std.testing.expectEqual(BuildId.none, try parse("none"));
        try std.testing.expectEqual(BuildId.fast, try parse("fast"));
        try std.testing.expectEqual(BuildId.uuid, try parse("uuid"));
        try std.testing.expectEqual(BuildId.sha1, try parse("sha1"));
        try std.testing.expectEqual(BuildId.sha1, try parse("tree"));

FormatId


        try std.testing.expect(BuildId.initHexString("").eql(try parse("0x")));
        try std.testing.expect(BuildId.initHexString("\x12\x34\x56").eql(try parse("0x123456")));
        try std.testing.expectError(error.InvalidLength, parse("0x12-34"));
        try std.testing.expectError(error.InvalidCharacter, parse("0xfoobbb"));
        try std.testing.expectError(error.InvalidBuildIdStyle, parse("yaddaxxx"));
    }

Flags


    pub fn format(id: BuildId, writer: *std.io.Writer) std.io.Writer.Error!void {
        switch (id) {
            .none, .fast, .uuid, .sha1, .md5 => {
                try writer.writeAll(@tagName(id));
            },
            .hexstring => |hs| {
                try writer.print("0x{x}", .{hs.toSlice()});
            },
        }
    }

format()


    test format {
        try std.testing.expectFmt("none", "{f}", .{@as(BuildId, .none)});
        try std.testing.expectFmt("fast", "{f}", .{@as(BuildId, .fast)});
        try std.testing.expectFmt("uuid", "{f}", .{@as(BuildId, .uuid)});
        try std.testing.expectFmt("sha1", "{f}", .{@as(BuildId, .sha1)});
        try std.testing.expectFmt("md5", "{f}", .{@as(BuildId, .md5)});
        try std.testing.expectFmt("0x", "{f}", .{BuildId.initHexString("")});
        try std.testing.expectFmt("0x1234cdef", "{f}", .{BuildId.initHexString("\x12\x34\xcd\xef")});
    }
};

fmtString()


pub const LtoMode = enum { none, full, thin };

fmtChar()


/// Renders a `std.Target.Cpu` value into a textual representation that can be parsed
/// via the `-mcpu` flag passed to the Zig compiler.
/// Appends the result to `buffer`.
pub fn serializeCpu(buffer: *std.array_list.Managed(u8), cpu: std.Target.Cpu) Allocator.Error!void {
    const all_features = cpu.arch.allFeaturesList();
    var populated_cpu_features = cpu.model.features;
    populated_cpu_features.populateDependencies(all_features);

Test: fmtString


    try buffer.appendSlice(cpu.model.name);

Test: fmtChar


    if (populated_cpu_features.eql(cpu.features)) {
        // The CPU name alone is sufficient.
        return;
    }

stringEscape()


    for (all_features, 0..) |feature, i_usize| {
        const i: std.Target.Cpu.Feature.Set.Index = @intCast(i_usize);
        const in_cpu_set = populated_cpu_features.isEnabled(i);
        const in_actual_set = cpu.features.isEnabled(i);
        try buffer.ensureUnusedCapacity(feature.name.len + 1);
        if (in_cpu_set and !in_actual_set) {
            buffer.appendAssumeCapacity('-');
            buffer.appendSliceAssumeCapacity(feature.name);
        } else if (!in_cpu_set and in_actual_set) {
            buffer.appendAssumeCapacity('+');
            buffer.appendSliceAssumeCapacity(feature.name);
        }
    }
}

charEscape()


pub fn serializeCpuAlloc(ally: Allocator, cpu: std.Target.Cpu) Allocator.Error![]u8 {
    var buffer = std.array_list.Managed(u8).init(ally);
    try serializeCpu(&buffer, cpu);
    return buffer.toOwnedSlice();
}

isValidId()


/// Return a Formatter for a Zig identifier, escaping it with `@""` syntax if needed.
///
/// See also `fmtIdFlags`.
pub fn fmtId(bytes: []const u8) FormatId {
    return .{ .bytes = bytes, .flags = .{} };
}

Test: isValidId


/// Return a Formatter for a Zig identifier, escaping it with `@""` syntax if needed.
///
/// See also `fmtId`.
pub fn fmtIdFlags(bytes: []const u8, flags: FormatId.Flags) FormatId {
    return .{ .bytes = bytes, .flags = flags };
}

isUnderscore()


pub fn fmtIdPU(bytes: []const u8) FormatId {
    return .{ .bytes = bytes, .flags = .{ .allow_primitive = true, .allow_underscore = true } };
}

Test: isUnderscore


pub fn fmtIdP(bytes: []const u8) FormatId {
    return .{ .bytes = bytes, .flags = .{ .allow_primitive = true } };
}

readSourceFileToEndAlloc()


test fmtId {
    const expectFmt = std.testing.expectFmt;
    try expectFmt("@\"while\"", "{f}", .{fmtId("while")});
    try expectFmt("@\"while\"", "{f}", .{fmtIdFlags("while", .{ .allow_primitive = true })});
    try expectFmt("@\"while\"", "{f}", .{fmtIdFlags("while", .{ .allow_underscore = true })});
    try expectFmt("@\"while\"", "{f}", .{fmtIdFlags("while", .{ .allow_primitive = true, .allow_underscore = true })});

printAstErrorsToStderr()


    try expectFmt("hello", "{f}", .{fmtId("hello")});
    try expectFmt("hello", "{f}", .{fmtIdFlags("hello", .{ .allow_primitive = true })});
    try expectFmt("hello", "{f}", .{fmtIdFlags("hello", .{ .allow_underscore = true })});
    try expectFmt("hello", "{f}", .{fmtIdFlags("hello", .{ .allow_primitive = true, .allow_underscore = true })});

putAstErrorsIntoBundle()


    try expectFmt("@\"type\"", "{f}", .{fmtId("type")});
    try expectFmt("type", "{f}", .{fmtIdFlags("type", .{ .allow_primitive = true })});
    try expectFmt("@\"type\"", "{f}", .{fmtIdFlags("type", .{ .allow_underscore = true })});
    try expectFmt("type", "{f}", .{fmtIdFlags("type", .{ .allow_primitive = true, .allow_underscore = true })});

resolveTargetQueryOrFatal()


    try expectFmt("@\"_\"", "{f}", .{fmtId("_")});
    try expectFmt("@\"_\"", "{f}", .{fmtIdFlags("_", .{ .allow_primitive = true })});
    try expectFmt("_", "{f}", .{fmtIdFlags("_", .{ .allow_underscore = true })});
    try expectFmt("_", "{f}", .{fmtIdFlags("_", .{ .allow_primitive = true, .allow_underscore = true })});

parseTargetQueryOrReportFatalError()


    try expectFmt("@\"i123\"", "{f}", .{fmtId("i123")});
    try expectFmt("i123", "{f}", .{fmtIdFlags("i123", .{ .allow_primitive = true })});
    try expectFmt("@\"4four\"", "{f}", .{fmtId("4four")});
    try expectFmt("_underscore", "{f}", .{fmtId("_underscore")});
    try expectFmt("@\"11\\\"23\"", "{f}", .{fmtId("11\"23")});
    try expectFmt("@\"11\\x0f23\"", "{f}", .{fmtId("11\x0F23")});

EnvVar


    // These are technically not currently legal in Zig.
    try expectFmt("@\"\"", "{f}", .{fmtId("")});
    try expectFmt("@\"\\x00\"", "{f}", .{fmtId("\x00")});
}

isSet()


pub const FormatId = struct {
    bytes: []const u8,
    flags: Flags,
    pub const Flags = struct {
        allow_primitive: bool = false,
        allow_underscore: bool = false,
    };

get()


    /// Print the string as a Zig identifier, escaping it with `@""` syntax if needed.
    pub fn format(ctx: FormatId, writer: *Writer) Writer.Error!void {
        const bytes = ctx.bytes;
        if (isValidId(bytes) and
            (ctx.flags.allow_primitive or !std.zig.isPrimitive(bytes)) and
            (ctx.flags.allow_underscore or !isUnderscore(bytes)))
        {
            return writer.writeAll(bytes);
        }
        try writer.writeAll("@\"");
        try stringEscape(bytes, writer);
        try writer.writeByte('"');
    }
};

getPosix()


/// Return a formatter for escaping a double quoted Zig string.
pub fn fmtString(bytes: []const u8) std.fmt.Formatter([]const u8, stringEscape) {
    return .{ .data = bytes };
}

SimpleComptimeReason


/// Return a formatter for escaping a single quoted Zig string.
pub fn fmtChar(c: u21) std.fmt.Formatter(u21, charEscape) {
    return .{ .data = c };
}

message()


test fmtString {
    try std.testing.expectFmt("\\x0f", "{f}", .{fmtString("\x0f")});
    try std.testing.expectFmt(
        \\" \\ hi \x07 \x11 \" derp '"
    , "\"{f}\"", .{fmtString(" \\ hi \x07 \x11 \" derp '")});
}

EmitArtifact


test fmtChar {
    try std.testing.expectFmt("c \\u{26a1}", "{f} {f}", .{ fmtChar('c'), fmtChar('⚡') });
}

cacheName()


/// Print the string as escaped contents of a double quoted string.
pub fn stringEscape(bytes: []const u8, w: *Writer) Writer.Error!void {
    for (bytes) |byte| switch (byte) {
        '\n' => try w.writeAll("\\n"),
        '\r' => try w.writeAll("\\r"),
        '\t' => try w.writeAll("\\t"),
        '\\' => try w.writeAll("\\\\"),
        '"' => try w.writeAll("\\\""),
        '\'' => try w.writeByte('\''),
        ' ', '!', '#'...'&', '('...'[', ']'...'~' => try w.writeByte(byte),
        else => {
            try w.writeAll("\\x");
            try w.printInt(byte, 16, .lower, .{ .width = 2, .fill = '0' });
        },
    };
}

/// Print as escaped contents of a single-quoted string.
pub fn charEscape(codepoint: u21, w: *Writer) Writer.Error!void {
    switch (codepoint) {
        '\n' => try w.writeAll("\\n"),
        '\r' => try w.writeAll("\\r"),
        '\t' => try w.writeAll("\\t"),
        '\\' => try w.writeAll("\\\\"),
        '\'' => try w.writeAll("\\'"),
        '"', ' ', '!', '#'...'&', '('...'[', ']'...'~' => try w.writeByte(@intCast(codepoint)),
        else => {
            if (std.math.cast(u8, codepoint)) |byte| {
                try w.writeAll("\\x");
                try w.printInt(byte, 16, .lower, .{ .width = 2, .fill = '0' });
            } else {
                try w.writeAll("\\u{");
                try w.printInt(codepoint, 16, .lower, .{});
                try w.writeByte('}');
            }
        },
    }
}

pub fn isValidId(bytes: []const u8) bool {
    if (bytes.len == 0) return false;
    for (bytes, 0..) |c, i| {
        switch (c) {
            '_', 'a'...'z', 'A'...'Z' => {},
            '0'...'9' => if (i == 0) return false,
            else => return false,
        }
    }
    return std.zig.Token.getKeyword(bytes) == null;
}

test isValidId {
    try std.testing.expect(!isValidId(""));
    try std.testing.expect(isValidId("foobar"));
    try std.testing.expect(!isValidId("a b c"));
    try std.testing.expect(!isValidId("3d"));
    try std.testing.expect(!isValidId("enum"));
    try std.testing.expect(isValidId("i386"));
}

pub fn isUnderscore(bytes: []const u8) bool {
    return bytes.len == 1 and bytes[0] == '_';
}

test isUnderscore {
    try std.testing.expect(isUnderscore("_"));
    try std.testing.expect(!isUnderscore("__"));
    try std.testing.expect(!isUnderscore("_foo"));
    try std.testing.expect(isUnderscore("\x5f"));
    try std.testing.expect(!isUnderscore("\\x5f"));
}

pub fn readSourceFileToEndAlloc(gpa: Allocator, file_reader: *std.fs.File.Reader) ![:0]u8 {
    var buffer: std.ArrayListAlignedUnmanaged(u8, .@"2") = .empty;
    defer buffer.deinit(gpa);

    if (file_reader.getSize()) |size| {
        const casted_size = std.math.cast(u32, size) orelse return error.StreamTooLong;
        // +1 to avoid resizing for the null byte added in toOwnedSliceSentinel below.
        try buffer.ensureTotalCapacityPrecise(gpa, casted_size + 1);
    } else |_| {}

    try file_reader.interface.appendRemaining(gpa, .@"2", &buffer, .limited(max_src_size));

    // Detect unsupported file types with their Byte Order Mark
    const unsupported_boms = [_][]const u8{
        "\xff\xfe\x00\x00", // UTF-32 little endian
        "\xfe\xff\x00\x00", // UTF-32 big endian
        "\xfe\xff", // UTF-16 big endian
    };
    for (unsupported_boms) |bom| {
        if (std.mem.startsWith(u8, buffer.items, bom)) {
            return error.UnsupportedEncoding;
        }
    }

    // If the file starts with a UTF-16 little endian BOM, translate it to UTF-8
    if (std.mem.startsWith(u8, buffer.items, "\xff\xfe")) {
        if (buffer.items.len % 2 != 0) return error.InvalidEncoding;
        return std.unicode.utf16LeToUtf8AllocZ(gpa, @ptrCast(buffer.items)) catch |err| switch (err) {
            error.DanglingSurrogateHalf => error.UnsupportedEncoding,
            error.ExpectedSecondSurrogateHalf => error.UnsupportedEncoding,
            error.UnexpectedSecondSurrogateHalf => error.UnsupportedEncoding,
            else => |e| return e,
        };
    }

    return buffer.toOwnedSliceSentinel(gpa, 0);
}

pub fn printAstErrorsToStderr(gpa: Allocator, tree: Ast, path: []const u8, color: Color) !void {
    var wip_errors: std.zig.ErrorBundle.Wip = undefined;
    try wip_errors.init(gpa);
    defer wip_errors.deinit();

    try putAstErrorsIntoBundle(gpa, tree, path, &wip_errors);

    var error_bundle = try wip_errors.toOwnedBundle("");
    defer error_bundle.deinit(gpa);
    error_bundle.renderToStdErr(color.renderOptions());
}

pub fn putAstErrorsIntoBundle(
    gpa: Allocator,
    tree: Ast,
    path: []const u8,
    wip_errors: *std.zig.ErrorBundle.Wip,
) Allocator.Error!void {
    var zir = try AstGen.generate(gpa, tree);
    defer zir.deinit(gpa);

    try wip_errors.addZirErrorMessages(zir, tree, tree.source, path);
}

pub fn resolveTargetQueryOrFatal(target_query: std.Target.Query) std.Target {
    return std.zig.system.resolveTargetQuery(target_query) catch |err|
        std.process.fatal("unable to resolve target: {s}", .{@errorName(err)});
}

pub fn parseTargetQueryOrReportFatalError(
    allocator: Allocator,
    opts: std.Target.Query.ParseOptions,
) std.Target.Query {
    var opts_with_diags = opts;
    var diags: std.Target.Query.ParseOptions.Diagnostics = .{};
    if (opts_with_diags.diagnostics == null) {
        opts_with_diags.diagnostics = &diags;
    }
    return std.Target.Query.parse(opts_with_diags) catch |err| switch (err) {
        error.UnknownCpuModel => {
            help: {
                var help_text = std.array_list.Managed(u8).init(allocator);
                defer help_text.deinit();
                for (diags.arch.?.allCpuModels()) |cpu| {
                    help_text.print(" {s}\n", .{cpu.name}) catch break :help;
                }
                std.log.info("available CPUs for architecture '{s}':\n{s}", .{
                    @tagName(diags.arch.?), help_text.items,
                });
            }
            std.process.fatal("unknown CPU: '{s}'", .{diags.cpu_name.?});
        },
        error.UnknownCpuFeature => {
            help: {
                var help_text = std.array_list.Managed(u8).init(allocator);
                defer help_text.deinit();
                for (diags.arch.?.allFeaturesList()) |feature| {
                    help_text.print(" {s}: {s}\n", .{ feature.name, feature.description }) catch break :help;
                }
                std.log.info("available CPU features for architecture '{s}':\n{s}", .{
                    @tagName(diags.arch.?), help_text.items,
                });
            }
            std.process.fatal("unknown CPU feature: '{s}'", .{diags.unknown_feature_name.?});
        },
        error.UnknownObjectFormat => {
            help: {
                var help_text = std.array_list.Managed(u8).init(allocator);
                defer help_text.deinit();
                inline for (@typeInfo(std.Target.ObjectFormat).@"enum".fields) |field| {
                    help_text.print(" {s}\n", .{field.name}) catch break :help;
                }
                std.log.info("available object formats:\n{s}", .{help_text.items});
            }
            std.process.fatal("unknown object format: '{s}'", .{opts.object_format.?});
        },
        error.UnknownArchitecture => {
            help: {
                var help_text = std.array_list.Managed(u8).init(allocator);
                defer help_text.deinit();
                inline for (@typeInfo(std.Target.Cpu.Arch).@"enum".fields) |field| {
                    help_text.print(" {s}\n", .{field.name}) catch break :help;
                }
                std.log.info("available architectures:\n{s} native\n", .{help_text.items});
            }
            std.process.fatal("unknown architecture: '{s}'", .{diags.unknown_architecture_name.?});
        },
        else => |e| std.process.fatal("unable to parse target query '{s}': {s}", .{
            opts.arch_os_abi, @errorName(e),
        }),
    };
}

/// Collects all the environment variables that Zig could possibly inspect, so
/// that we can do reflection on this and print them with `zig env`.
pub const EnvVar = enum {
    ZIG_GLOBAL_CACHE_DIR,
    ZIG_LOCAL_CACHE_DIR,
    ZIG_LIB_DIR,
    ZIG_LIBC,
    ZIG_BUILD_RUNNER,
    ZIG_VERBOSE_LINK,
    ZIG_VERBOSE_CC,
    ZIG_BTRFS_WORKAROUND,
    ZIG_DEBUG_CMD,
    CC,
    NO_COLOR,
    CLICOLOR_FORCE,
    XDG_CACHE_HOME,
    HOME,

    pub fn isSet(comptime ev: EnvVar) bool {
        return std.process.hasNonEmptyEnvVarConstant(@tagName(ev));
    }

    pub fn get(ev: EnvVar, arena: std.mem.Allocator) !?[]u8 {
        if (std.process.getEnvVarOwned(arena, @tagName(ev))) |value| {
            return value;
        } else |err| switch (err) {
            error.EnvironmentVariableNotFound => return null,
            else => |e| return e,
        }
    }

    pub fn getPosix(comptime ev: EnvVar) ?[:0]const u8 {
        return std.posix.getenvZ(@tagName(ev));
    }
};

pub const SimpleComptimeReason = enum(u32) {
    // Evaluating at comptime because a builtin operand must be comptime-known.
    // These messages all mention a specific builtin.
    operand_Type,
    operand_setEvalBranchQuota,
    operand_setFloatMode,
    operand_branchHint,
    operand_setRuntimeSafety,
    operand_embedFile,
    operand_cImport,
    operand_cDefine_macro_name,
    operand_cDefine_macro_value,
    operand_cInclude_file_name,
    operand_cUndef_macro_name,
    operand_shuffle_mask,
    operand_atomicRmw_operation,
    operand_reduce_operation,

    // Evaluating at comptime because an operand must be comptime-known.
    // These messages do not mention a specific builtin (and may not be about a builtin at all).
    export_target,
    export_options,
    extern_options,
    prefetch_options,
    call_modifier,
    compile_error_string,
    inline_assembly_code,
    atomic_order,
    array_mul_factor,
    slice_cat_operand,
    inline_call_target,
    generic_call_target,
    wasm_memory_index,
    work_group_dim_index,
    clobber,

    // Evaluating at comptime because types must be comptime-known.
    // Reasons other than `.type` are just more specific messages.
    type,
    array_sentinel,
    pointer_sentinel,
    slice_sentinel,
    array_length,
    vector_length,
    error_set_contents,
    struct_fields,
    enum_fields,
    union_fields,
    function_ret_ty,
    function_parameters,

    // Evaluating at comptime because decl/field name must be comptime-known.
    decl_name,
    field_name,
    struct_field_name,
    enum_field_name,
    union_field_name,
    tuple_field_name,
    tuple_field_index,

    // Evaluating at comptime because it is an attribute of a global declaration.
    container_var_init,
    @"callconv",
    @"align",
    @"addrspace",
    @"linksection",

    // Miscellaneous reasons.
    comptime_keyword,
    comptime_call_modifier,
    inline_loop_operand,
    switch_item,
    tuple_field_default_value,
    struct_field_default_value,
    enum_field_tag_value,
    slice_single_item_ptr_bounds,
    stored_to_comptime_field,
    stored_to_comptime_var,
    casted_to_comptime_enum,
    casted_to_comptime_int,
    casted_to_comptime_float,
    panic_handler,

    pub fn message(r: SimpleComptimeReason) []const u8 {
        return switch (r) {
            // zig fmt: off
            .operand_Type                => "operand to '@Type' must be comptime-known",
            .operand_setEvalBranchQuota  => "operand to '@setEvalBranchQuota' must be comptime-known",
            .operand_setFloatMode        => "operand to '@setFloatMode' must be comptime-known",
            .operand_branchHint          => "operand to '@branchHint' must be comptime-known",
            .operand_setRuntimeSafety    => "operand to '@setRuntimeSafety' must be comptime-known",
            .operand_embedFile           => "operand to '@embedFile' must be comptime-known",
            .operand_cImport             => "operand to '@cImport' is evaluated at comptime",
            .operand_cDefine_macro_name  => "'@cDefine' macro name must be comptime-known",
            .operand_cDefine_macro_value => "'@cDefine' macro value must be comptime-known",
            .operand_cInclude_file_name  => "'@cInclude' file name must be comptime-known",
            .operand_cUndef_macro_name   => "'@cUndef' macro name must be comptime-known",
            .operand_shuffle_mask        => "'@shuffle' mask must be comptime-known",
            .operand_atomicRmw_operation => "'@atomicRmw' operation must be comptime-known",
            .operand_reduce_operation    => "'@reduce' operation must be comptime-known",

            .export_target        => "export target must be comptime-known",
            .export_options       => "export options must be comptime-known",
            .extern_options       => "extern options must be comptime-known",
            .prefetch_options     => "prefetch options must be comptime-known",
            .call_modifier        => "call modifier must be comptime-known",
            .compile_error_string => "compile error string must be comptime-known",
            .inline_assembly_code => "inline assembly code must be comptime-known",
            .atomic_order         => "atomic order must be comptime-known",
            .array_mul_factor     => "array multiplication factor must be comptime-known",
            .slice_cat_operand    => "slice being concatenated must be comptime-known",
            .inline_call_target   => "function being called inline must be comptime-known",
            .generic_call_target  => "generic function being called must be comptime-known",
            .wasm_memory_index    => "wasm memory index must be comptime-known",
            .work_group_dim_index => "work group dimension index must be comptime-known",
            .clobber              => "clobber must be comptime-known",

            .type                => "types must be comptime-known",
            .array_sentinel      => "array sentinel value must be comptime-known",
            .pointer_sentinel    => "pointer sentinel value must be comptime-known",
            .slice_sentinel      => "slice sentinel value must be comptime-known",
            .array_length        => "array length must be comptime-known",
            .vector_length       => "vector length must be comptime-known",
            .error_set_contents  => "error set contents must be comptime-known",
            .struct_fields       => "struct fields must be comptime-known",
            .enum_fields         => "enum fields must be comptime-known",
            .union_fields        => "union fields must be comptime-known",
            .function_ret_ty     => "function return type must be comptime-known",
            .function_parameters => "function parameters must be comptime-known",

            .decl_name         => "declaration name must be comptime-known",
            .field_name        => "field name must be comptime-known",
            .struct_field_name => "struct field name must be comptime-known",
            .enum_field_name   => "enum field name must be comptime-known",
            .union_field_name  => "union field name must be comptime-known",
            .tuple_field_name  => "tuple field name must be comptime-known",
            .tuple_field_index => "tuple field index must be comptime-known",

            .container_var_init => "initializer of container-level variable must be comptime-known",
            .@"callconv"        => "calling convention must be comptime-known",
            .@"align"           => "alignment must be comptime-known",
            .@"addrspace"       => "address space must be comptime-known",
            .@"linksection"     => "linksection must be comptime-known",

            .comptime_keyword             => "'comptime' keyword forces comptime evaluation",
            .comptime_call_modifier       => "'.compile_time' call modifier forces comptime evaluation",
            .inline_loop_operand          => "inline loop condition must be comptime-known",
            .switch_item                  => "switch prong values must be comptime-known",
            .tuple_field_default_value    => "tuple field default value must be comptime-known",
            .struct_field_default_value   => "struct field default value must be comptime-known",
            .enum_field_tag_value         => "enum field tag value must be comptime-known",
            .slice_single_item_ptr_bounds => "slice of single-item pointer must have comptime-known bounds",
            .stored_to_comptime_field     => "value stored to a comptime field must be comptime-known",
            .stored_to_comptime_var       => "value stored to a comptime variable must be comptime-known",
            .casted_to_comptime_enum      => "value casted to enum with 'comptime_int' tag type must be comptime-known",
            .casted_to_comptime_int       => "value casted to 'comptime_int' must be comptime-known",
            .casted_to_comptime_float     => "value casted to 'comptime_float' must be comptime-known",
            .panic_handler                => "panic handler must be comptime-known",
            // zig fmt: on
        };
    }
};

/// Every kind of artifact which the compiler can emit.
pub const EmitArtifact = enum {
    bin,
    @"asm",
    implib,
    llvm_ir,
    llvm_bc,
    docs,
    pdb,
    h,

    /// If using `Server` to communicate with the compiler, it will place requested artifacts in
    /// paths under the output directory, where those paths are named according to this function.
    /// Returned string is allocated with `gpa` and owned by the caller.
    pub fn cacheName(ea: EmitArtifact, gpa: Allocator, opts: BinNameOptions) Allocator.Error![]const u8 {
        const suffix: []const u8 = switch (ea) {
            .bin => return binNameAlloc(gpa, opts),
            .@"asm" => ".s",
            .implib => ".lib",
            .llvm_ir => ".ll",
            .llvm_bc => ".bc",
            .docs => "-docs",
            .pdb => ".pdb",
            .h => ".h",
        };
        return std.fmt.allocPrint(gpa, "{s}{s}", .{ opts.root_name, suffix });
    }
};

test {
    _ = Ast;
    _ = AstRlAnnotate;
    _ = BuiltinFn;
    _ = Client;
    _ = ErrorBundle;
    _ = LibCDirs;
    _ = LibCInstallation;
    _ = Server;
    _ = WindowsSdk;
    _ = number_literal;
    _ = primitives;
    _ = string_literal;
    _ = system;
    _ = target;
    _ = c_translation;
    _ = llvm;
}