mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 22:33:08 +00:00
912 lines
35 KiB
Zig
912 lines
35 KiB
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.
|
|
|
|
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;
|
|
|
|
pub const ErrorBundle = @import("zig/ErrorBundle.zig");
|
|
pub const Server = @import("zig/Server.zig");
|
|
pub const Client = @import("zig/Client.zig");
|
|
pub const Token = tokenizer.Token;
|
|
pub const Tokenizer = tokenizer.Tokenizer;
|
|
pub const string_literal = @import("zig/string_literal.zig");
|
|
pub const number_literal = @import("zig/number_literal.zig");
|
|
pub const primitives = @import("zig/primitives.zig");
|
|
pub const isPrimitive = primitives.isPrimitive;
|
|
pub const Ast = @import("zig/Ast.zig");
|
|
pub const AstGen = @import("zig/AstGen.zig");
|
|
pub const Zir = @import("zig/Zir.zig");
|
|
pub const Zoir = @import("zig/Zoir.zig");
|
|
pub const ZonGen = @import("zig/ZonGen.zig");
|
|
pub const system = @import("zig/system.zig");
|
|
pub const BuiltinFn = @import("zig/BuiltinFn.zig");
|
|
pub const AstRlAnnotate = @import("zig/AstRlAnnotate.zig");
|
|
pub const LibCInstallation = @import("zig/LibCInstallation.zig");
|
|
pub const WindowsSdk = @import("zig/WindowsSdk.zig");
|
|
pub const LibCDirs = @import("zig/LibCDirs.zig");
|
|
pub const target = @import("zig/target.zig");
|
|
pub const llvm = @import("zig/llvm.zig");
|
|
|
|
// Character literal parsing
|
|
pub const ParsedCharLiteral = string_literal.ParsedCharLiteral;
|
|
pub const parseCharLiteral = string_literal.parseCharLiteral;
|
|
pub const parseNumberLiteral = number_literal.parseNumberLiteral;
|
|
|
|
// Files needed by translate-c.
|
|
pub const c_builtins = @import("zig/c_builtins.zig");
|
|
pub const c_translation = @import("zig/c_translation.zig");
|
|
|
|
pub const SrcHasher = std.crypto.hash.Blake3;
|
|
pub const SrcHash = [16]u8;
|
|
|
|
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,
|
|
|
|
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,
|
|
};
|
|
}
|
|
|
|
pub fn renderOptions(color: Color) std.zig.ErrorBundle.RenderOptions {
|
|
return .{
|
|
.ttyconf = get_tty_conf(color),
|
|
};
|
|
}
|
|
};
|
|
|
|
/// 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);
|
|
|
|
pub fn hashSrc(src: []const u8) SrcHash {
|
|
var out: SrcHash = undefined;
|
|
SrcHasher.hash(src, &out, .{});
|
|
return out;
|
|
}
|
|
|
|
pub fn srcHashEql(a: SrcHash, b: SrcHash) bool {
|
|
return @as(u128, @bitCast(a)) == @as(u128, @bitCast(b));
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
pub const Loc = struct {
|
|
line: usize,
|
|
column: usize,
|
|
/// Does not include the trailing newline.
|
|
source_line: []const u8,
|
|
|
|
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);
|
|
}
|
|
};
|
|
|
|
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],
|
|
};
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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,
|
|
};
|
|
|
|
/// 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,
|
|
}),
|
|
},
|
|
}
|
|
}
|
|
|
|
pub const SanitizeC = enum {
|
|
off,
|
|
trap,
|
|
full,
|
|
};
|
|
|
|
pub const BuildId = union(enum) {
|
|
none,
|
|
fast,
|
|
uuid,
|
|
sha1,
|
|
md5,
|
|
hexstring: HexString,
|
|
|
|
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()),
|
|
};
|
|
}
|
|
|
|
pub const HexString = struct {
|
|
bytes: [32]u8,
|
|
len: u8,
|
|
|
|
/// Result is byte values, *not* hex-encoded.
|
|
pub fn toSlice(hs: *const HexString) []const u8 {
|
|
return hs.bytes[0..hs.len];
|
|
}
|
|
};
|
|
|
|
/// 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;
|
|
}
|
|
|
|
/// 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 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"));
|
|
|
|
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"));
|
|
}
|
|
};
|
|
|
|
pub const LtoMode = enum { none, full, thin };
|
|
|
|
/// 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.ArrayList(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);
|
|
|
|
try buffer.appendSlice(cpu.model.name);
|
|
|
|
if (populated_cpu_features.eql(cpu.features)) {
|
|
// The CPU name alone is sufficient.
|
|
return;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn serializeCpuAlloc(ally: Allocator, cpu: std.Target.Cpu) Allocator.Error![]u8 {
|
|
var buffer = std.ArrayList(u8).init(ally);
|
|
try serializeCpu(&buffer, cpu);
|
|
return buffer.toOwnedSlice();
|
|
}
|
|
|
|
/// Return a Formatter for a Zig identifier, escaping it with `@""` syntax if needed.
|
|
///
|
|
/// See also `fmtIdFlags`.
|
|
pub fn fmtId(bytes: []const u8) std.fmt.Formatter(FormatId, FormatId.render) {
|
|
return .{ .data = .{ .bytes = bytes, .flags = .{} } };
|
|
}
|
|
|
|
/// 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) std.fmt.Formatter(FormatId, FormatId.render) {
|
|
return .{ .data = .{ .bytes = bytes, .flags = flags } };
|
|
}
|
|
|
|
pub fn fmtIdPU(bytes: []const u8) std.fmt.Formatter(FormatId, FormatId.render) {
|
|
return .{ .data = .{ .bytes = bytes, .flags = .{ .allow_primitive = true, .allow_underscore = true } } };
|
|
}
|
|
|
|
pub fn fmtIdP(bytes: []const u8) std.fmt.Formatter(FormatId, FormatId.render) {
|
|
return .{ .data = .{ .bytes = bytes, .flags = .{ .allow_primitive = true } } };
|
|
}
|
|
|
|
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 })});
|
|
|
|
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 })});
|
|
|
|
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 })});
|
|
|
|
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 })});
|
|
|
|
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")});
|
|
|
|
// These are technically not currently legal in Zig.
|
|
try expectFmt("@\"\"", "{f}", .{fmtId("")});
|
|
try expectFmt("@\"\\x00\"", "{f}", .{fmtId("\x00")});
|
|
}
|
|
|
|
pub const FormatId = struct {
|
|
bytes: []const u8,
|
|
flags: Flags,
|
|
pub const Flags = struct {
|
|
allow_primitive: bool = false,
|
|
allow_underscore: bool = false,
|
|
};
|
|
|
|
/// Print the string as a Zig identifier, escaping it with `@""` syntax if needed.
|
|
fn render(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('"');
|
|
}
|
|
};
|
|
|
|
/// 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 };
|
|
}
|
|
|
|
/// Return a formatter for escaping a single quoted Zig string.
|
|
pub fn fmtChar(c: u21) std.fmt.Formatter(u21, charEscape) {
|
|
return .{ .data = c };
|
|
}
|
|
|
|
test fmtString {
|
|
try std.testing.expectFmt("\\x0f", "{f}", .{fmtString("\x0f")});
|
|
try std.testing.expectFmt(
|
|
\\" \\ hi \x07 \x11 \" derp '"
|
|
, "\"{f}\"", .{fmtString(" \\ hi \x07 \x11 \" derp '")});
|
|
}
|
|
|
|
test fmtChar {
|
|
try std.testing.expectFmt("c \\u{26a1}", "{f} {f}", .{ fmtChar('c'), fmtChar('âš¡') });
|
|
}
|
|
|
|
/// 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.ArrayList(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.ArrayList(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.ArrayList(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.ArrayList(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;
|
|
}
|