aboutsummaryrefslogtreecommitdiff
path: root/std
diff options
context:
space:
mode:
authorAndrew Kelley <superjoe30@gmail.com>2018-02-11 23:49:20 -0500
committerAndrew Kelley <superjoe30@gmail.com>2018-02-11 23:49:20 -0500
commitef6260b3a7ed09e5dc5d8383ad20f229411bd9ff (patch)
tree23f2b5396a95cae6c730cdfafb7dfcdba7eca8f2 /std
parent5d9e3cb77f864ccdbcae43329e13a9c8e1f8494e (diff)
parentf2d601661d286b135293373a83ce1a8628272379 (diff)
downloadzig-ef6260b3a7ed09e5dc5d8383ad20f229411bd9ff.tar.gz
zig-ef6260b3a7ed09e5dc5d8383ad20f229411bd9ff.zip
Merge remote-tracking branch 'origin/master' into llvm6
Diffstat (limited to 'std')
-rw-r--r--std/array_list.zig49
-rw-r--r--std/base64.zig27
-rw-r--r--std/buf_map.zig4
-rw-r--r--std/buf_set.zig4
-rw-r--r--std/buffer.zig18
-rw-r--r--std/build.zig74
-rw-r--r--std/c/index.zig2
-rw-r--r--std/crypto/throughput_test.zig2
-rw-r--r--std/cstr.zig14
-rw-r--r--std/debug/failing_allocator.zig4
-rw-r--r--std/debug/index.zig110
-rw-r--r--std/elf.zig15
-rw-r--r--std/fmt/index.zig108
-rw-r--r--std/hash_map.zig4
-rw-r--r--std/heap.zig12
-rw-r--r--std/index.zig2
-rw-r--r--std/io.zig686
-rw-r--r--std/io_test.zig8
-rw-r--r--std/linked_list.zig4
-rw-r--r--std/math/index.zig44
-rw-r--r--std/mem.zig30
-rw-r--r--std/net.zig34
-rw-r--r--std/os/child_process.zig129
-rw-r--r--std/os/file.zig311
-rw-r--r--std/os/get_user_id.zig7
-rw-r--r--std/os/index.zig522
-rw-r--r--std/os/linux/index.zig2
-rw-r--r--std/os/path.zig31
-rw-r--r--std/os/windows/util.zig95
-rw-r--r--std/special/bootstrap.zig4
-rw-r--r--std/special/build_file_template.zig2
-rw-r--r--std/special/build_runner.zig30
-rw-r--r--std/special/test_runner.zig2
-rw-r--r--std/unicode.zig20
-rw-r--r--std/zig/ast.zig271
-rw-r--r--std/zig/index.zig11
-rw-r--r--std/zig/parser.zig1158
-rw-r--r--std/zig/tokenizer.zig655
38 files changed, 3431 insertions, 1074 deletions
diff --git a/std/array_list.zig b/std/array_list.zig
index bc4d3c1d81..2a44b66518 100644
--- a/std/array_list.zig
+++ b/std/array_list.zig
@@ -40,6 +40,10 @@ pub fn AlignedArrayList(comptime T: type, comptime A: u29) type{
return l.items[0..l.len];
}
+ pub fn at(l: &const Self, n: usize) T {
+ return l.toSliceConst()[n];
+ }
+
/// ArrayList takes ownership of the passed in slice. The slice must have been
/// allocated with `allocator`.
/// Deinitialize with `deinit` or use `toOwnedSlice`.
@@ -59,18 +63,34 @@ pub fn AlignedArrayList(comptime T: type, comptime A: u29) type{
return result;
}
- pub fn append(l: &Self, item: &const T) %void {
+ pub fn insert(l: &Self, n: usize, item: &const T) !void {
+ try l.ensureCapacity(l.len + 1);
+ l.len += 1;
+
+ mem.copy(T, l.items[n+1..l.len], l.items[n..l.len-1]);
+ l.items[n] = *item;
+ }
+
+ pub fn insertSlice(l: &Self, n: usize, items: []align(A) const T) !void {
+ try l.ensureCapacity(l.len + items.len);
+ l.len += items.len;
+
+ mem.copy(T, l.items[n+items.len..l.len], l.items[n..l.len-items.len]);
+ mem.copy(T, l.items[n..n+items.len], items);
+ }
+
+ pub fn append(l: &Self, item: &const T) !void {
const new_item_ptr = try l.addOne();
*new_item_ptr = *item;
}
- pub fn appendSlice(l: &Self, items: []align(A) const T) %void {
+ pub fn appendSlice(l: &Self, items: []align(A) const T) !void {
try l.ensureCapacity(l.len + items.len);
mem.copy(T, l.items[l.len..], items);
l.len += items.len;
}
- pub fn resize(l: &Self, new_len: usize) %void {
+ pub fn resize(l: &Self, new_len: usize) !void {
try l.ensureCapacity(new_len);
l.len = new_len;
}
@@ -80,7 +100,7 @@ pub fn AlignedArrayList(comptime T: type, comptime A: u29) type{
l.len = new_len;
}
- pub fn ensureCapacity(l: &Self, new_capacity: usize) %void {
+ pub fn ensureCapacity(l: &Self, new_capacity: usize) !void {
var better_capacity = l.items.len;
if (better_capacity >= new_capacity) return;
while (true) {
@@ -90,7 +110,7 @@ pub fn AlignedArrayList(comptime T: type, comptime A: u29) type{
l.items = try l.allocator.alignedRealloc(T, A, l.items, better_capacity);
}
- pub fn addOne(l: &Self) %&T {
+ pub fn addOne(l: &Self) !&T {
const new_length = l.len + 1;
try l.ensureCapacity(new_length);
const result = &l.items[l.len];
@@ -136,3 +156,22 @@ test "basic ArrayList test" {
list.appendSlice([]const i32 {}) catch unreachable;
assert(list.len == 9);
}
+
+test "insert ArrayList test" {
+ var list = ArrayList(i32).init(debug.global_allocator);
+ defer list.deinit();
+
+ try list.append(1);
+ try list.insert(0, 5);
+ assert(list.items[0] == 5);
+ assert(list.items[1] == 1);
+
+ try list.insertSlice(1, []const i32 { 9, 8 });
+ assert(list.items[0] == 5);
+ assert(list.items[1] == 9);
+ assert(list.items[2] == 8);
+
+ const items = []const i32 { 1 };
+ try list.insertSlice(0, items[0..0]);
+ assert(list.items[0] == 5);
+}
diff --git a/std/base64.zig b/std/base64.zig
index 8cd89b67b5..d9e1d2f908 100644
--- a/std/base64.zig
+++ b/std/base64.zig
@@ -79,8 +79,6 @@ pub const Base64Encoder = struct {
};
pub const standard_decoder = Base64Decoder.init(standard_alphabet_chars, standard_pad_char);
-error InvalidPadding;
-error InvalidCharacter;
pub const Base64Decoder = struct {
/// e.g. 'A' => 0.
@@ -111,7 +109,7 @@ pub const Base64Decoder = struct {
}
/// If the encoded buffer is detected to be invalid, returns error.InvalidPadding.
- pub fn calcSize(decoder: &const Base64Decoder, source: []const u8) %usize {
+ pub fn calcSize(decoder: &const Base64Decoder, source: []const u8) !usize {
if (source.len % 4 != 0) return error.InvalidPadding;
return calcDecodedSizeExactUnsafe(source, decoder.pad_char);
}
@@ -119,7 +117,7 @@ pub const Base64Decoder = struct {
/// dest.len must be what you get from ::calcSize.
/// invalid characters result in error.InvalidCharacter.
/// invalid padding results in error.InvalidPadding.
- pub fn decode(decoder: &const Base64Decoder, dest: []u8, source: []const u8) %void {
+ pub fn decode(decoder: &const Base64Decoder, dest: []u8, source: []const u8) !void {
assert(dest.len == (decoder.calcSize(source) catch unreachable));
assert(source.len % 4 == 0);
@@ -163,8 +161,6 @@ pub const Base64Decoder = struct {
}
};
-error OutputTooSmall;
-
pub const Base64DecoderWithIgnore = struct {
decoder: Base64Decoder,
char_is_ignored: [256]bool,
@@ -185,7 +181,7 @@ pub const Base64DecoderWithIgnore = struct {
}
/// If no characters end up being ignored or padding, this will be the exact decoded size.
- pub fn calcSizeUpperBound(encoded_len: usize) %usize {
+ pub fn calcSizeUpperBound(encoded_len: usize) usize {
return @divTrunc(encoded_len, 4) * 3;
}
@@ -193,7 +189,7 @@ pub const Base64DecoderWithIgnore = struct {
/// Invalid padding results in error.InvalidPadding.
/// Decoding more data than can fit in dest results in error.OutputTooSmall. See also ::calcSizeUpperBound.
/// Returns the number of bytes writen to dest.
- pub fn decode(decoder_with_ignore: &const Base64DecoderWithIgnore, dest: []u8, source: []const u8) %usize {
+ pub fn decode(decoder_with_ignore: &const Base64DecoderWithIgnore, dest: []u8, source: []const u8) !usize {
const decoder = &decoder_with_ignore.decoder;
var src_cursor: usize = 0;
@@ -378,7 +374,7 @@ test "base64" {
comptime (testBase64() catch unreachable);
}
-fn testBase64() %void {
+fn testBase64() !void {
try testAllApis("", "");
try testAllApis("f", "Zg==");
try testAllApis("fo", "Zm8=");
@@ -412,7 +408,7 @@ fn testBase64() %void {
try testOutputTooSmallError("AAAAAA==");
}
-fn testAllApis(expected_decoded: []const u8, expected_encoded: []const u8) %void {
+fn testAllApis(expected_decoded: []const u8, expected_encoded: []const u8) !void {
// Base64Encoder
{
var buffer: [0x100]u8 = undefined;
@@ -434,7 +430,7 @@ fn testAllApis(expected_decoded: []const u8, expected_encoded: []const u8) %void
const standard_decoder_ignore_nothing = Base64DecoderWithIgnore.init(
standard_alphabet_chars, standard_pad_char, "");
var buffer: [0x100]u8 = undefined;
- var decoded = buffer[0..try Base64DecoderWithIgnore.calcSizeUpperBound(expected_encoded.len)];
+ var decoded = buffer[0..Base64DecoderWithIgnore.calcSizeUpperBound(expected_encoded.len)];
var written = try standard_decoder_ignore_nothing.decode(decoded, expected_encoded);
assert(written <= decoded.len);
assert(mem.eql(u8, decoded[0..written], expected_decoded));
@@ -449,17 +445,16 @@ fn testAllApis(expected_decoded: []const u8, expected_encoded: []const u8) %void
}
}
-fn testDecodeIgnoreSpace(expected_decoded: []const u8, encoded: []const u8) %void {
+fn testDecodeIgnoreSpace(expected_decoded: []const u8, encoded: []const u8) !void {
const standard_decoder_ignore_space = Base64DecoderWithIgnore.init(
standard_alphabet_chars, standard_pad_char, " ");
var buffer: [0x100]u8 = undefined;
- var decoded = buffer[0..try Base64DecoderWithIgnore.calcSizeUpperBound(encoded.len)];
+ var decoded = buffer[0..Base64DecoderWithIgnore.calcSizeUpperBound(encoded.len)];
var written = try standard_decoder_ignore_space.decode(decoded, encoded);
assert(mem.eql(u8, decoded[0..written], expected_decoded));
}
-error ExpectedError;
-fn testError(encoded: []const u8, expected_err: error) %void {
+fn testError(encoded: []const u8, expected_err: error) !void {
const standard_decoder_ignore_space = Base64DecoderWithIgnore.init(
standard_alphabet_chars, standard_pad_char, " ");
var buffer: [0x100]u8 = undefined;
@@ -475,7 +470,7 @@ fn testError(encoded: []const u8, expected_err: error) %void {
} else |err| if (err != expected_err) return err;
}
-fn testOutputTooSmallError(encoded: []const u8) %void {
+fn testOutputTooSmallError(encoded: []const u8) !void {
const standard_decoder_ignore_space = Base64DecoderWithIgnore.init(
standard_alphabet_chars, standard_pad_char, " ");
var buffer: [0x100]u8 = undefined;
diff --git a/std/buf_map.zig b/std/buf_map.zig
index 15ffe785e6..d7f81cf2cc 100644
--- a/std/buf_map.zig
+++ b/std/buf_map.zig
@@ -27,7 +27,7 @@ pub const BufMap = struct {
self.hash_map.deinit();
}
- pub fn set(self: &BufMap, key: []const u8, value: []const u8) %void {
+ pub fn set(self: &BufMap, key: []const u8, value: []const u8) !void {
if (self.hash_map.get(key)) |entry| {
const value_copy = try self.copy(value);
errdefer self.free(value_copy);
@@ -67,7 +67,7 @@ pub const BufMap = struct {
self.hash_map.allocator.free(mut_value);
}
- fn copy(self: &BufMap, value: []const u8) %[]const u8 {
+ fn copy(self: &BufMap, value: []const u8) ![]const u8 {
const result = try self.hash_map.allocator.alloc(u8, value.len);
mem.copy(u8, result, value);
return result;
diff --git a/std/buf_set.zig b/std/buf_set.zig
index 2349c17433..4fa16762b6 100644
--- a/std/buf_set.zig
+++ b/std/buf_set.zig
@@ -24,7 +24,7 @@ pub const BufSet = struct {
self.hash_map.deinit();
}
- pub fn put(self: &BufSet, key: []const u8) %void {
+ pub fn put(self: &BufSet, key: []const u8) !void {
if (self.hash_map.get(key) == null) {
const key_copy = try self.copy(key);
errdefer self.free(key_copy);
@@ -55,7 +55,7 @@ pub const BufSet = struct {
self.hash_map.allocator.free(mut_value);
}
- fn copy(self: &BufSet, value: []const u8) %[]const u8 {
+ fn copy(self: &BufSet, value: []const u8) ![]const u8 {
const result = try self.hash_map.allocator.alloc(u8, value.len);
mem.copy(u8, result, value);
return result;
diff --git a/std/buffer.zig b/std/buffer.zig
index 34428aa8e4..e0892d5933 100644
--- a/std/buffer.zig
+++ b/std/buffer.zig
@@ -12,14 +12,14 @@ pub const Buffer = struct {
list: ArrayList(u8),
/// Must deinitialize with deinit.
- pub fn init(allocator: &Allocator, m: []const u8) %Buffer {
+ pub fn init(allocator: &Allocator, m: []const u8) !Buffer {
var self = try initSize(allocator, m.len);
mem.copy(u8, self.list.items, m);
return self;
}
/// Must deinitialize with deinit.
- pub fn initSize(allocator: &Allocator, size: usize) %Buffer {
+ pub fn initSize(allocator: &Allocator, size: usize) !Buffer {
var self = initNull(allocator);
try self.resize(size);
return self;
@@ -37,7 +37,7 @@ pub const Buffer = struct {
}
/// Must deinitialize with deinit.
- pub fn initFromBuffer(buffer: &const Buffer) %Buffer {
+ pub fn initFromBuffer(buffer: &const Buffer) !Buffer {
return Buffer.init(buffer.list.allocator, buffer.toSliceConst());
}
@@ -80,7 +80,7 @@ pub const Buffer = struct {
self.list.items[self.len()] = 0;
}
- pub fn resize(self: &Buffer, new_len: usize) %void {
+ pub fn resize(self: &Buffer, new_len: usize) !void {
try self.list.resize(new_len + 1);
self.list.items[self.len()] = 0;
}
@@ -93,24 +93,24 @@ pub const Buffer = struct {
return self.list.len - 1;
}
- pub fn append(self: &Buffer, m: []const u8) %void {
+ pub fn append(self: &Buffer, m: []const u8) !void {
const old_len = self.len();
try self.resize(old_len + m.len);
mem.copy(u8, self.list.toSlice()[old_len..], m);
}
// TODO: remove, use OutStream for this
- pub fn appendFormat(self: &Buffer, comptime format: []const u8, args: ...) %void {
+ pub fn appendFormat(self: &Buffer, comptime format: []const u8, args: ...) !void {
return fmt.format(self, append, format, args);
}
// TODO: remove, use OutStream for this
- pub fn appendByte(self: &Buffer, byte: u8) %void {
+ pub fn appendByte(self: &Buffer, byte: u8) !void {
return self.appendByteNTimes(byte, 1);
}
// TODO: remove, use OutStream for this
- pub fn appendByteNTimes(self: &Buffer, byte: u8, count: usize) %void {
+ pub fn appendByteNTimes(self: &Buffer, byte: u8, count: usize) !void {
var prev_size: usize = self.len();
const new_size = prev_size + count;
try self.resize(new_size);
@@ -137,7 +137,7 @@ pub const Buffer = struct {
return mem.eql(u8, self.list.items[start..l], m);
}
- pub fn replaceContents(self: &const Buffer, m: []const u8) %void {
+ pub fn replaceContents(self: &const Buffer, m: []const u8) !void {
try self.resize(m.len);
mem.copy(u8, self.list.toSlice(), m);
}
diff --git a/std/build.zig b/std/build.zig
index 6c56988896..e6b6676261 100644
--- a/std/build.zig
+++ b/std/build.zig
@@ -15,13 +15,6 @@ const BufSet = std.BufSet;
const BufMap = std.BufMap;
const fmt_lib = std.fmt;
-error ExtraArg;
-error UncleanExit;
-error InvalidStepName;
-error DependencyLoopDetected;
-error NoCompilerFound;
-error NeedAnObject;
-
pub const Builder = struct {
uninstall_tls: TopLevelStep,
install_tls: TopLevelStep,
@@ -242,7 +235,7 @@ pub const Builder = struct {
self.lib_paths.append(path) catch unreachable;
}
- pub fn make(self: &Builder, step_names: []const []const u8) %void {
+ pub fn make(self: &Builder, step_names: []const []const u8) !void {
var wanted_steps = ArrayList(&Step).init(self.allocator);
defer wanted_steps.deinit();
@@ -278,7 +271,7 @@ pub const Builder = struct {
return &self.uninstall_tls.step;
}
- fn makeUninstall(uninstall_step: &Step) %void {
+ fn makeUninstall(uninstall_step: &Step) error!void {
const uninstall_tls = @fieldParentPtr(TopLevelStep, "step", uninstall_step);
const self = @fieldParentPtr(Builder, "uninstall_tls", uninstall_tls);
@@ -292,7 +285,7 @@ pub const Builder = struct {
// TODO remove empty directories
}
- fn makeOneStep(self: &Builder, s: &Step) %void {
+ fn makeOneStep(self: &Builder, s: &Step) error!void {
if (s.loop_flag) {
warn("Dependency loop detected:\n {}\n", s.name);
return error.DependencyLoopDetected;
@@ -313,7 +306,7 @@ pub const Builder = struct {
try s.make();
}
- fn getTopLevelStepByName(self: &Builder, name: []const u8) %&Step {
+ fn getTopLevelStepByName(self: &Builder, name: []const u8) !&Step {
for (self.top_level_steps.toSliceConst()) |top_level_step| {
if (mem.eql(u8, top_level_step.step.name, name)) {
return &top_level_step.step;
@@ -548,7 +541,7 @@ pub const Builder = struct {
return self.invalid_user_input;
}
- fn spawnChild(self: &Builder, argv: []const []const u8) %void {
+ fn spawnChild(self: &Builder, argv: []const []const u8) !void {
return self.spawnChildEnvMap(null, &self.env_map, argv);
}
@@ -561,7 +554,7 @@ pub const Builder = struct {
}
fn spawnChildEnvMap(self: &Builder, cwd: ?[]const u8, env_map: &const BufMap,
- argv: []const []const u8) %void
+ argv: []const []const u8) !void
{
if (self.verbose) {
printCmd(cwd, argv);
@@ -595,7 +588,7 @@ pub const Builder = struct {
}
}
- pub fn makePath(self: &Builder, path: []const u8) %void {
+ pub fn makePath(self: &Builder, path: []const u8) !void {
os.makePath(self.allocator, self.pathFromRoot(path)) catch |err| {
warn("Unable to create path {}: {}\n", path, @errorName(err));
return err;
@@ -630,11 +623,11 @@ pub const Builder = struct {
self.installed_files.append(full_path) catch unreachable;
}
- fn copyFile(self: &Builder, source_path: []const u8, dest_path: []const u8) %void {
- return self.copyFileMode(source_path, dest_path, 0o666);
+ fn copyFile(self: &Builder, source_path: []const u8, dest_path: []const u8) !void {
+ return self.copyFileMode(source_path, dest_path, os.default_file_mode);
}
- fn copyFileMode(self: &Builder, source_path: []const u8, dest_path: []const u8, mode: usize) %void {
+ fn copyFileMode(self: &Builder, source_path: []const u8, dest_path: []const u8, mode: os.FileMode) !void {
if (self.verbose) {
warn("cp {} {}\n", source_path, dest_path);
}
@@ -672,7 +665,7 @@ pub const Builder = struct {
}
}
- pub fn findProgram(self: &Builder, names: []const []const u8, paths: []const []const u8) %[]const u8 {
+ pub fn findProgram(self: &Builder, names: []const []const u8, paths: []const []const u8) ![]const u8 {
// TODO report error for ambiguous situations
const exe_extension = (Target { .Native = {}}).exeFileExt();
for (self.search_prefixes.toSliceConst()) |search_prefix| {
@@ -721,7 +714,7 @@ pub const Builder = struct {
return error.FileNotFound;
}
- pub fn exec(self: &Builder, argv: []const []const u8) %[]u8 {
+ pub fn exec(self: &Builder, argv: []const []const u8) ![]u8 {
const max_output_size = 100 * 1024;
const result = try os.ChildProcess.exec(self.allocator, argv, null, null, max_output_size);
switch (result.term) {
@@ -1180,12 +1173,12 @@ pub const LibExeObjStep = struct {
self.disable_libc = disable;
}
- fn make(step: &Step) %void {
+ fn make(step: &Step) !void {
const self = @fieldParentPtr(LibExeObjStep, "step", step);
return if (self.is_zig) self.makeZig() else self.makeC();
}
- fn makeZig(self: &LibExeObjStep) %void {
+ fn makeZig(self: &LibExeObjStep) !void {
const builder = self.builder;
assert(self.is_zig);
@@ -1396,7 +1389,7 @@ pub const LibExeObjStep = struct {
}
}
- fn makeC(self: &LibExeObjStep) %void {
+ fn makeC(self: &LibExeObjStep) !void {
const builder = self.builder;
const cc = builder.getCCExe();
@@ -1687,7 +1680,7 @@ pub const TestStep = struct {
self.exec_cmd_args = args;
}
- fn make(step: &Step) %void {
+ fn make(step: &Step) !void {
const self = @fieldParentPtr(TestStep, "step", step);
const builder = self.builder;
@@ -1796,7 +1789,7 @@ pub const CommandStep = struct {
return self;
}
- fn make(step: &Step) %void {
+ fn make(step: &Step) !void {
const self = @fieldParentPtr(CommandStep, "step", step);
const cwd = if (self.cwd) |cwd| self.builder.pathFromRoot(cwd) else self.builder.build_root;
@@ -1836,14 +1829,17 @@ const InstallArtifactStep = struct {
return self;
}
- fn make(step: &Step) %void {
+ fn make(step: &Step) !void {
const self = @fieldParentPtr(Self, "step", step);
const builder = self.builder;
- const mode = switch (self.artifact.kind) {
- LibExeObjStep.Kind.Obj => unreachable,
- LibExeObjStep.Kind.Exe => usize(0o755),
- LibExeObjStep.Kind.Lib => if (self.artifact.static) usize(0o666) else usize(0o755),
+ const mode = switch (builtin.os) {
+ builtin.Os.windows => {},
+ else => switch (self.artifact.kind) {
+ LibExeObjStep.Kind.Obj => unreachable,
+ LibExeObjStep.Kind.Exe => u32(0o755),
+ LibExeObjStep.Kind.Lib => if (self.artifact.static) u32(0o666) else u32(0o755),
+ },
};
try builder.copyFileMode(self.artifact.getOutputPath(), self.dest_file, mode);
if (self.artifact.kind == LibExeObjStep.Kind.Lib and !self.artifact.static) {
@@ -1868,7 +1864,7 @@ pub const InstallFileStep = struct {
};
}
- fn make(step: &Step) %void {
+ fn make(step: &Step) !void {
const self = @fieldParentPtr(InstallFileStep, "step", step);
try self.builder.copyFile(self.src_path, self.dest_path);
}
@@ -1889,7 +1885,7 @@ pub const WriteFileStep = struct {
};
}
- fn make(step: &Step) %void {
+ fn make(step: &Step) !void {
const self = @fieldParentPtr(WriteFileStep, "step", step);
const full_path = self.builder.pathFromRoot(self.file_path);
const full_path_dir = os.path.dirname(full_path);
@@ -1897,7 +1893,7 @@ pub const WriteFileStep = struct {
warn("unable to make path {}: {}\n", full_path_dir, @errorName(err));
return err;
};
- io.writeFile(full_path, self.data, self.builder.allocator) catch |err| {
+ io.writeFile(self.builder.allocator, full_path, self.data) catch |err| {
warn("unable to write {}: {}\n", full_path, @errorName(err));
return err;
};
@@ -1917,7 +1913,7 @@ pub const LogStep = struct {
};
}
- fn make(step: &Step) %void {
+ fn make(step: &Step) error!void {
const self = @fieldParentPtr(LogStep, "step", step);
warn("{}", self.data);
}
@@ -1936,7 +1932,7 @@ pub const RemoveDirStep = struct {
};
}
- fn make(step: &Step) %void {
+ fn make(step: &Step) !void {
const self = @fieldParentPtr(RemoveDirStep, "step", step);
const full_path = self.builder.pathFromRoot(self.dir_path);
@@ -1949,12 +1945,12 @@ pub const RemoveDirStep = struct {
pub const Step = struct {
name: []const u8,
- makeFn: fn(self: &Step) %void,
+ makeFn: fn(self: &Step) error!void,
dependencies: ArrayList(&Step),
loop_flag: bool,
done_flag: bool,
- pub fn init(name: []const u8, allocator: &Allocator, makeFn: fn (&Step)%void) Step {
+ pub fn init(name: []const u8, allocator: &Allocator, makeFn: fn (&Step)error!void) Step {
return Step {
.name = name,
.makeFn = makeFn,
@@ -1967,7 +1963,7 @@ pub const Step = struct {
return init(name, allocator, makeNoOp);
}
- pub fn make(self: &Step) %void {
+ pub fn make(self: &Step) !void {
if (self.done_flag)
return;
@@ -1979,11 +1975,11 @@ pub const Step = struct {
self.dependencies.append(other) catch unreachable;
}
- fn makeNoOp(self: &Step) %void {}
+ fn makeNoOp(self: &Step) error!void {}
};
fn doAtomicSymLinks(allocator: &Allocator, output_path: []const u8, filename_major_only: []const u8,
- filename_name_only: []const u8) %void
+ filename_name_only: []const u8) !void
{
const out_dir = os.path.dirname(output_path);
const out_basename = os.path.basename(output_path);
diff --git a/std/c/index.zig b/std/c/index.zig
index 7b34ccea82..24e24dc3d3 100644
--- a/std/c/index.zig
+++ b/std/c/index.zig
@@ -20,7 +20,7 @@ pub extern "c" fn open(path: &const u8, oflag: c_int, ...) c_int;
pub extern "c" fn raise(sig: c_int) c_int;
pub extern "c" fn read(fd: c_int, buf: &c_void, nbyte: usize) isize;
pub extern "c" fn stat(noalias path: &const u8, noalias buf: &Stat) c_int;
-pub extern "c" fn write(fd: c_int, buf: &const c_void, nbyte: usize) c_int;
+pub extern "c" fn write(fd: c_int, buf: &const c_void, nbyte: usize) isize;
pub extern "c" fn mmap(addr: ?&c_void, len: usize, prot: c_int, flags: c_int,
fd: c_int, offset: isize) ?&c_void;
pub extern "c" fn munmap(addr: &c_void, len: usize) c_int;
diff --git a/std/crypto/throughput_test.zig b/std/crypto/throughput_test.zig
index 1ebe64d5a4..60610411b5 100644
--- a/std/crypto/throughput_test.zig
+++ b/std/crypto/throughput_test.zig
@@ -18,7 +18,7 @@ const c = @cImport({
const Mb = 1024 * 1024;
-pub fn main() %void {
+pub fn main() !void {
var stdout_file = try std.io.getStdOut();
var stdout_out_stream = std.io.FileOutStream.init(&stdout_file);
const stdout = &stdout_out_stream.stream;
diff --git a/std/cstr.zig b/std/cstr.zig
index 987c6d3341..d396dcbce3 100644
--- a/std/cstr.zig
+++ b/std/cstr.zig
@@ -1,8 +1,15 @@
const std = @import("index.zig");
+const builtin = @import("builtin");
const debug = std.debug;
const mem = std.mem;
const assert = debug.assert;
+pub const line_sep = switch (builtin.os) {
+ builtin.Os.windows => "\r\n",
+ else => "\n",
+};
+
+
pub fn len(ptr: &const u8) usize {
var count: usize = 0;
while (ptr[count] != 0) : (count += 1) {}
@@ -39,10 +46,9 @@ fn testCStrFnsImpl() void {
assert(len(c"123456789") == 9);
}
-/// Returns a mutable slice with exactly the same size which is guaranteed to
-/// have a null byte after it.
+/// Returns a mutable slice with 1 more byte of length which is a null byte.
/// Caller owns the returned memory.
-pub fn addNullByte(allocator: &mem.Allocator, slice: []const u8) %[]u8 {
+pub fn addNullByte(allocator: &mem.Allocator, slice: []const u8) ![]u8 {
const result = try allocator.alloc(u8, slice.len + 1);
mem.copy(u8, result, slice);
result[slice.len] = 0;
@@ -56,7 +62,7 @@ pub const NullTerminated2DArray = struct {
/// Takes N lists of strings, concatenates the lists together, and adds a null terminator
/// Caller must deinit result
- pub fn fromSlices(allocator: &mem.Allocator, slices: []const []const []const u8) %NullTerminated2DArray {
+ pub fn fromSlices(allocator: &mem.Allocator, slices: []const []const []const u8) !NullTerminated2DArray {
var new_len: usize = 1; // 1 for the list null
var byte_count: usize = 0;
for (slices) |slice| {
diff --git a/std/debug/failing_allocator.zig b/std/debug/failing_allocator.zig
index cc5a8bc045..f876b7902d 100644
--- a/std/debug/failing_allocator.zig
+++ b/std/debug/failing_allocator.zig
@@ -28,7 +28,7 @@ pub const FailingAllocator = struct {
};
}
- fn alloc(allocator: &mem.Allocator, n: usize, alignment: u29) %[]u8 {
+ fn alloc(allocator: &mem.Allocator, n: usize, alignment: u29) ![]u8 {
const self = @fieldParentPtr(FailingAllocator, "allocator", allocator);
if (self.index == self.fail_index) {
return error.OutOfMemory;
@@ -39,7 +39,7 @@ pub const FailingAllocator = struct {
return result;
}
- fn realloc(allocator: &mem.Allocator, old_mem: []u8, new_size: usize, alignment: u29) %[]u8 {
+ fn realloc(allocator: &mem.Allocator, old_mem: []u8, new_size: usize, alignment: u29) ![]u8 {
const self = @fieldParentPtr(FailingAllocator, "allocator", allocator);
if (new_size <= old_mem.len) {
self.freed_bytes += old_mem.len - new_size;
diff --git a/std/debug/index.zig b/std/debug/index.zig
index ccf5f6d413..5426a197f2 100644
--- a/std/debug/index.zig
+++ b/std/debug/index.zig
@@ -10,26 +10,17 @@ const builtin = @import("builtin");
pub const FailingAllocator = @import("failing_allocator.zig").FailingAllocator;
-error MissingDebugInfo;
-error InvalidDebugInfo;
-error UnsupportedDebugInfo;
-error UnknownObjectFormat;
-error TodoSupportCoffDebugInfo;
-error TodoSupportMachoDebugInfo;
-error TodoSupportCOFFDebugInfo;
-
-
/// Tries to write to stderr, unbuffered, and ignores any error returned.
/// Does not append a newline.
/// TODO atomic/multithread support
-var stderr_file: io.File = undefined;
+var stderr_file: os.File = undefined;
var stderr_file_out_stream: io.FileOutStream = undefined;
-var stderr_stream: ?&io.OutStream = null;
+var stderr_stream: ?&io.OutStream(io.FileOutStream.Error) = null;
pub fn warn(comptime fmt: []const u8, args: ...) void {
const stderr = getStderrStream() catch return;
stderr.print(fmt, args) catch return;
}
-fn getStderrStream() %&io.OutStream {
+fn getStderrStream() !&io.OutStream(io.FileOutStream.Error) {
if (stderr_stream) |st| {
return st;
} else {
@@ -42,7 +33,7 @@ fn getStderrStream() %&io.OutStream {
}
var self_debug_info: ?&ElfStackTrace = null;
-pub fn getSelfDebugInfo() %&ElfStackTrace {
+pub fn getSelfDebugInfo() !&ElfStackTrace {
if (self_debug_info) |info| {
return info;
} else {
@@ -149,11 +140,8 @@ const WHITE = "\x1b[37;1m";
const DIM = "\x1b[2m";
const RESET = "\x1b[0m";
-error PathNotFound;
-error InvalidDebugInfo;
-
-pub fn writeStackTrace(stack_trace: &const builtin.StackTrace, out_stream: &io.OutStream, allocator: &mem.Allocator,
- debug_info: &ElfStackTrace, tty_color: bool) %void
+pub fn writeStackTrace(stack_trace: &const builtin.StackTrace, out_stream: var, allocator: &mem.Allocator,
+ debug_info: &ElfStackTrace, tty_color: bool) !void
{
var frame_index: usize = undefined;
var frames_left: usize = undefined;
@@ -174,8 +162,8 @@ pub fn writeStackTrace(stack_trace: &const builtin.StackTrace, out_stream: &io.O
}
}
-pub fn writeCurrentStackTrace(out_stream: &io.OutStream, allocator: &mem.Allocator,
- debug_info: &ElfStackTrace, tty_color: bool, ignore_frame_count: usize) %void
+pub fn writeCurrentStackTrace(out_stream: var, allocator: &mem.Allocator,
+ debug_info: &ElfStackTrace, tty_color: bool, ignore_frame_count: usize) !void
{
var ignored_count: usize = 0;
@@ -191,7 +179,7 @@ pub fn writeCurrentStackTrace(out_stream: &io.OutStream, allocator: &mem.Allocat
}
}
-fn printSourceAtAddress(debug_info: &ElfStackTrace, out_stream: &io.OutStream, address: usize) %void {
+fn printSourceAtAddress(debug_info: &ElfStackTrace, out_stream: var, address: usize) !void {
if (builtin.os == builtin.Os.windows) {
return error.UnsupportedDebugInfo;
}
@@ -221,7 +209,7 @@ fn printSourceAtAddress(debug_info: &ElfStackTrace, out_stream: &io.OutStream, a
try out_stream.write(GREEN ++ "^" ++ RESET ++ "\n");
}
} else |err| switch (err) {
- error.EndOfFile, error.PathNotFound => {},
+ error.EndOfFile => {},
else => return err,
}
} else |err| switch (err) {
@@ -232,7 +220,7 @@ fn printSourceAtAddress(debug_info: &ElfStackTrace, out_stream: &io.OutStream, a
}
}
-pub fn openSelfDebugInfo(allocator: &mem.Allocator) %&ElfStackTrace {
+pub fn openSelfDebugInfo(allocator: &mem.Allocator) !&ElfStackTrace {
switch (builtin.object_format) {
builtin.ObjectFormat.elf => {
const st = try allocator.create(ElfStackTrace);
@@ -276,8 +264,8 @@ pub fn openSelfDebugInfo(allocator: &mem.Allocator) %&ElfStackTrace {
}
}
-fn printLineFromFile(allocator: &mem.Allocator, out_stream: &io.OutStream, line_info: &const LineInfo) %void {
- var f = try io.File.openRead(line_info.file_name, allocator);
+fn printLineFromFile(allocator: &mem.Allocator, out_stream: var, line_info: &const LineInfo) !void {
+ var f = try os.File.openRead(allocator, line_info.file_name);
defer f.close();
// TODO fstat and make sure that the file has the correct size
@@ -310,7 +298,7 @@ fn printLineFromFile(allocator: &mem.Allocator, out_stream: &io.OutStream, line_
}
pub const ElfStackTrace = struct {
- self_exe_file: io.File,
+ self_exe_file: os.File,
elf: elf.Elf,
debug_info: &elf.SectionHeader,
debug_abbrev: &elf.SectionHeader,
@@ -324,7 +312,7 @@ pub const ElfStackTrace = struct {
return self.abbrev_table_list.allocator;
}
- pub fn readString(self: &ElfStackTrace) %[]u8 {
+ pub fn readString(self: &ElfStackTrace) ![]u8 {
var in_file_stream = io.FileInStream.init(&self.self_exe_file);
const in_stream = &in_file_stream.stream;
return readStringRaw(self.allocator(), in_stream);
@@ -387,7 +375,7 @@ const Constant = struct {
payload: []u8,
signed: bool,
- fn asUnsignedLe(self: &const Constant) %u64 {
+ fn asUnsignedLe(self: &const Constant) !u64 {
if (self.payload.len > @sizeOf(u64))
return error.InvalidDebugInfo;
if (self.signed)
@@ -414,7 +402,7 @@ const Die = struct {
return null;
}
- fn getAttrAddr(self: &const Die, id: u64) %u64 {
+ fn getAttrAddr(self: &const Die, id: u64) !u64 {
const form_value = self.getAttr(id) ?? return error.MissingDebugInfo;
return switch (*form_value) {
FormValue.Address => |value| value,
@@ -422,7 +410,7 @@ const Die = struct {
};
}
- fn getAttrSecOffset(self: &const Die, id: u64) %u64 {
+ fn getAttrSecOffset(self: &const Die, id: u64) !u64 {
const form_value = self.getAttr(id) ?? return error.MissingDebugInfo;
return switch (*form_value) {
FormValue.Const => |value| value.asUnsignedLe(),
@@ -431,7 +419,7 @@ const Die = struct {
};
}
- fn getAttrUnsignedLe(self: &const Die, id: u64) %u64 {
+ fn getAttrUnsignedLe(self: &const Die, id: u64) !u64 {
const form_value = self.getAttr(id) ?? return error.MissingDebugInfo;
return switch (*form_value) {
FormValue.Const => |value| value.asUnsignedLe(),
@@ -439,7 +427,7 @@ const Die = struct {
};
}
- fn getAttrString(self: &const Die, st: &ElfStackTrace, id: u64) %[]u8 {
+ fn getAttrString(self: &const Die, st: &ElfStackTrace, id: u64) ![]u8 {
const form_value = self.getAttr(id) ?? return error.MissingDebugInfo;
return switch (*form_value) {
FormValue.String => |value| value,
@@ -512,7 +500,7 @@ const LineNumberProgram = struct {
};
}
- pub fn checkLineMatch(self: &LineNumberProgram) %?LineInfo {
+ pub fn checkLineMatch(self: &LineNumberProgram) !?LineInfo {
if (self.target_address >= self.prev_address and self.target_address < self.address) {
const file_entry = if (self.prev_file == 0) {
return error.MissingDebugInfo;
@@ -544,7 +532,7 @@ const LineNumberProgram = struct {
}
};
-fn readStringRaw(allocator: &mem.Allocator, in_stream: &io.InStream) %[]u8 {
+fn readStringRaw(allocator: &mem.Allocator, in_stream: var) ![]u8 {
var buf = ArrayList(u8).init(allocator);
while (true) {
const byte = try in_stream.readByte();
@@ -555,58 +543,70 @@ fn readStringRaw(allocator: &mem.Allocator, in_stream: &io.InStream) %[]u8 {
return buf.toSlice();
}
-fn getString(st: &ElfStackTrace, offset: u64) %[]u8 {
+fn getString(st: &ElfStackTrace, offset: u64) ![]u8 {
const pos = st.debug_str.offset + offset;
try st.self_exe_file.seekTo(pos);
return st.readString();
}
-fn readAllocBytes(allocator: &mem.Allocator, in_stream: &io.InStream, size: usize) %[]u8 {
+fn readAllocBytes(allocator: &mem.Allocator, in_stream: var, size: usize) ![]u8 {
const buf = try global_allocator.alloc(u8, size);
errdefer global_allocator.free(buf);
if ((try in_stream.read(buf)) < size) return error.EndOfFile;
return buf;
}
-fn parseFormValueBlockLen(allocator: &mem.Allocator, in_stream: &io.InStream, size: usize) %FormValue {
+fn parseFormValueBlockLen(allocator: &mem.Allocator, in_stream: var, size: usize) !FormValue {
const buf = try readAllocBytes(allocator, in_stream, size);
return FormValue { .Block = buf };
}
-fn parseFormValueBlock(allocator: &mem.Allocator, in_stream: &io.InStream, size: usize) %FormValue {
+fn parseFormValueBlock(allocator: &mem.Allocator, in_stream: var, size: usize) !FormValue {
const block_len = try in_stream.readVarInt(builtin.Endian.Little, usize, size);
return parseFormValueBlockLen(allocator, in_stream, block_len);
}
-fn parseFormValueConstant(allocator: &mem.Allocator, in_stream: &io.InStream, signed: bool, size: usize) %FormValue {
+fn parseFormValueConstant(allocator: &mem.Allocator, in_stream: var, signed: bool, size: usize) !FormValue {
return FormValue { .Const = Constant {
.signed = signed,
.payload = try readAllocBytes(allocator, in_stream, size),
}};
}
-fn parseFormValueDwarfOffsetSize(in_stream: &io.InStream, is_64: bool) %u64 {
+fn parseFormValueDwarfOffsetSize(in_stream: var, is_64: bool) !u64 {
return if (is_64) try in_stream.readIntLe(u64)
else u64(try in_stream.readIntLe(u32)) ;
}
-fn parseFormValueTargetAddrSize(in_stream: &io.InStream) %u64 {
+fn parseFormValueTargetAddrSize(in_stream: var) !u64 {
return if (@sizeOf(usize) == 4) u64(try in_stream.readIntLe(u32))
else if (@sizeOf(usize) == 8) try in_stream.readIntLe(u64)
else unreachable;
}
-fn parseFormValueRefLen(allocator: &mem.Allocator, in_stream: &io.InStream, size: usize) %FormValue {
+fn parseFormValueRefLen(allocator: &mem.Allocator, in_stream: var, size: usize) !FormValue {
const buf = try readAllocBytes(allocator, in_stream, size);
return FormValue { .Ref = buf };
}
-fn parseFormValueRef(allocator: &mem.Allocator, in_stream: &io.InStream, comptime T: type) %FormValue {
+fn parseFormValueRef(allocator: &mem.Allocator, in_stream: var, comptime T: type) !FormValue {
const block_len = try in_stream.readIntLe(T);
return parseFormValueRefLen(allocator, in_stream, block_len);
}
-fn parseFormValue(allocator: &mem.Allocator, in_stream: &io.InStream, form_id: u64, is_64: bool) %FormValue {
+const ParseFormValueError = error {
+ EndOfStream,
+ Io,
+ BadFd,
+ Unexpected,
+ InvalidDebugInfo,
+ EndOfFile,
+ OutOfMemory,
+};
+
+fn parseFormValue(allocator: &mem.Allocator, in_stream: var, form_id: u64, is_64: bool)
+ ParseFormValueError!FormValue
+{
return switch (form_id) {
DW.FORM_addr => FormValue { .Address = try parseFormValueTargetAddrSize(in_stream) },
DW.FORM_block1 => parseFormValueBlock(allocator, in_stream, 1),
@@ -656,7 +656,7 @@ fn parseFormValue(allocator: &mem.Allocator, in_stream: &io.InStream, form_id: u
};
}
-fn parseAbbrevTable(st: &ElfStackTrace) %AbbrevTable {
+fn parseAbbrevTable(st: &ElfStackTrace) !AbbrevTable {
const in_file = &st.self_exe_file;
var in_file_stream = io.FileInStream.init(in_file);
const in_stream = &in_file_stream.stream;
@@ -688,7 +688,7 @@ fn parseAbbrevTable(st: &ElfStackTrace) %AbbrevTable {
/// Gets an already existing AbbrevTable given the abbrev_offset, or if not found,
/// seeks in the stream and parses it.
-fn getAbbrevTable(st: &ElfStackTrace, abbrev_offset: u64) %&const AbbrevTable {
+fn getAbbrevTable(st: &ElfStackTrace, abbrev_offset: u64) !&const AbbrevTable {
for (st.abbrev_table_list.toSlice()) |*header| {
if (header.offset == abbrev_offset) {
return &header.table;
@@ -710,7 +710,7 @@ fn getAbbrevTableEntry(abbrev_table: &const AbbrevTable, abbrev_code: u64) ?&con
return null;
}
-fn parseDie(st: &ElfStackTrace, abbrev_table: &const AbbrevTable, is_64: bool) %Die {
+fn parseDie(st: &ElfStackTrace, abbrev_table: &const AbbrevTable, is_64: bool) !Die {
const in_file = &st.self_exe_file;
var in_file_stream = io.FileInStream.init(in_file);
const in_stream = &in_file_stream.stream;
@@ -732,7 +732,7 @@ fn parseDie(st: &ElfStackTrace, abbrev_table: &const AbbrevTable, is_64: bool) %
return result;
}
-fn getLineNumberInfo(st: &ElfStackTrace, compile_unit: &const CompileUnit, target_address: usize) %LineInfo {
+fn getLineNumberInfo(st: &ElfStackTrace, compile_unit: &const CompileUnit, target_address: usize) !LineInfo {
const compile_unit_cwd = try compile_unit.die.getAttrString(st, DW.AT_comp_dir);
const in_file = &st.self_exe_file;
@@ -747,7 +747,7 @@ fn getLineNumberInfo(st: &ElfStackTrace, compile_unit: &const CompileUnit, targe
try in_file.seekTo(this_offset);
var is_64: bool = undefined;
- const unit_length = try readInitialLength(in_stream, &is_64);
+ const unit_length = try readInitialLength(@typeOf(in_stream.readFn).ReturnType.ErrorSet, in_stream, &is_64);
if (unit_length == 0)
return error.MissingDebugInfo;
const next_offset = unit_length + (if (is_64) usize(12) else usize(4));
@@ -910,7 +910,7 @@ fn getLineNumberInfo(st: &ElfStackTrace, compile_unit: &const CompileUnit, targe
return error.MissingDebugInfo;
}
-fn scanAllCompileUnits(st: &ElfStackTrace) %void {
+fn scanAllCompileUnits(st: &ElfStackTrace) !void {
const debug_info_end = st.debug_info.offset + st.debug_info.size;
var this_unit_offset = st.debug_info.offset;
var cu_index: usize = 0;
@@ -922,7 +922,7 @@ fn scanAllCompileUnits(st: &ElfStackTrace) %void {
try st.self_exe_file.seekTo(this_unit_offset);
var is_64: bool = undefined;
- const unit_length = try readInitialLength(in_stream, &is_64);
+ const unit_length = try readInitialLength(@typeOf(in_stream.readFn).ReturnType.ErrorSet, in_stream, &is_64);
if (unit_length == 0)
return;
const next_offset = unit_length + (if (is_64) usize(12) else usize(4));
@@ -986,7 +986,7 @@ fn scanAllCompileUnits(st: &ElfStackTrace) %void {
}
}
-fn findCompileUnit(st: &ElfStackTrace, target_address: u64) %&const CompileUnit {
+fn findCompileUnit(st: &ElfStackTrace, target_address: u64) !&const CompileUnit {
var in_file_stream = io.FileInStream.init(&st.self_exe_file);
const in_stream = &in_file_stream.stream;
for (st.compile_unit_list.toSlice()) |*compile_unit| {
@@ -1022,7 +1022,7 @@ fn findCompileUnit(st: &ElfStackTrace, target_address: u64) %&const CompileUnit
return error.MissingDebugInfo;
}
-fn readInitialLength(in_stream: &io.InStream, is_64: &bool) %u64 {
+fn readInitialLength(comptime E: type, in_stream: &io.InStream(E), is_64: &bool) !u64 {
const first_32_bits = try in_stream.readIntLe(u32);
*is_64 = (first_32_bits == 0xffffffff);
if (*is_64) {
@@ -1033,7 +1033,7 @@ fn readInitialLength(in_stream: &io.InStream, is_64: &bool) %u64 {
}
}
-fn readULeb128(in_stream: &io.InStream) %u64 {
+fn readULeb128(in_stream: var) !u64 {
var result: u64 = 0;
var shift: usize = 0;
@@ -1054,7 +1054,7 @@ fn readULeb128(in_stream: &io.InStream) %u64 {
}
}
-fn readILeb128(in_stream: &io.InStream) %i64 {
+fn readILeb128(in_stream: var) !i64 {
var result: i64 = 0;
var shift: usize = 0;
diff --git a/std/elf.zig b/std/elf.zig
index 59e2150c69..7e20fa000f 100644
--- a/std/elf.zig
+++ b/std/elf.zig
@@ -1,13 +1,12 @@
const builtin = @import("builtin");
const std = @import("index.zig");
const io = std.io;
+const os = std.os;
const math = std.math;
const mem = std.mem;
const debug = std.debug;
const InStream = std.stream.InStream;
-error InvalidFormat;
-
pub const SHT_NULL = 0;
pub const SHT_PROGBITS = 1;
pub const SHT_SYMTAB = 2;
@@ -65,7 +64,7 @@ pub const SectionHeader = struct {
};
pub const Elf = struct {
- in_file: &io.File,
+ in_file: &os.File,
auto_close_stream: bool,
is_64: bool,
endian: builtin.Endian,
@@ -78,17 +77,17 @@ pub const Elf = struct {
string_section: &SectionHeader,
section_headers: []SectionHeader,
allocator: &mem.Allocator,
- prealloc_file: io.File,
+ prealloc_file: os.File,
/// Call close when done.
- pub fn openPath(elf: &Elf, allocator: &mem.Allocator, path: []const u8) %void {
+ pub fn openPath(elf: &Elf, allocator: &mem.Allocator, path: []const u8) !void {
try elf.prealloc_file.open(path);
try elf.openFile(allocator, &elf.prealloc_file);
elf.auto_close_stream = true;
}
/// Call close when done.
- pub fn openFile(elf: &Elf, allocator: &mem.Allocator, file: &io.File) %void {
+ pub fn openFile(elf: &Elf, allocator: &mem.Allocator, file: &os.File) !void {
elf.allocator = allocator;
elf.in_file = file;
elf.auto_close_stream = false;
@@ -239,7 +238,7 @@ pub const Elf = struct {
elf.in_file.close();
}
- pub fn findSection(elf: &Elf, name: []const u8) %?&SectionHeader {
+ pub fn findSection(elf: &Elf, name: []const u8) !?&SectionHeader {
var file_stream = io.FileInStream.init(elf.in_file);
const in = &file_stream.stream;
@@ -263,7 +262,7 @@ pub const Elf = struct {
return null;
}
- pub fn seekToSection(elf: &Elf, elf_section: &SectionHeader) %void {
+ pub fn seekToSection(elf: &Elf, elf_section: &SectionHeader) !void {
try elf.in_file.seekTo(elf_section.offset);
}
};
diff --git a/std/fmt/index.zig b/std/fmt/index.zig
index a32a6e0295..56b0add86d 100644
--- a/std/fmt/index.zig
+++ b/std/fmt/index.zig
@@ -24,8 +24,8 @@ const State = enum { // TODO put inside format function and make sure the name a
/// Renders fmt string with args, calling output with slices of bytes.
/// If `output` returns an error, the error is returned from `format` and
/// `output` is not called again.
-pub fn format(context: var, output: fn(@typeOf(context), []const u8)%void,
- comptime fmt: []const u8, args: ...) %void
+pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8) Errors!void,
+ comptime fmt: []const u8, args: ...) Errors!void
{
comptime var start_index = 0;
comptime var state = State.Start;
@@ -58,7 +58,7 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)%void,
start_index = i;
},
'}' => {
- try formatValue(args[next_arg], context, output);
+ try formatValue(args[next_arg], context, Errors, output);
next_arg += 1;
state = State.Start;
start_index = i + 1;
@@ -110,7 +110,7 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)%void,
},
State.Integer => switch (c) {
'}' => {
- try formatInt(args[next_arg], radix, uppercase, width, context, output);
+ try formatInt(args[next_arg], radix, uppercase, width, context, Errors, output);
next_arg += 1;
state = State.Start;
start_index = i + 1;
@@ -124,7 +124,7 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)%void,
State.IntegerWidth => switch (c) {
'}' => {
width = comptime (parseUnsigned(usize, fmt[width_start..i], 10) catch unreachable);
- try formatInt(args[next_arg], radix, uppercase, width, context, output);
+ try formatInt(args[next_arg], radix, uppercase, width, context, Errors, output);
next_arg += 1;
state = State.Start;
start_index = i + 1;
@@ -134,7 +134,7 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)%void,
},
State.Float => switch (c) {
'}' => {
- try formatFloatDecimal(args[next_arg], 0, context, output);
+ try formatFloatDecimal(args[next_arg], 0, context, Errors, output);
next_arg += 1;
state = State.Start;
start_index = i + 1;
@@ -148,7 +148,7 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)%void,
State.FloatWidth => switch (c) {
'}' => {
width = comptime (parseUnsigned(usize, fmt[width_start..i], 10) catch unreachable);
- try formatFloatDecimal(args[next_arg], width, context, output);
+ try formatFloatDecimal(args[next_arg], width, context, Errors, output);
next_arg += 1;
state = State.Start;
start_index = i + 1;
@@ -159,7 +159,7 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)%void,
State.BufWidth => switch (c) {
'}' => {
width = comptime (parseUnsigned(usize, fmt[width_start..i], 10) catch unreachable);
- try formatBuf(args[next_arg], width, context, output);
+ try formatBuf(args[next_arg], width, context, Errors, output);
next_arg += 1;
state = State.Start;
start_index = i + 1;
@@ -169,7 +169,7 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)%void,
},
State.Character => switch (c) {
'}' => {
- try formatAsciiChar(args[next_arg], context, output);
+ try formatAsciiChar(args[next_arg], context, Errors, output);
next_arg += 1;
state = State.Start;
start_index = i + 1;
@@ -191,14 +191,14 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)%void,
}
}
-pub fn formatValue(value: var, context: var, output: fn(@typeOf(context), []const u8)%void) %void {
+pub fn formatValue(value: var, context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void {
const T = @typeOf(value);
switch (@typeId(T)) {
builtin.TypeId.Int => {
- return formatInt(value, 10, false, 0, context, output);
+ return formatInt(value, 10, false, 0, context, Errors, output);
},
builtin.TypeId.Float => {
- return formatFloat(value, context, output);
+ return formatFloat(value, context, Errors, output);
},
builtin.TypeId.Void => {
return output(context, "void");
@@ -208,19 +208,19 @@ pub fn formatValue(value: var, context: var, output: fn(@typeOf(context), []cons
},
builtin.TypeId.Nullable => {
if (value) |payload| {
- return formatValue(payload, context, output);
+ return formatValue(payload, context, Errors, output);
} else {
return output(context, "null");
}
},
builtin.TypeId.ErrorUnion => {
if (value) |payload| {
- return formatValue(payload, context, output);
+ return formatValue(payload, context, Errors, output);
} else |err| {
- return formatValue(err, context, output);
+ return formatValue(err, context, Errors, output);
}
},
- builtin.TypeId.Error => {
+ builtin.TypeId.ErrorSet => {
try output(context, "error.");
return output(context, @errorName(value));
},
@@ -228,7 +228,7 @@ pub fn formatValue(value: var, context: var, output: fn(@typeOf(context), []cons
if (@typeId(T.Child) == builtin.TypeId.Array and T.Child.Child == u8) {
return output(context, (*value)[0..]);
} else {
- return format(context, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value));
+ return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value));
}
},
else => if (@canImplicitCast([]const u8, value)) {
@@ -240,12 +240,12 @@ pub fn formatValue(value: var, context: var, output: fn(@typeOf(context), []cons
}
}
-pub fn formatAsciiChar(c: u8, context: var, output: fn(@typeOf(context), []const u8)%void) %void {
+pub fn formatAsciiChar(c: u8, context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void {
return output(context, (&c)[0..1]);
}
pub fn formatBuf(buf: []const u8, width: usize,
- context: var, output: fn(@typeOf(context), []const u8)%void) %void
+ context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void
{
try output(context, buf);
@@ -256,7 +256,7 @@ pub fn formatBuf(buf: []const u8, width: usize,
}
}
-pub fn formatFloat(value: var, context: var, output: fn(@typeOf(context), []const u8)%void) %void {
+pub fn formatFloat(value: var, context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void {
var x = f64(value);
// Errol doesn't handle these special cases.
@@ -290,11 +290,11 @@ pub fn formatFloat(value: var, context: var, output: fn(@typeOf(context), []cons
if (float_decimal.exp != 1) {
try output(context, "e");
- try formatInt(float_decimal.exp - 1, 10, false, 0, context, output);
+ try formatInt(float_decimal.exp - 1, 10, false, 0, context, Errors, output);
}
}
-pub fn formatFloatDecimal(value: var, precision: usize, context: var, output: fn(@typeOf(context), []const u8)%void) %void {
+pub fn formatFloatDecimal(value: var, precision: usize, context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void {
var x = f64(value);
// Errol doesn't handle these special cases.
@@ -336,17 +336,17 @@ pub fn formatFloatDecimal(value: var, precision: usize, context: var, output: fn
pub fn formatInt(value: var, base: u8, uppercase: bool, width: usize,
- context: var, output: fn(@typeOf(context), []const u8)%void) %void
+ context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void
{
if (@typeOf(value).is_signed) {
- return formatIntSigned(value, base, uppercase, width, context, output);
+ return formatIntSigned(value, base, uppercase, width, context, Errors, output);
} else {
- return formatIntUnsigned(value, base, uppercase, width, context, output);
+ return formatIntUnsigned(value, base, uppercase, width, context, Errors, output);
}
}
fn formatIntSigned(value: var, base: u8, uppercase: bool, width: usize,
- context: var, output: fn(@typeOf(context), []const u8)%void) %void
+ context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void
{
const uint = @IntType(false, @typeOf(value).bit_count);
if (value < 0) {
@@ -354,20 +354,20 @@ fn formatIntSigned(value: var, base: u8, uppercase: bool, width: usize,
try output(context, (&minus_sign)[0..1]);
const new_value = uint(-(value + 1)) + 1;
const new_width = if (width == 0) 0 else (width - 1);
- return formatIntUnsigned(new_value, base, uppercase, new_width, context, output);
+ return formatIntUnsigned(new_value, base, uppercase, new_width, context, Errors, output);
} else if (width == 0) {
- return formatIntUnsigned(uint(value), base, uppercase, width, context, output);
+ return formatIntUnsigned(uint(value), base, uppercase, width, context, Errors, output);
} else {
const plus_sign: u8 = '+';
try output(context, (&plus_sign)[0..1]);
const new_value = uint(value);
const new_width = if (width == 0) 0 else (width - 1);
- return formatIntUnsigned(new_value, base, uppercase, new_width, context, output);
+ return formatIntUnsigned(new_value, base, uppercase, new_width, context, Errors, output);
}
}
fn formatIntUnsigned(value: var, base: u8, uppercase: bool, width: usize,
- context: var, output: fn(@typeOf(context), []const u8)%void) %void
+ context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void
{
// max_int_digits accounts for the minus sign. when printing an unsigned
// number we don't need to do that.
@@ -410,19 +410,19 @@ pub fn formatIntBuf(out_buf: []u8, value: var, base: u8, uppercase: bool, width:
.out_buf = out_buf,
.index = 0,
};
- formatInt(value, base, uppercase, width, &context, formatIntCallback) catch unreachable;
+ formatInt(value, base, uppercase, width, &context, error{}, formatIntCallback) catch unreachable;
return context.index;
}
const FormatIntBuf = struct {
out_buf: []u8,
index: usize,
};
-fn formatIntCallback(context: &FormatIntBuf, bytes: []const u8) %void {
+fn formatIntCallback(context: &FormatIntBuf, bytes: []const u8) (error{}!void) {
mem.copy(u8, context.out_buf[context.index..], bytes);
context.index += bytes.len;
}
-pub fn parseInt(comptime T: type, buf: []const u8, radix: u8) %T {
+pub fn parseInt(comptime T: type, buf: []const u8, radix: u8) !T {
if (!T.is_signed)
return parseUnsigned(T, buf, radix);
if (buf.len == 0)
@@ -439,14 +439,21 @@ pub fn parseInt(comptime T: type, buf: []const u8, radix: u8) %T {
test "fmt.parseInt" {
assert((parseInt(i32, "-10", 10) catch unreachable) == -10);
assert((parseInt(i32, "+10", 10) catch unreachable) == 10);
- assert(if (parseInt(i32, " 10", 10)) |_| false else |err| err == error.InvalidChar);
- assert(if (parseInt(i32, "10 ", 10)) |_| false else |err| err == error.InvalidChar);
- assert(if (parseInt(u32, "-10", 10)) |_| false else |err| err == error.InvalidChar);
+ assert(if (parseInt(i32, " 10", 10)) |_| false else |err| err == error.InvalidCharacter);
+ assert(if (parseInt(i32, "10 ", 10)) |_| false else |err| err == error.InvalidCharacter);
+ assert(if (parseInt(u32, "-10", 10)) |_| false else |err| err == error.InvalidCharacter);
assert((parseInt(u8, "255", 10) catch unreachable) == 255);
assert(if (parseInt(u8, "256", 10)) |_| false else |err| err == error.Overflow);
}
-pub fn parseUnsigned(comptime T: type, buf: []const u8, radix: u8) %T {
+const ParseUnsignedError = error {
+ /// The result cannot fit in the type specified
+ Overflow,
+ /// The input had a byte that was not a digit
+ InvalidCharacter,
+};
+
+pub fn parseUnsigned(comptime T: type, buf: []const u8, radix: u8) ParseUnsignedError!T {
var x: T = 0;
for (buf) |c| {
@@ -458,17 +465,16 @@ pub fn parseUnsigned(comptime T: type, buf: []const u8, radix: u8) %T {
return x;
}
-error InvalidChar;
-fn charToDigit(c: u8, radix: u8) %u8 {
+fn charToDigit(c: u8, radix: u8) (error{InvalidCharacter}!u8) {
const value = switch (c) {
'0' ... '9' => c - '0',
'A' ... 'Z' => c - 'A' + 10,
'a' ... 'z' => c - 'a' + 10,
- else => return error.InvalidChar,
+ else => return error.InvalidCharacter,
};
if (value >= radix)
- return error.InvalidChar;
+ return error.InvalidCharacter;
return value;
}
@@ -485,28 +491,26 @@ const BufPrintContext = struct {
remaining: []u8,
};
-error BufferTooSmall;
-fn bufPrintWrite(context: &BufPrintContext, bytes: []const u8) %void {
+fn bufPrintWrite(context: &BufPrintContext, bytes: []const u8) !void {
if (context.remaining.len < bytes.len) return error.BufferTooSmall;
mem.copy(u8, context.remaining, bytes);
context.remaining = context.remaining[bytes.len..];
}
-pub fn bufPrint(buf: []u8, comptime fmt: []const u8, args: ...) %[]u8 {
+pub fn bufPrint(buf: []u8, comptime fmt: []const u8, args: ...) ![]u8 {
var context = BufPrintContext { .remaining = buf, };
- try format(&context, bufPrintWrite, fmt, args);
+ try format(&context, error{BufferTooSmall}, bufPrintWrite, fmt, args);
return buf[0..buf.len - context.remaining.len];
}
-pub fn allocPrint(allocator: &mem.Allocator, comptime fmt: []const u8, args: ...) %[]u8 {
+pub fn allocPrint(allocator: &mem.Allocator, comptime fmt: []const u8, args: ...) ![]u8 {
var size: usize = 0;
- // Cannot fail because `countSize` cannot fail.
- format(&size, countSize, fmt, args) catch unreachable;
+ format(&size, error{}, countSize, fmt, args) catch |err| switch (err) {};
const buf = try allocator.alloc(u8, size);
return bufPrint(buf, fmt, args);
}
-fn countSize(size: &usize, bytes: []const u8) %void {
+fn countSize(size: &usize, bytes: []const u8) (error{}!void) {
*size += bytes.len;
}
@@ -534,7 +538,7 @@ fn bufPrintIntToSlice(buf: []u8, value: var, base: u8, uppercase: bool, width: u
test "parse u64 digit too big" {
_ = parseUnsigned(u64, "123a", 10) catch |err| {
- if (err == error.InvalidChar) return;
+ if (err == error.InvalidCharacter) return;
unreachable;
};
unreachable;
@@ -567,13 +571,13 @@ test "fmt.format" {
}
{
var buf1: [32]u8 = undefined;
- const value: %i32 = 1234;
+ const value: error!i32 = 1234;
const result = try bufPrint(buf1[0..], "error union: {}\n", value);
assert(mem.eql(u8, result, "error union: 1234\n"));
}
{
var buf1: [32]u8 = undefined;
- const value: %i32 = error.InvalidChar;
+ const value: error!i32 = error.InvalidChar;
const result = try bufPrint(buf1[0..], "error union: {}\n", value);
assert(mem.eql(u8, result, "error union: error.InvalidChar\n"));
}
diff --git a/std/hash_map.zig b/std/hash_map.zig
index 96ec10b933..659783bc84 100644
--- a/std/hash_map.zig
+++ b/std/hash_map.zig
@@ -80,7 +80,7 @@ pub fn HashMap(comptime K: type, comptime V: type,
}
/// Returns the value that was already there.
- pub fn put(hm: &Self, key: K, value: &const V) %?V {
+ pub fn put(hm: &Self, key: K, value: &const V) !?V {
if (hm.entries.len == 0) {
try hm.initCapacity(16);
}
@@ -151,7 +151,7 @@ pub fn HashMap(comptime K: type, comptime V: type,
};
}
- fn initCapacity(hm: &Self, capacity: usize) %void {
+ fn initCapacity(hm: &Self, capacity: usize) !void {
hm.entries = try hm.allocator.alloc(Entry, capacity);
hm.size = 0;
hm.max_distance_from_start_index = 0;
diff --git a/std/heap.zig b/std/heap.zig
index f023e7376d..2ff0e665c9 100644
--- a/std/heap.zig
+++ b/std/heap.zig
@@ -9,8 +9,6 @@ const c = std.c;
const Allocator = mem.Allocator;
-error OutOfMemory;
-
pub const c_allocator = &c_allocator_state;
var c_allocator_state = Allocator {
.allocFn = cAlloc,
@@ -18,14 +16,14 @@ var c_allocator_state = Allocator {
.freeFn = cFree,
};
-fn cAlloc(self: &Allocator, n: usize, alignment: u29) %[]u8 {
+fn cAlloc(self: &Allocator, n: usize, alignment: u29) ![]u8 {
return if (c.malloc(usize(n))) |buf|
@ptrCast(&u8, buf)[0..n]
else
error.OutOfMemory;
}
-fn cRealloc(self: &Allocator, old_mem: []u8, new_size: usize, alignment: u29) %[]u8 {
+fn cRealloc(self: &Allocator, old_mem: []u8, new_size: usize, alignment: u29) ![]u8 {
const old_ptr = @ptrCast(&c_void, old_mem.ptr);
if (c.realloc(old_ptr, new_size)) |buf| {
return @ptrCast(&u8, buf)[0..new_size];
@@ -47,7 +45,7 @@ pub const IncrementingAllocator = struct {
end_index: usize,
heap_handle: if (builtin.os == Os.windows) os.windows.HANDLE else void,
- fn init(capacity: usize) %IncrementingAllocator {
+ fn init(capacity: usize) !IncrementingAllocator {
switch (builtin.os) {
Os.linux, Os.macosx, Os.ios => {
const p = os.posix;
@@ -105,7 +103,7 @@ pub const IncrementingAllocator = struct {
return self.bytes.len - self.end_index;
}
- fn alloc(allocator: &Allocator, n: usize, alignment: u29) %[]u8 {
+ fn alloc(allocator: &Allocator, n: usize, alignment: u29) ![]u8 {
const self = @fieldParentPtr(IncrementingAllocator, "allocator", allocator);
const addr = @ptrToInt(&self.bytes[self.end_index]);
const rem = @rem(addr, alignment);
@@ -120,7 +118,7 @@ pub const IncrementingAllocator = struct {
return result;
}
- fn realloc(allocator: &Allocator, old_mem: []u8, new_size: usize, alignment: u29) %[]u8 {
+ fn realloc(allocator: &Allocator, old_mem: []u8, new_size: usize, alignment: u29) ![]u8 {
if (new_size <= old_mem.len) {
return old_mem[0..new_size];
} else {
diff --git a/std/index.zig b/std/index.zig
index b5a80cba23..8d292c2f5c 100644
--- a/std/index.zig
+++ b/std/index.zig
@@ -28,6 +28,7 @@ pub const os = @import("os/index.zig");
pub const rand = @import("rand.zig");
pub const sort = @import("sort.zig");
pub const unicode = @import("unicode.zig");
+pub const zig = @import("zig/index.zig");
test "std" {
// run tests from these
@@ -58,4 +59,5 @@ test "std" {
_ = @import("rand.zig");
_ = @import("sort.zig");
_ = @import("unicode.zig");
+ _ = @import("zig/index.zig");
}
diff --git a/std/io.zig b/std/io.zig
index 2fe57e4dfe..94685c4d03 100644
--- a/std/io.zig
+++ b/std/io.zig
@@ -1,12 +1,6 @@
const std = @import("index.zig");
const builtin = @import("builtin");
const Os = builtin.Os;
-const system = switch(builtin.os) {
- Os.linux => @import("os/linux/index.zig"),
- Os.macosx, Os.ios => @import("os/darwin.zig"),
- Os.windows => @import("os/windows/index.zig"),
- else => @compileError("Unsupported OS"),
-};
const c = std.c;
const math = std.math;
@@ -16,65 +10,38 @@ const os = std.os;
const mem = std.mem;
const Buffer = std.Buffer;
const fmt = std.fmt;
+const File = std.os.File;
const is_posix = builtin.os != builtin.Os.windows;
const is_windows = builtin.os == builtin.Os.windows;
-test "import io tests" {
- comptime {
- _ = @import("io_test.zig");
- }
-}
+const GetStdIoErrs = os.WindowsGetStdHandleErrs;
-/// The function received invalid input at runtime. An Invalid error means a
-/// bug in the program that called the function.
-error Invalid;
-
-error DiskQuota;
-error FileTooBig;
-error Io;
-error NoSpaceLeft;
-error BadPerm;
-error BrokenPipe;
-error BadFd;
-error IsDir;
-error NotDir;
-error SymLinkLoop;
-error ProcessFdQuotaExceeded;
-error SystemFdQuotaExceeded;
-error NameTooLong;
-error NoDevice;
-error PathNotFound;
-error OutOfMemory;
-error Unseekable;
-error EndOfFile;
-error FilePosLargerThanPointerRange;
-
-pub fn getStdErr() %File {
+pub fn getStdErr() GetStdIoErrs!File {
const handle = if (is_windows)
- try os.windowsGetStdHandle(system.STD_ERROR_HANDLE)
+ try os.windowsGetStdHandle(os.windows.STD_ERROR_HANDLE)
else if (is_posix)
- system.STDERR_FILENO
+ os.posix.STDERR_FILENO
else
unreachable;
return File.openHandle(handle);
}
-pub fn getStdOut() %File {
+pub fn getStdOut() GetStdIoErrs!File {
const handle = if (is_windows)
- try os.windowsGetStdHandle(system.STD_OUTPUT_HANDLE)
+ try os.windowsGetStdHandle(os.windows.STD_OUTPUT_HANDLE)
else if (is_posix)
- system.STDOUT_FILENO
+ os.posix.STDOUT_FILENO
else
unreachable;
return File.openHandle(handle);
}
-pub fn getStdIn() %File {
+pub fn getStdIn() GetStdIoErrs!File {
const handle = if (is_windows)
- try os.windowsGetStdHandle(system.STD_INPUT_HANDLE)
+ try os.windowsGetStdHandle(os.windows.STD_INPUT_HANDLE)
else if (is_posix)
- system.STDIN_FILENO
+ os.posix.STDIN_FILENO
else
unreachable;
return File.openHandle(handle);
@@ -83,18 +50,21 @@ pub fn getStdIn() %File {
/// Implementation of InStream trait for File
pub const FileInStream = struct {
file: &File,
- stream: InStream,
+ stream: Stream,
+
+ pub const Error = @typeOf(File.read).ReturnType.ErrorSet;
+ pub const Stream = InStream(Error);
pub fn init(file: &File) FileInStream {
return FileInStream {
.file = file,
- .stream = InStream {
+ .stream = Stream {
.readFn = readFn,
},
};
}
- fn readFn(in_stream: &InStream, buffer: []u8) %usize {
+ fn readFn(in_stream: &Stream, buffer: []u8) Error!usize {
const self = @fieldParentPtr(FileInStream, "stream", in_stream);
return self.file.read(buffer);
}
@@ -103,453 +73,202 @@ pub const FileInStream = struct {
/// Implementation of OutStream trait for File
pub const FileOutStream = struct {
file: &File,
- stream: OutStream,
+ stream: Stream,
+
+ pub const Error = File.WriteError;
+ pub const Stream = OutStream(Error);
pub fn init(file: &File) FileOutStream {
return FileOutStream {
.file = file,
- .stream = OutStream {
+ .stream = Stream {
.writeFn = writeFn,
},
};
}
- fn writeFn(out_stream: &OutStream, bytes: []const u8) %void {
+ fn writeFn(out_stream: &Stream, bytes: []const u8) !void {
const self = @fieldParentPtr(FileOutStream, "stream", out_stream);
return self.file.write(bytes);
}
};
-pub const File = struct {
- /// The OS-specific file descriptor or file handle.
- handle: os.FileHandle,
-
- /// `path` may need to be copied in memory to add a null terminating byte. In this case
- /// a fixed size buffer of size std.os.max_noalloc_path_len is an attempted solution. If the fixed
- /// size buffer is too small, and the provided allocator is null, error.NameTooLong is returned.
- /// otherwise if the fixed size buffer is too small, allocator is used to obtain the needed memory.
- /// Call close to clean up.
- pub fn openRead(path: []const u8, allocator: ?&mem.Allocator) %File {
- if (is_posix) {
- const flags = system.O_LARGEFILE|system.O_RDONLY;
- const fd = try os.posixOpen(path, flags, 0, allocator);
- return openHandle(fd);
- } else if (is_windows) {
- const handle = try os.windowsOpen(path, system.GENERIC_READ, system.FILE_SHARE_READ,
- system.OPEN_EXISTING, system.FILE_ATTRIBUTE_NORMAL, allocator);
- return openHandle(handle);
- } else {
- unreachable;
- }
- }
-
- /// Calls `openWriteMode` with 0o666 for the mode.
- pub fn openWrite(path: []const u8, allocator: ?&mem.Allocator) %File {
- return openWriteMode(path, 0o666, allocator);
-
- }
-
- /// `path` may need to be copied in memory to add a null terminating byte. In this case
- /// a fixed size buffer of size std.os.max_noalloc_path_len is an attempted solution. If the fixed
- /// size buffer is too small, and the provided allocator is null, error.NameTooLong is returned.
- /// otherwise if the fixed size buffer is too small, allocator is used to obtain the needed memory.
- /// Call close to clean up.
- pub fn openWriteMode(path: []const u8, mode: usize, allocator: ?&mem.Allocator) %File {
- if (is_posix) {
- const flags = system.O_LARGEFILE|system.O_WRONLY|system.O_CREAT|system.O_CLOEXEC|system.O_TRUNC;
- const fd = try os.posixOpen(path, flags, mode, allocator);
- return openHandle(fd);
- } else if (is_windows) {
- const handle = try os.windowsOpen(path, system.GENERIC_WRITE,
- system.FILE_SHARE_WRITE|system.FILE_SHARE_READ|system.FILE_SHARE_DELETE,
- system.CREATE_ALWAYS, system.FILE_ATTRIBUTE_NORMAL, allocator);
- return openHandle(handle);
- } else {
- unreachable;
- }
-
- }
-
- pub fn openHandle(handle: os.FileHandle) File {
- return File {
- .handle = handle,
- };
- }
+pub fn InStream(comptime ReadError: type) type {
+ return struct {
+ const Self = this;
+ pub const Error = ReadError;
+ /// Return the number of bytes read. If the number read is smaller than buf.len, it
+ /// means the stream reached the end. Reaching the end of a stream is not an error
+ /// condition.
+ readFn: fn(self: &Self, buffer: []u8) Error!usize,
- /// Upon success, the stream is in an uninitialized state. To continue using it,
- /// you must use the open() function.
- pub fn close(self: &File) void {
- os.close(self.handle);
- self.handle = undefined;
- }
+ /// Replaces `buffer` contents by reading from the stream until it is finished.
+ /// If `buffer.len()` would exceed `max_size`, `error.StreamTooLong` is returned and
+ /// the contents read from the stream are lost.
+ pub fn readAllBuffer(self: &Self, buffer: &Buffer, max_size: usize) !void {
+ try buffer.resize(0);
- /// Calls `os.isTty` on `self.handle`.
- pub fn isTty(self: &File) bool {
- return os.isTty(self.handle);
- }
+ var actual_buf_len: usize = 0;
+ while (true) {
+ const dest_slice = buffer.toSlice()[actual_buf_len..];
+ const bytes_read = try self.readFn(self, dest_slice);
+ actual_buf_len += bytes_read;
- pub fn seekForward(self: &File, amount: isize) %void {
- switch (builtin.os) {
- Os.linux, Os.macosx, Os.ios => {
- const result = system.lseek(self.handle, amount, system.SEEK_CUR);
- const err = system.getErrno(result);
- if (err > 0) {
- return switch (err) {
- system.EBADF => error.BadFd,
- system.EINVAL => error.Unseekable,
- system.EOVERFLOW => error.Unseekable,
- system.ESPIPE => error.Unseekable,
- system.ENXIO => error.Unseekable,
- else => os.unexpectedErrorPosix(err),
- };
+ if (bytes_read != dest_slice.len) {
+ buffer.shrink(actual_buf_len);
+ return;
}
- },
- Os.windows => {
- if (system.SetFilePointerEx(self.handle, amount, null, system.FILE_CURRENT) == 0) {
- const err = system.GetLastError();
- return switch (err) {
- system.ERROR.INVALID_PARAMETER => error.BadFd,
- else => os.unexpectedErrorWindows(err),
- };
- }
- },
- else => @compileError("unsupported OS"),
- }
- }
- pub fn seekTo(self: &File, pos: usize) %void {
- switch (builtin.os) {
- Os.linux, Os.macosx, Os.ios => {
- const ipos = try math.cast(isize, pos);
- const result = system.lseek(self.handle, ipos, system.SEEK_SET);
- const err = system.getErrno(result);
- if (err > 0) {
- return switch (err) {
- system.EBADF => error.BadFd,
- system.EINVAL => error.Unseekable,
- system.EOVERFLOW => error.Unseekable,
- system.ESPIPE => error.Unseekable,
- system.ENXIO => error.Unseekable,
- else => os.unexpectedErrorPosix(err),
- };
- }
- },
- Os.windows => {
- const ipos = try math.cast(isize, pos);
- if (system.SetFilePointerEx(self.handle, ipos, null, system.FILE_BEGIN) == 0) {
- const err = system.GetLastError();
- return switch (err) {
- system.ERROR.INVALID_PARAMETER => error.BadFd,
- else => os.unexpectedErrorWindows(err),
- };
- }
- },
- else => @compileError("unsupported OS: " ++ @tagName(builtin.os)),
+ const new_buf_size = math.min(max_size, actual_buf_len + os.page_size);
+ if (new_buf_size == actual_buf_len)
+ return error.StreamTooLong;
+ try buffer.resize(new_buf_size);
+ }
}
- }
- pub fn getPos(self: &File) %usize {
- switch (builtin.os) {
- Os.linux, Os.macosx, Os.ios => {
- const result = system.lseek(self.handle, 0, system.SEEK_CUR);
- const err = system.getErrno(result);
- if (err > 0) {
- return switch (err) {
- system.EBADF => error.BadFd,
- system.EINVAL => error.Unseekable,
- system.EOVERFLOW => error.Unseekable,
- system.ESPIPE => error.Unseekable,
- system.ENXIO => error.Unseekable,
- else => os.unexpectedErrorPosix(err),
- };
- }
- return result;
- },
- Os.windows => {
- var pos : system.LARGE_INTEGER = undefined;
- if (system.SetFilePointerEx(self.handle, 0, &pos, system.FILE_CURRENT) == 0) {
- const err = system.GetLastError();
- return switch (err) {
- system.ERROR.INVALID_PARAMETER => error.BadFd,
- else => os.unexpectedErrorWindows(err),
- };
- }
-
- assert(pos >= 0);
- if (@sizeOf(@typeOf(pos)) > @sizeOf(usize)) {
- if (pos > @maxValue(usize)) {
- return error.FilePosLargerThanPointerRange;
- }
- }
+ /// Allocates enough memory to hold all the contents of the stream. If the allocated
+ /// memory would be greater than `max_size`, returns `error.StreamTooLong`.
+ /// Caller owns returned memory.
+ /// If this function returns an error, the contents from the stream read so far are lost.
+ pub fn readAllAlloc(self: &Self, allocator: &mem.Allocator, max_size: usize) ![]u8 {
+ var buf = Buffer.initNull(allocator);
+ defer buf.deinit();
- return usize(pos);
- },
- else => @compileError("unsupported OS"),
+ try self.readAllBuffer(&buf, max_size);
+ return buf.toOwnedSlice();
}
- }
- pub fn getEndPos(self: &File) %usize {
- if (is_posix) {
- var stat: system.Stat = undefined;
- const err = system.getErrno(system.fstat(self.handle, &stat));
- if (err > 0) {
- return switch (err) {
- system.EBADF => error.BadFd,
- system.ENOMEM => error.SystemResources,
- else => os.unexpectedErrorPosix(err),
- };
- }
+ /// Replaces `buffer` contents by reading from the stream until `delimiter` is found.
+ /// Does not include the delimiter in the result.
+ /// If `buffer.len()` would exceed `max_size`, `error.StreamTooLong` is returned and the contents
+ /// read from the stream so far are lost.
+ pub fn readUntilDelimiterBuffer(self: &Self, buffer: &Buffer, delimiter: u8, max_size: usize) !void {
+ try buf.resize(0);
- return usize(stat.size);
- } else if (is_windows) {
- var file_size: system.LARGE_INTEGER = undefined;
- if (system.GetFileSizeEx(self.handle, &file_size) == 0) {
- const err = system.GetLastError();
- return switch (err) {
- else => os.unexpectedErrorWindows(err),
- };
- }
- if (file_size < 0)
- return error.Overflow;
- return math.cast(usize, u64(file_size));
- } else {
- unreachable;
- }
- }
+ while (true) {
+ var byte: u8 = try self.readByte();
- pub fn read(self: &File, buffer: []u8) %usize {
- if (is_posix) {
- var index: usize = 0;
- while (index < buffer.len) {
- const amt_read = system.read(self.handle, &buffer[index], buffer.len - index);
- const read_err = system.getErrno(amt_read);
- if (read_err > 0) {
- switch (read_err) {
- system.EINTR => continue,
- system.EINVAL => unreachable,
- system.EFAULT => unreachable,
- system.EBADF => return error.BadFd,
- system.EIO => return error.Io,
- else => return os.unexpectedErrorPosix(read_err),
- }
+ if (byte == delimiter) {
+ return;
}
- if (amt_read == 0) return index;
- index += amt_read;
- }
- return index;
- } else if (is_windows) {
- var index: usize = 0;
- while (index < buffer.len) {
- const want_read_count = system.DWORD(math.min(system.DWORD(@maxValue(system.DWORD)), buffer.len - index));
- var amt_read: system.DWORD = undefined;
- if (system.ReadFile(self.handle, @ptrCast(&c_void, &buffer[index]), want_read_count, &amt_read, null) == 0) {
- const err = system.GetLastError();
- return switch (err) {
- system.ERROR.OPERATION_ABORTED => continue,
- system.ERROR.BROKEN_PIPE => return index,
- else => os.unexpectedErrorWindows(err),
- };
+
+ if (buf.len() == max_size) {
+ return error.StreamTooLong;
}
- if (amt_read == 0) return index;
- index += amt_read;
+
+ try buf.appendByte(byte);
}
- return index;
- } else {
- unreachable;
}
- }
- fn write(self: &File, bytes: []const u8) %void {
- if (is_posix) {
- try os.posixWrite(self.handle, bytes);
- } else if (is_windows) {
- try os.windowsWrite(self.handle, bytes);
- } else {
- @compileError("Unsupported OS");
+ /// Allocates enough memory to read until `delimiter`. If the allocated
+ /// memory would be greater than `max_size`, returns `error.StreamTooLong`.
+ /// Caller owns returned memory.
+ /// If this function returns an error, the contents from the stream read so far are lost.
+ pub fn readUntilDelimiterAlloc(self: &Self, allocator: &mem.Allocator,
+ delimiter: u8, max_size: usize) ![]u8
+ {
+ var buf = Buffer.initNull(allocator);
+ defer buf.deinit();
+
+ try self.readUntilDelimiterBuffer(self, &buf, delimiter, max_size);
+ return buf.toOwnedSlice();
}
- }
-};
-
-error StreamTooLong;
-error EndOfStream;
-
-pub const InStream = struct {
- /// Return the number of bytes read. If the number read is smaller than buf.len, it
- /// means the stream reached the end. Reaching the end of a stream is not an error
- /// condition.
- readFn: fn(self: &InStream, buffer: []u8) %usize,
-
- /// Replaces `buffer` contents by reading from the stream until it is finished.
- /// If `buffer.len()` would exceed `max_size`, `error.StreamTooLong` is returned and
- /// the contents read from the stream are lost.
- pub fn readAllBuffer(self: &InStream, buffer: &Buffer, max_size: usize) %void {
- try buffer.resize(0);
-
- var actual_buf_len: usize = 0;
- while (true) {
- const dest_slice = buffer.toSlice()[actual_buf_len..];
- const bytes_read = try self.readFn(self, dest_slice);
- actual_buf_len += bytes_read;
-
- if (bytes_read != dest_slice.len) {
- buffer.shrink(actual_buf_len);
- return;
- }
- const new_buf_size = math.min(max_size, actual_buf_len + os.page_size);
- if (new_buf_size == actual_buf_len)
- return error.StreamTooLong;
- try buffer.resize(new_buf_size);
+ /// Returns the number of bytes read. If the number read is smaller than buf.len, it
+ /// means the stream reached the end. Reaching the end of a stream is not an error
+ /// condition.
+ pub fn read(self: &Self, buffer: []u8) !usize {
+ return self.readFn(self, buffer);
}
- }
-
- /// Allocates enough memory to hold all the contents of the stream. If the allocated
- /// memory would be greater than `max_size`, returns `error.StreamTooLong`.
- /// Caller owns returned memory.
- /// If this function returns an error, the contents from the stream read so far are lost.
- pub fn readAllAlloc(self: &InStream, allocator: &mem.Allocator, max_size: usize) %[]u8 {
- var buf = Buffer.initNull(allocator);
- defer buf.deinit();
-
- try self.readAllBuffer(&buf, max_size);
- return buf.toOwnedSlice();
- }
-
- /// Replaces `buffer` contents by reading from the stream until `delimiter` is found.
- /// Does not include the delimiter in the result.
- /// If `buffer.len()` would exceed `max_size`, `error.StreamTooLong` is returned and the contents
- /// read from the stream so far are lost.
- pub fn readUntilDelimiterBuffer(self: &InStream, buffer: &Buffer, delimiter: u8, max_size: usize) %void {
- try buf.resize(0);
-
- while (true) {
- var byte: u8 = try self.readByte();
-
- if (byte == delimiter) {
- return;
- }
- if (buf.len() == max_size) {
- return error.StreamTooLong;
- }
-
- try buf.appendByte(byte);
+ /// Same as `read` but end of stream returns `error.EndOfStream`.
+ pub fn readNoEof(self: &Self, buf: []u8) !void {
+ const amt_read = try self.read(buf);
+ if (amt_read < buf.len) return error.EndOfStream;
}
- }
-
- /// Allocates enough memory to read until `delimiter`. If the allocated
- /// memory would be greater than `max_size`, returns `error.StreamTooLong`.
- /// Caller owns returned memory.
- /// If this function returns an error, the contents from the stream read so far are lost.
- pub fn readUntilDelimiterAlloc(self: &InStream, allocator: &mem.Allocator,
- delimiter: u8, max_size: usize) %[]u8
- {
- var buf = Buffer.initNull(allocator);
- defer buf.deinit();
-
- try self.readUntilDelimiterBuffer(self, &buf, delimiter, max_size);
- return buf.toOwnedSlice();
- }
-
- /// Returns the number of bytes read. If the number read is smaller than buf.len, it
- /// means the stream reached the end. Reaching the end of a stream is not an error
- /// condition.
- pub fn read(self: &InStream, buffer: []u8) %usize {
- return self.readFn(self, buffer);
- }
-
- /// Same as `read` but end of stream returns `error.EndOfStream`.
- pub fn readNoEof(self: &InStream, buf: []u8) %void {
- const amt_read = try self.read(buf);
- if (amt_read < buf.len) return error.EndOfStream;
- }
- /// Reads 1 byte from the stream or returns `error.EndOfStream`.
- pub fn readByte(self: &InStream) %u8 {
- var result: [1]u8 = undefined;
- try self.readNoEof(result[0..]);
- return result[0];
- }
-
- /// Same as `readByte` except the returned byte is signed.
- pub fn readByteSigned(self: &InStream) %i8 {
- return @bitCast(i8, try self.readByte());
- }
+ /// Reads 1 byte from the stream or returns `error.EndOfStream`.
+ pub fn readByte(self: &Self) !u8 {
+ var result: [1]u8 = undefined;
+ try self.readNoEof(result[0..]);
+ return result[0];
+ }
- pub fn readIntLe(self: &InStream, comptime T: type) %T {
- return self.readInt(builtin.Endian.Little, T);
- }
+ /// Same as `readByte` except the returned byte is signed.
+ pub fn readByteSigned(self: &Self) !i8 {
+ return @bitCast(i8, try self.readByte());
+ }
- pub fn readIntBe(self: &InStream, comptime T: type) %T {
- return self.readInt(builtin.Endian.Big, T);
- }
+ pub fn readIntLe(self: &Self, comptime T: type) !T {
+ return self.readInt(builtin.Endian.Little, T);
+ }
- pub fn readInt(self: &InStream, endian: builtin.Endian, comptime T: type) %T {
- var bytes: [@sizeOf(T)]u8 = undefined;
- try self.readNoEof(bytes[0..]);
- return mem.readInt(bytes, T, endian);
- }
+ pub fn readIntBe(self: &Self, comptime T: type) !T {
+ return self.readInt(builtin.Endian.Big, T);
+ }
- pub fn readVarInt(self: &InStream, endian: builtin.Endian, comptime T: type, size: usize) %T {
- assert(size <= @sizeOf(T));
- assert(size <= 8);
- var input_buf: [8]u8 = undefined;
- const input_slice = input_buf[0..size];
- try self.readNoEof(input_slice);
- return mem.readInt(input_slice, T, endian);
- }
+ pub fn readInt(self: &Self, endian: builtin.Endian, comptime T: type) !T {
+ var bytes: [@sizeOf(T)]u8 = undefined;
+ try self.readNoEof(bytes[0..]);
+ return mem.readInt(bytes, T, endian);
+ }
+ pub fn readVarInt(self: &Self, endian: builtin.Endian, comptime T: type, size: usize) !T {
+ assert(size <= @sizeOf(T));
+ assert(size <= 8);
+ var input_buf: [8]u8 = undefined;
+ const input_slice = input_buf[0..size];
+ try self.readNoEof(input_slice);
+ return mem.readInt(input_slice, T, endian);
+ }
+ };
+}
-};
+pub fn OutStream(comptime WriteError: type) type {
+ return struct {
+ const Self = this;
+ pub const Error = WriteError;
-pub const OutStream = struct {
- writeFn: fn(self: &OutStream, bytes: []const u8) %void,
+ writeFn: fn(self: &Self, bytes: []const u8) Error!void,
- pub fn print(self: &OutStream, comptime format: []const u8, args: ...) %void {
- return std.fmt.format(self, self.writeFn, format, args);
- }
+ pub fn print(self: &Self, comptime format: []const u8, args: ...) !void {
+ return std.fmt.format(self, Error, self.writeFn, format, args);
+ }
- pub fn write(self: &OutStream, bytes: []const u8) %void {
- return self.writeFn(self, bytes);
- }
+ pub fn write(self: &Self, bytes: []const u8) !void {
+ return self.writeFn(self, bytes);
+ }
- pub fn writeByte(self: &OutStream, byte: u8) %void {
- const slice = (&byte)[0..1];
- return self.writeFn(self, slice);
- }
+ pub fn writeByte(self: &Self, byte: u8) !void {
+ const slice = (&byte)[0..1];
+ return self.writeFn(self, slice);
+ }
- pub fn writeByteNTimes(self: &OutStream, byte: u8, n: usize) %void {
- const slice = (&byte)[0..1];
- var i: usize = 0;
- while (i < n) : (i += 1) {
- try self.writeFn(self, slice);
+ pub fn writeByteNTimes(self: &Self, byte: u8, n: usize) !void {
+ const slice = (&byte)[0..1];
+ var i: usize = 0;
+ while (i < n) : (i += 1) {
+ try self.writeFn(self, slice);
+ }
}
- }
-};
+ };
+}
-/// `path` may need to be copied in memory to add a null terminating byte. In this case
-/// a fixed size buffer of size `std.os.max_noalloc_path_len` is an attempted solution. If the fixed
-/// size buffer is too small, and the provided allocator is null, `error.NameTooLong` is returned.
-/// otherwise if the fixed size buffer is too small, allocator is used to obtain the needed memory.
-pub fn writeFile(path: []const u8, data: []const u8, allocator: ?&mem.Allocator) %void {
- var file = try File.openWrite(path, allocator);
+/// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
+pub fn writeFile(allocator: &mem.Allocator, path: []const u8, data: []const u8) !void {
+ var file = try File.openWrite(allocator, path);
defer file.close();
try file.write(data);
}
/// On success, caller owns returned buffer.
-pub fn readFileAlloc(path: []const u8, allocator: &mem.Allocator) %[]u8 {
- return readFileAllocExtra(path, allocator, 0);
-}
-/// On success, caller owns returned buffer.
-/// Allocates extra_len extra bytes at the end of the file buffer, which are uninitialized.
-pub fn readFileAllocExtra(path: []const u8, allocator: &mem.Allocator, extra_len: usize) %[]u8 {
- var file = try File.openRead(path, allocator);
+pub fn readFileAlloc(allocator: &mem.Allocator, path: []const u8) ![]u8 {
+ var file = try File.openRead(allocator, path);
defer file.close();
const size = try file.getEndPos();
- const buf = try allocator.alloc(u8, size + extra_len);
+ const buf = try allocator.alloc(u8, size);
errdefer allocator.free(buf);
var adapter = FileInStream.init(&file);
@@ -557,21 +276,24 @@ pub fn readFileAllocExtra(path: []const u8, allocator: &mem.Allocator, extra_len
return buf;
}
-pub const BufferedInStream = BufferedInStreamCustom(os.page_size);
+pub fn BufferedInStream(comptime Error: type) type {
+ return BufferedInStreamCustom(os.page_size, Error);
+}
-pub fn BufferedInStreamCustom(comptime buffer_size: usize) type {
+pub fn BufferedInStreamCustom(comptime buffer_size: usize, comptime Error: type) type {
return struct {
const Self = this;
+ const Stream = InStream(Error);
- pub stream: InStream,
+ pub stream: Stream,
- unbuffered_in_stream: &InStream,
+ unbuffered_in_stream: &Stream,
buffer: [buffer_size]u8,
start_index: usize,
end_index: usize,
- pub fn init(unbuffered_in_stream: &InStream) Self {
+ pub fn init(unbuffered_in_stream: &Stream) Self {
return Self {
.unbuffered_in_stream = unbuffered_in_stream,
.buffer = undefined,
@@ -583,13 +305,13 @@ pub fn BufferedInStreamCustom(comptime buffer_size: usize) type {
.start_index = buffer_size,
.end_index = buffer_size,
- .stream = InStream {
+ .stream = Stream {
.readFn = readFn,
},
};
}
- fn readFn(in_stream: &InStream, dest: []u8) %usize {
+ fn readFn(in_stream: &Stream, dest: []u8) !usize {
const self = @fieldParentPtr(Self, "stream", in_stream);
var dest_index: usize = 0;
@@ -628,39 +350,40 @@ pub fn BufferedInStreamCustom(comptime buffer_size: usize) type {
};
}
-pub const BufferedOutStream = BufferedOutStreamCustom(os.page_size);
+pub fn BufferedOutStream(comptime Error: type) type {
+ return BufferedOutStreamCustom(os.page_size, Error);
+}
-pub fn BufferedOutStreamCustom(comptime buffer_size: usize) type {
+pub fn BufferedOutStreamCustom(comptime buffer_size: usize, comptime OutStreamError: type) type {
return struct {
const Self = this;
+ pub const Stream = OutStream(Error);
+ pub const Error = OutStreamError;
- pub stream: OutStream,
+ pub stream: Stream,
- unbuffered_out_stream: &OutStream,
+ unbuffered_out_stream: &Stream,
buffer: [buffer_size]u8,
index: usize,
- pub fn init(unbuffered_out_stream: &OutStream) Self {
+ pub fn init(unbuffered_out_stream: &Stream) Self {
return Self {
.unbuffered_out_stream = unbuffered_out_stream,
.buffer = undefined,
.index = 0,
- .stream = OutStream {
+ .stream = Stream {
.writeFn = writeFn,
},
};
}
- pub fn flush(self: &Self) %void {
- if (self.index == 0)
- return;
-
+ pub fn flush(self: &Self) !void {
try self.unbuffered_out_stream.write(self.buffer[0..self.index]);
self.index = 0;
}
- fn writeFn(out_stream: &OutStream, bytes: []const u8) %void {
+ fn writeFn(out_stream: &Stream, bytes: []const u8) !void {
const self = @fieldParentPtr(Self, "stream", out_stream);
if (bytes.len >= self.buffer.len) {
@@ -687,20 +410,71 @@ pub fn BufferedOutStreamCustom(comptime buffer_size: usize) type {
/// Implementation of OutStream trait for Buffer
pub const BufferOutStream = struct {
buffer: &Buffer,
- stream: OutStream,
+ stream: Stream,
+
+ pub const Error = error{OutOfMemory};
+ pub const Stream = OutStream(Error);
pub fn init(buffer: &Buffer) BufferOutStream {
return BufferOutStream {
.buffer = buffer,
- .stream = OutStream {
+ .stream = Stream {
.writeFn = writeFn,
},
};
}
- fn writeFn(out_stream: &OutStream, bytes: []const u8) %void {
+ fn writeFn(out_stream: &Stream, bytes: []const u8) !void {
const self = @fieldParentPtr(BufferOutStream, "stream", out_stream);
return self.buffer.append(bytes);
}
};
+
+pub const BufferedAtomicFile = struct {
+ atomic_file: os.AtomicFile,
+ file_stream: FileOutStream,
+ buffered_stream: BufferedOutStream(FileOutStream.Error),
+
+ pub fn create(allocator: &mem.Allocator, dest_path: []const u8) !&BufferedAtomicFile {
+ // TODO with well defined copy elision we don't need this allocation
+ var self = try allocator.create(BufferedAtomicFile);
+ errdefer allocator.destroy(self);
+
+ *self = BufferedAtomicFile {
+ .atomic_file = undefined,
+ .file_stream = undefined,
+ .buffered_stream = undefined,
+ };
+
+ self.atomic_file = try os.AtomicFile.init(allocator, dest_path, os.default_file_mode);
+ errdefer self.atomic_file.deinit();
+
+ self.file_stream = FileOutStream.init(&self.atomic_file.file);
+ self.buffered_stream = BufferedOutStream(FileOutStream.Error).init(&self.file_stream.stream);
+ return self;
+ }
+
+ /// always call destroy, even after successful finish()
+ pub fn destroy(self: &BufferedAtomicFile) void {
+ const allocator = self.atomic_file.allocator;
+ self.atomic_file.deinit();
+ allocator.destroy(self);
+ }
+
+ pub fn finish(self: &BufferedAtomicFile) !void {
+ try self.buffered_stream.flush();
+ try self.atomic_file.finish();
+ }
+
+ pub fn stream(self: &BufferedAtomicFile) &OutStream(FileOutStream.Error) {
+ return &self.buffered_stream.stream;
+ }
+};
+
+test "import io tests" {
+ comptime {
+ _ = @import("io_test.zig");
+ }
+}
+
diff --git a/std/io_test.zig b/std/io_test.zig
index 1767a546ea..993ec84d20 100644
--- a/std/io_test.zig
+++ b/std/io_test.zig
@@ -13,11 +13,11 @@ test "write a file, read it, then delete it" {
rng.fillBytes(data[0..]);
const tmp_file_name = "temp_test_file.txt";
{
- var file = try io.File.openWrite(tmp_file_name, allocator);
+ var file = try os.File.openWrite(allocator, tmp_file_name);
defer file.close();
var file_out_stream = io.FileOutStream.init(&file);
- var buf_stream = io.BufferedOutStream.init(&file_out_stream.stream);
+ var buf_stream = io.BufferedOutStream(io.FileOutStream.Error).init(&file_out_stream.stream);
const st = &buf_stream.stream;
try st.print("begin");
try st.write(data[0..]);
@@ -25,7 +25,7 @@ test "write a file, read it, then delete it" {
try buf_stream.flush();
}
{
- var file = try io.File.openRead(tmp_file_name, allocator);
+ var file = try os.File.openRead(allocator, tmp_file_name);
defer file.close();
const file_size = try file.getEndPos();
@@ -33,7 +33,7 @@ test "write a file, read it, then delete it" {
assert(file_size == expected_file_size);
var file_in_stream = io.FileInStream.init(&file);
- var buf_stream = io.BufferedInStream.init(&file_in_stream.stream);
+ var buf_stream = io.BufferedInStream(io.FileInStream.Error).init(&file_in_stream.stream);
const st = &buf_stream.stream;
const contents = try st.readAllAlloc(allocator, 2 * 1024);
defer allocator.free(contents);
diff --git a/std/linked_list.zig b/std/linked_list.zig
index ffd68d5147..a6ab093341 100644
--- a/std/linked_list.zig
+++ b/std/linked_list.zig
@@ -190,7 +190,7 @@ fn BaseLinkedList(comptime T: type, comptime ParentType: type, comptime field_na
///
/// Returns:
/// A pointer to the new node.
- pub fn allocateNode(list: &Self, allocator: &Allocator) %&Node {
+ pub fn allocateNode(list: &Self, allocator: &Allocator) !&Node {
comptime assert(!isIntrusive());
return allocator.create(Node);
}
@@ -213,7 +213,7 @@ fn BaseLinkedList(comptime T: type, comptime ParentType: type, comptime field_na
///
/// Returns:
/// A pointer to the new node.
- pub fn createNode(list: &Self, data: &const T, allocator: &Allocator) %&Node {
+ pub fn createNode(list: &Self, data: &const T, allocator: &Allocator) !&Node {
comptime assert(!isIntrusive());
var node = try list.allocateNode(allocator);
*node = Node.init(data);
diff --git a/std/math/index.zig b/std/math/index.zig
index 64d24a4dfd..f8668cc00d 100644
--- a/std/math/index.zig
+++ b/std/math/index.zig
@@ -191,30 +191,26 @@ test "math.max" {
assert(max(i32(-1), i32(2)) == 2);
}
-error Overflow;
-pub fn mul(comptime T: type, a: T, b: T) %T {
+pub fn mul(comptime T: type, a: T, b: T) (error{Overflow}!T) {
var answer: T = undefined;
return if (@mulWithOverflow(T, a, b, &answer)) error.Overflow else answer;
}
-error Overflow;
-pub fn add(comptime T: type, a: T, b: T) %T {
+pub fn add(comptime T: type, a: T, b: T) (error{Overflow}!T) {
var answer: T = undefined;
return if (@addWithOverflow(T, a, b, &answer)) error.Overflow else answer;
}
-error Overflow;
-pub fn sub(comptime T: type, a: T, b: T) %T {
+pub fn sub(comptime T: type, a: T, b: T) (error{Overflow}!T) {
var answer: T = undefined;
return if (@subWithOverflow(T, a, b, &answer)) error.Overflow else answer;
}
-pub fn negate(x: var) %@typeOf(x) {
+pub fn negate(x: var) !@typeOf(x) {
return sub(@typeOf(x), 0, x);
}
-error Overflow;
-pub fn shlExact(comptime T: type, a: T, shift_amt: Log2Int(T)) %T {
+pub fn shlExact(comptime T: type, a: T, shift_amt: Log2Int(T)) !T {
var answer: T = undefined;
return if (@shlWithOverflow(T, a, shift_amt, &answer)) error.Overflow else answer;
}
@@ -323,8 +319,7 @@ fn testOverflow() void {
}
-error Overflow;
-pub fn absInt(x: var) %@typeOf(x) {
+pub fn absInt(x: var) !@typeOf(x) {
const T = @typeOf(x);
comptime assert(@typeId(T) == builtin.TypeId.Int); // must pass an integer to absInt
comptime assert(T.is_signed); // must pass a signed integer to absInt
@@ -347,9 +342,7 @@ fn testAbsInt() void {
pub const absFloat = @import("fabs.zig").fabs;
-error DivisionByZero;
-error Overflow;
-pub fn divTrunc(comptime T: type, numerator: T, denominator: T) %T {
+pub fn divTrunc(comptime T: type, numerator: T, denominator: T) !T {
@setRuntimeSafety(false);
if (denominator == 0)
return error.DivisionByZero;
@@ -372,9 +365,7 @@ fn testDivTrunc() void {
assert((divTrunc(f32, -5.0, 3.0) catch unreachable) == -1.0);
}
-error DivisionByZero;
-error Overflow;
-pub fn divFloor(comptime T: type, numerator: T, denominator: T) %T {
+pub fn divFloor(comptime T: type, numerator: T, denominator: T) !T {
@setRuntimeSafety(false);
if (denominator == 0)
return error.DivisionByZero;
@@ -397,10 +388,7 @@ fn testDivFloor() void {
assert((divFloor(f32, -5.0, 3.0) catch unreachable) == -2.0);
}
-error DivisionByZero;
-error Overflow;
-error UnexpectedRemainder;
-pub fn divExact(comptime T: type, numerator: T, denominator: T) %T {
+pub fn divExact(comptime T: type, numerator: T, denominator: T) !T {
@setRuntimeSafety(false);
if (denominator == 0)
return error.DivisionByZero;
@@ -428,9 +416,7 @@ fn testDivExact() void {
if (divExact(f32, 5.0, 2.0)) |_| unreachable else |err| assert(err == error.UnexpectedRemainder);
}
-error DivisionByZero;
-error NegativeDenominator;
-pub fn mod(comptime T: type, numerator: T, denominator: T) %T {
+pub fn mod(comptime T: type, numerator: T, denominator: T) !T {
@setRuntimeSafety(false);
if (denominator == 0)
return error.DivisionByZero;
@@ -455,9 +441,7 @@ fn testMod() void {
if (mod(f32, 10, 0)) |_| unreachable else |err| assert(err == error.DivisionByZero);
}
-error DivisionByZero;
-error NegativeDenominator;
-pub fn rem(comptime T: type, numerator: T, denominator: T) %T {
+pub fn rem(comptime T: type, numerator: T, denominator: T) !T {
@setRuntimeSafety(false);
if (denominator == 0)
return error.DivisionByZero;
@@ -505,8 +489,7 @@ test "math.absCast" {
/// Returns the negation of the integer parameter.
/// Result is a signed integer.
-error Overflow;
-pub fn negateCast(x: var) %@IntType(true, @typeOf(x).bit_count) {
+pub fn negateCast(x: var) !@IntType(true, @typeOf(x).bit_count) {
if (@typeOf(x).is_signed)
return negate(x);
@@ -532,8 +515,7 @@ test "math.negateCast" {
/// Cast an integer to a different integer type. If the value doesn't fit,
/// return an error.
-error Overflow;
-pub fn cast(comptime T: type, x: var) %T {
+pub fn cast(comptime T: type, x: var) !T {
comptime assert(@typeId(T) == builtin.TypeId.Int); // must pass an integer
if (x > @maxValue(T)) {
return error.Overflow;
diff --git a/std/mem.zig b/std/mem.zig
index 86bf5e2f3d..25c0648888 100644
--- a/std/mem.zig
+++ b/std/mem.zig
@@ -4,13 +4,13 @@ const assert = debug.assert;
const math = std.math;
const builtin = @import("builtin");
-error OutOfMemory;
-
pub const Allocator = struct {
+ const Error = error {OutOfMemory};
+
/// Allocate byte_count bytes and return them in a slice, with the
/// slice's pointer aligned at least to alignment bytes.
/// The returned newly allocated memory is undefined.
- allocFn: fn (self: &Allocator, byte_count: usize, alignment: u29) %[]u8,
+ allocFn: fn (self: &Allocator, byte_count: usize, alignment: u29) Error![]u8,
/// If `new_byte_count > old_mem.len`:
/// * `old_mem.len` is the same as what was returned from allocFn or reallocFn.
@@ -21,12 +21,12 @@ pub const Allocator = struct {
/// * alignment <= alignment of old_mem.ptr
///
/// The returned newly allocated memory is undefined.
- reallocFn: fn (self: &Allocator, old_mem: []u8, new_byte_count: usize, alignment: u29) %[]u8,
+ reallocFn: fn (self: &Allocator, old_mem: []u8, new_byte_count: usize, alignment: u29) Error![]u8,
/// Guaranteed: `old_mem.len` is the same as what was returned from `allocFn` or `reallocFn`
freeFn: fn (self: &Allocator, old_mem: []u8) void,
- fn create(self: &Allocator, comptime T: type) %&T {
+ fn create(self: &Allocator, comptime T: type) !&T {
const slice = try self.alloc(T, 1);
return &slice[0];
}
@@ -35,14 +35,14 @@ pub const Allocator = struct {
self.free(ptr[0..1]);
}
- fn alloc(self: &Allocator, comptime T: type, n: usize) %[]T {
+ fn alloc(self: &Allocator, comptime T: type, n: usize) ![]T {
return self.alignedAlloc(T, @alignOf(T), n);
}
fn alignedAlloc(self: &Allocator, comptime T: type, comptime alignment: u29,
- n: usize) %[]align(alignment) T
+ n: usize) ![]align(alignment) T
{
- const byte_count = try math.mul(usize, @sizeOf(T), n);
+ const byte_count = math.mul(usize, @sizeOf(T), n) catch return Error.OutOfMemory;
const byte_slice = try self.allocFn(self, byte_count, alignment);
// This loop should get optimized out in ReleaseFast mode
for (byte_slice) |*byte| {
@@ -51,19 +51,19 @@ pub const Allocator = struct {
return ([]align(alignment) T)(@alignCast(alignment, byte_slice));
}
- fn realloc(self: &Allocator, comptime T: type, old_mem: []T, n: usize) %[]T {
+ fn realloc(self: &Allocator, comptime T: type, old_mem: []T, n: usize) ![]T {
return self.alignedRealloc(T, @alignOf(T), @alignCast(@alignOf(T), old_mem), n);
}
fn alignedRealloc(self: &Allocator, comptime T: type, comptime alignment: u29,
- old_mem: []align(alignment) T, n: usize) %[]align(alignment) T
+ old_mem: []align(alignment) T, n: usize) ![]align(alignment) T
{
if (old_mem.len == 0) {
return self.alloc(T, n);
}
const old_byte_slice = ([]u8)(old_mem);
- const byte_count = try math.mul(usize, @sizeOf(T), n);
+ const byte_count = math.mul(usize, @sizeOf(T), n) catch return Error.OutOfMemory;
const byte_slice = try self.reallocFn(self, old_byte_slice, byte_count, alignment);
// This loop should get optimized out in ReleaseFast mode
for (byte_slice[old_byte_slice.len..]) |*byte| {
@@ -123,7 +123,7 @@ pub const FixedBufferAllocator = struct {
};
}
- fn alloc(allocator: &Allocator, n: usize, alignment: u29) %[]u8 {
+ fn alloc(allocator: &Allocator, n: usize, alignment: u29) ![]u8 {
const self = @fieldParentPtr(FixedBufferAllocator, "allocator", allocator);
const addr = @ptrToInt(&self.buffer[self.end_index]);
const rem = @rem(addr, alignment);
@@ -138,7 +138,7 @@ pub const FixedBufferAllocator = struct {
return result;
}
- fn realloc(allocator: &Allocator, old_mem: []u8, new_size: usize, alignment: u29) %[]u8 {
+ fn realloc(allocator: &Allocator, old_mem: []u8, new_size: usize, alignment: u29) ![]u8 {
if (new_size <= old_mem.len) {
return old_mem[0..new_size];
} else {
@@ -197,7 +197,7 @@ pub fn eql(comptime T: type, a: []const T, b: []const T) bool {
}
/// Copies ::m to newly allocated memory. Caller is responsible to free it.
-pub fn dupe(allocator: &Allocator, comptime T: type, m: []const T) %[]T {
+pub fn dupe(allocator: &Allocator, comptime T: type, m: []const T) ![]T {
const new_buf = try allocator.alloc(T, m.len);
copy(T, new_buf, m);
return new_buf;
@@ -428,7 +428,7 @@ const SplitIterator = struct {
/// Naively combines a series of strings with a separator.
/// Allocates memory for the result, which must be freed by the caller.
-pub fn join(allocator: &Allocator, sep: u8, strings: ...) %[]u8 {
+pub fn join(allocator: &Allocator, sep: u8, strings: ...) ![]u8 {
comptime assert(strings.len >= 1);
var total_strings_len: usize = strings.len; // 1 sep per string
{
diff --git a/std/net.zig b/std/net.zig
index 4fbfd9b9aa..1140b6449b 100644
--- a/std/net.zig
+++ b/std/net.zig
@@ -5,19 +5,10 @@ const endian = std.endian;
// TODO don't trust this file, it bit rotted. start over
-error SigInterrupt;
-error Io;
-error TimedOut;
-error ConnectionReset;
-error ConnectionRefused;
-error OutOfMemory;
-error NotSocket;
-error BadFd;
-
const Connection = struct {
socket_fd: i32,
- pub fn send(c: Connection, buf: []const u8) %usize {
+ pub fn send(c: Connection, buf: []const u8) !usize {
const send_ret = linux.sendto(c.socket_fd, buf.ptr, buf.len, 0, null, 0);
const send_err = linux.getErrno(send_ret);
switch (send_err) {
@@ -31,7 +22,7 @@ const Connection = struct {
}
}
- pub fn recv(c: Connection, buf: []u8) %[]u8 {
+ pub fn recv(c: Connection, buf: []u8) ![]u8 {
const recv_ret = linux.recvfrom(c.socket_fd, buf.ptr, buf.len, 0, null, null);
const recv_err = linux.getErrno(recv_ret);
switch (recv_err) {
@@ -48,7 +39,7 @@ const Connection = struct {
}
}
- pub fn close(c: Connection) %void {
+ pub fn close(c: Connection) !void {
switch (linux.getErrno(linux.close(c.socket_fd))) {
0 => return,
linux.EBADF => unreachable,
@@ -66,7 +57,7 @@ const Address = struct {
sort_key: i32,
};
-pub fn lookup(hostname: []const u8, out_addrs: []Address) %[]Address {
+pub fn lookup(hostname: []const u8, out_addrs: []Address) ![]Address {
if (hostname.len == 0) {
unreachable; // TODO
@@ -75,7 +66,7 @@ pub fn lookup(hostname: []const u8, out_addrs: []Address) %[]Address {
unreachable; // TODO
}
-pub fn connectAddr(addr: &Address, port: u16) %Connection {
+pub fn connectAddr(addr: &Address, port: u16) !Connection {
const socket_ret = linux.socket(addr.family, linux.SOCK_STREAM, linux.PROTO_tcp);
const socket_err = linux.getErrno(socket_ret);
if (socket_err > 0) {
@@ -118,7 +109,7 @@ pub fn connectAddr(addr: &Address, port: u16) %Connection {
};
}
-pub fn connect(hostname: []const u8, port: u16) %Connection {
+pub fn connect(hostname: []const u8, port: u16) !Connection {
var addrs_buf: [1]Address = undefined;
const addrs_slice = try lookup(hostname, addrs_buf[0..]);
const main_addr = &addrs_slice[0];
@@ -126,9 +117,7 @@ pub fn connect(hostname: []const u8, port: u16) %Connection {
return connectAddr(main_addr, port);
}
-error InvalidIpLiteral;
-
-pub fn parseIpLiteral(buf: []const u8) %Address {
+pub fn parseIpLiteral(buf: []const u8) !Address {
return error.InvalidIpLiteral;
}
@@ -146,12 +135,7 @@ fn hexDigit(c: u8) u8 {
}
}
-error InvalidChar;
-error Overflow;
-error JunkAtEnd;
-error Incomplete;
-
-fn parseIp6(buf: []const u8) %Address {
+fn parseIp6(buf: []const u8) !Address {
var result: Address = undefined;
result.family = linux.AF_INET6;
result.scope_id = 0;
@@ -232,7 +216,7 @@ fn parseIp6(buf: []const u8) %Address {
return error.Incomplete;
}
-fn parseIp4(buf: []const u8) %u32 {
+fn parseIp4(buf: []const u8) !u32 {
var result: u32 = undefined;
const out_ptr = ([]u8)((&result)[0..1]);
diff --git a/std/os/child_process.zig b/std/os/child_process.zig
index f4709ce75a..c85202c9ed 100644
--- a/std/os/child_process.zig
+++ b/std/os/child_process.zig
@@ -13,10 +13,6 @@ const builtin = @import("builtin");
const Os = builtin.Os;
const LinkedList = std.LinkedList;
-error PermissionDenied;
-error ProcessNotFound;
-error InvalidName;
-
var children_nodes = LinkedList(&ChildProcess).init();
const is_windows = builtin.os == Os.windows;
@@ -28,11 +24,11 @@ pub const ChildProcess = struct {
pub allocator: &mem.Allocator,
- pub stdin: ?io.File,
- pub stdout: ?io.File,
- pub stderr: ?io.File,
+ pub stdin: ?os.File,
+ pub stdout: ?os.File,
+ pub stderr: ?os.File,
- pub term: ?%Term,
+ pub term: ?(SpawnError!Term),
pub argv: []const []const u8,
@@ -58,6 +54,25 @@ pub const ChildProcess = struct {
err_pipe: if (is_windows) void else [2]i32,
llnode: if (is_windows) void else LinkedList(&ChildProcess).Node,
+ pub const SpawnError = error {
+ ProcessFdQuotaExceeded,
+ Unexpected,
+ NotDir,
+ SystemResources,
+ FileNotFound,
+ NameTooLong,
+ SymLinkLoop,
+ FileSystem,
+ OutOfMemory,
+ AccessDenied,
+ PermissionDenied,
+ InvalidUserId,
+ ResourceLimitReached,
+ InvalidExe,
+ IsDir,
+ FileBusy,
+ };
+
pub const Term = union(enum) {
Exited: i32,
Signal: i32,
@@ -74,7 +89,7 @@ pub const ChildProcess = struct {
/// First argument in argv is the executable.
/// On success must call deinit.
- pub fn init(argv: []const []const u8, allocator: &mem.Allocator) %&ChildProcess {
+ pub fn init(argv: []const []const u8, allocator: &mem.Allocator) !&ChildProcess {
const child = try allocator.create(ChildProcess);
errdefer allocator.destroy(child);
@@ -103,7 +118,7 @@ pub const ChildProcess = struct {
return child;
}
- pub fn setUserName(self: &ChildProcess, name: []const u8) %void {
+ pub fn setUserName(self: &ChildProcess, name: []const u8) !void {
const user_info = try os.getUserInfo(name);
self.uid = user_info.uid;
self.gid = user_info.gid;
@@ -111,7 +126,7 @@ pub const ChildProcess = struct {
/// onTerm can be called before `spawn` returns.
/// On success must call `kill` or `wait`.
- pub fn spawn(self: &ChildProcess) %void {
+ pub fn spawn(self: &ChildProcess) !void {
if (is_windows) {
return self.spawnWindows();
} else {
@@ -119,13 +134,13 @@ pub const ChildProcess = struct {
}
}
- pub fn spawnAndWait(self: &ChildProcess) %Term {
+ pub fn spawnAndWait(self: &ChildProcess) !Term {
try self.spawn();
return self.wait();
}
/// Forcibly terminates child process and then cleans up all resources.
- pub fn kill(self: &ChildProcess) %Term {
+ pub fn kill(self: &ChildProcess) !Term {
if (is_windows) {
return self.killWindows(1);
} else {
@@ -133,7 +148,7 @@ pub const ChildProcess = struct {
}
}
- pub fn killWindows(self: &ChildProcess, exit_code: windows.UINT) %Term {
+ pub fn killWindows(self: &ChildProcess, exit_code: windows.UINT) !Term {
if (self.term) |term| {
self.cleanupStreams();
return term;
@@ -145,11 +160,11 @@ pub const ChildProcess = struct {
else => os.unexpectedErrorWindows(err),
};
}
- self.waitUnwrappedWindows();
+ try self.waitUnwrappedWindows();
return ??self.term;
}
- pub fn killPosix(self: &ChildProcess) %Term {
+ pub fn killPosix(self: &ChildProcess) !Term {
block_SIGCHLD();
defer restore_SIGCHLD();
@@ -172,7 +187,7 @@ pub const ChildProcess = struct {
}
/// Blocks until child process terminates and then cleans up all resources.
- pub fn wait(self: &ChildProcess) %Term {
+ pub fn wait(self: &ChildProcess) !Term {
if (is_windows) {
return self.waitWindows();
} else {
@@ -189,7 +204,7 @@ pub const ChildProcess = struct {
/// Spawns a child process, waits for it, collecting stdout and stderr, and then returns.
/// If it succeeds, the caller owns result.stdout and result.stderr memory.
pub fn exec(allocator: &mem.Allocator, argv: []const []const u8, cwd: ?[]const u8,
- env_map: ?&const BufMap, max_output_size: usize) %ExecResult
+ env_map: ?&const BufMap, max_output_size: usize) !ExecResult
{
const child = try ChildProcess.init(argv, allocator);
defer child.deinit();
@@ -220,7 +235,7 @@ pub const ChildProcess = struct {
};
}
- fn waitWindows(self: &ChildProcess) %Term {
+ fn waitWindows(self: &ChildProcess) !Term {
if (self.term) |term| {
self.cleanupStreams();
return term;
@@ -230,7 +245,7 @@ pub const ChildProcess = struct {
return ??self.term;
}
- fn waitPosix(self: &ChildProcess) %Term {
+ fn waitPosix(self: &ChildProcess) !Term {
block_SIGCHLD();
defer restore_SIGCHLD();
@@ -247,10 +262,10 @@ pub const ChildProcess = struct {
self.allocator.destroy(self);
}
- fn waitUnwrappedWindows(self: &ChildProcess) %void {
+ fn waitUnwrappedWindows(self: &ChildProcess) !void {
const result = os.windowsWaitSingle(self.handle, windows.INFINITE);
- self.term = (%Term)(x: {
+ self.term = (SpawnError!Term)(x: {
var exit_code: windows.DWORD = undefined;
if (windows.GetExitCodeProcess(self.handle, &exit_code) == 0) {
break :x Term { .Unknown = 0 };
@@ -295,7 +310,7 @@ pub const ChildProcess = struct {
if (self.stderr) |*stderr| { stderr.close(); self.stderr = null; }
}
- fn cleanupAfterWait(self: &ChildProcess, status: i32) %Term {
+ fn cleanupAfterWait(self: &ChildProcess, status: i32) !Term {
children_nodes.remove(&self.llnode);
defer {
@@ -313,7 +328,7 @@ pub const ChildProcess = struct {
// Here we potentially return the fork child's error
// from the parent pid.
if (err_int != @maxValue(ErrInt)) {
- return error(err_int);
+ return SpawnError(err_int);
}
return statusToTerm(status);
@@ -331,7 +346,7 @@ pub const ChildProcess = struct {
;
}
- fn spawnPosix(self: &ChildProcess) %void {
+ fn spawnPosix(self: &ChildProcess) !void {
// TODO atomically set a flag saying that we already did this
install_SIGCHLD_handler();
@@ -345,11 +360,14 @@ pub const ChildProcess = struct {
errdefer if (self.stderr_behavior == StdIo.Pipe) { destroyPipe(stderr_pipe); };
const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
- const dev_null_fd = if (any_ignore)
- try os.posixOpen("/dev/null", posix.O_RDWR, 0, null)
- else
- undefined
- ;
+ const dev_null_fd = if (any_ignore) blk: {
+ const dev_null_path = "/dev/null";
+ var fixed_buffer_mem: [dev_null_path.len + 1]u8 = undefined;
+ var fixed_allocator = mem.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
+ break :blk try os.posixOpen(&fixed_allocator.allocator, "/dev/null", posix.O_RDWR, 0);
+ } else blk: {
+ break :blk undefined;
+ };
defer { if (any_ignore) os.close(dev_null_fd); }
var env_map_owned: BufMap = undefined;
@@ -410,17 +428,17 @@ pub const ChildProcess = struct {
// we are the parent
const pid = i32(pid_result);
if (self.stdin_behavior == StdIo.Pipe) {
- self.stdin = io.File.openHandle(stdin_pipe[1]);
+ self.stdin = os.File.openHandle(stdin_pipe[1]);
} else {
self.stdin = null;
}
if (self.stdout_behavior == StdIo.Pipe) {
- self.stdout = io.File.openHandle(stdout_pipe[0]);
+ self.stdout = os.File.openHandle(stdout_pipe[0]);
} else {
self.stdout = null;
}
if (self.stderr_behavior == StdIo.Pipe) {
- self.stderr = io.File.openHandle(stderr_pipe[0]);
+ self.stderr = os.File.openHandle(stderr_pipe[0]);
} else {
self.stderr = null;
}
@@ -440,7 +458,7 @@ pub const ChildProcess = struct {
if (self.stderr_behavior == StdIo.Pipe) { os.close(stderr_pipe[1]); }
}
- fn spawnWindows(self: &ChildProcess) %void {
+ fn spawnWindows(self: &ChildProcess) !void {
const saAttr = windows.SECURITY_ATTRIBUTES {
.nLength = @sizeOf(windows.SECURITY_ATTRIBUTES),
.bInheritHandle = windows.TRUE,
@@ -451,12 +469,15 @@ pub const ChildProcess = struct {
self.stdout_behavior == StdIo.Ignore or
self.stderr_behavior == StdIo.Ignore);
- const nul_handle = if (any_ignore)
- try os.windowsOpen("NUL", windows.GENERIC_READ, windows.FILE_SHARE_READ,
- windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL, null)
- else
- undefined
- ;
+ const nul_handle = if (any_ignore) blk: {
+ const nul_file_path = "NUL";
+ var fixed_buffer_mem: [nul_file_path.len + 1]u8 = undefined;
+ var fixed_allocator = mem.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
+ break :blk try os.windowsOpen(&fixed_allocator.allocator, "NUL", windows.GENERIC_READ, windows.FILE_SHARE_READ,
+ windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL);
+ } else blk: {
+ break :blk undefined;
+ };
defer { if (any_ignore) os.close(nul_handle); }
if (any_ignore) {
try windowsSetHandleInfo(nul_handle, windows.HANDLE_FLAG_INHERIT, 0);
@@ -599,17 +620,17 @@ pub const ChildProcess = struct {
};
if (g_hChildStd_IN_Wr) |h| {
- self.stdin = io.File.openHandle(h);
+ self.stdin = os.File.openHandle(h);
} else {
self.stdin = null;
}
if (g_hChildStd_OUT_Rd) |h| {
- self.stdout = io.File.openHandle(h);
+ self.stdout = os.File.openHandle(h);
} else {
self.stdout = null;
}
if (g_hChildStd_ERR_Rd) |h| {
- self.stderr = io.File.openHandle(h);
+ self.stderr = os.File.openHandle(h);
} else {
self.stderr = null;
}
@@ -623,7 +644,7 @@ pub const ChildProcess = struct {
if (self.stdout_behavior == StdIo.Pipe) { os.close(??g_hChildStd_OUT_Wr); }
}
- fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) %void {
+ fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void {
switch (stdio) {
StdIo.Pipe => try os.posixDup2(pipe_fd, std_fileno),
StdIo.Close => os.close(std_fileno),
@@ -635,7 +656,7 @@ pub const ChildProcess = struct {
};
fn windowsCreateProcess(app_name: &u8, cmd_line: &u8, envp_ptr: ?&u8, cwd_ptr: ?&u8,
- lpStartupInfo: &windows.STARTUPINFOA, lpProcessInformation: &windows.PROCESS_INFORMATION) %void
+ lpStartupInfo: &windows.STARTUPINFOA, lpProcessInformation: &windows.PROCESS_INFORMATION) !void
{
if (windows.CreateProcessA(app_name, cmd_line, null, null, windows.TRUE, 0,
@ptrCast(?&c_void, envp_ptr), cwd_ptr, lpStartupInfo, lpProcessInformation) == 0)
@@ -655,7 +676,7 @@ fn windowsCreateProcess(app_name: &u8, cmd_line: &u8, envp_ptr: ?&u8, cwd_ptr: ?
/// Caller must dealloc.
/// Guarantees a null byte at result[result.len].
-fn windowsCreateCommandLine(allocator: &mem.Allocator, argv: []const []const u8) %[]u8 {
+fn windowsCreateCommandLine(allocator: &mem.Allocator, argv: []const []const u8) ![]u8 {
var buf = try Buffer.initSize(allocator, 0);
defer buf.deinit();
@@ -700,7 +721,7 @@ fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) void {
// a namespace field lookup
const SECURITY_ATTRIBUTES = windows.SECURITY_ATTRIBUTES;
-fn windowsMakePipe(rd: &windows.HANDLE, wr: &windows.HANDLE, sattr: &const SECURITY_ATTRIBUTES) %void {
+fn windowsMakePipe(rd: &windows.HANDLE, wr: &windows.HANDLE, sattr: &const SECURITY_ATTRIBUTES) !void {
if (windows.CreatePipe(rd, wr, sattr, 0) == 0) {
const err = windows.GetLastError();
return switch (err) {
@@ -709,7 +730,7 @@ fn windowsMakePipe(rd: &windows.HANDLE, wr: &windows.HANDLE, sattr: &const SECUR
}
}
-fn windowsSetHandleInfo(h: windows.HANDLE, mask: windows.DWORD, flags: windows.DWORD) %void {
+fn windowsSetHandleInfo(h: windows.HANDLE, mask: windows.DWORD, flags: windows.DWORD) !void {
if (windows.SetHandleInformation(h, mask, flags) == 0) {
const err = windows.GetLastError();
return switch (err) {
@@ -718,7 +739,7 @@ fn windowsSetHandleInfo(h: windows.HANDLE, mask: windows.DWORD, flags: windows.D
}
}
-fn windowsMakePipeIn(rd: &?windows.HANDLE, wr: &?windows.HANDLE, sattr: &const SECURITY_ATTRIBUTES) %void {
+fn windowsMakePipeIn(rd: &?windows.HANDLE, wr: &?windows.HANDLE, sattr: &const SECURITY_ATTRIBUTES) !void {
var rd_h: windows.HANDLE = undefined;
var wr_h: windows.HANDLE = undefined;
try windowsMakePipe(&rd_h, &wr_h, sattr);
@@ -728,7 +749,7 @@ fn windowsMakePipeIn(rd: &?windows.HANDLE, wr: &?windows.HANDLE, sattr: &const S
*wr = wr_h;
}
-fn windowsMakePipeOut(rd: &?windows.HANDLE, wr: &?windows.HANDLE, sattr: &const SECURITY_ATTRIBUTES) %void {
+fn windowsMakePipeOut(rd: &?windows.HANDLE, wr: &?windows.HANDLE, sattr: &const SECURITY_ATTRIBUTES) !void {
var rd_h: windows.HANDLE = undefined;
var wr_h: windows.HANDLE = undefined;
try windowsMakePipe(&rd_h, &wr_h, sattr);
@@ -738,7 +759,7 @@ fn windowsMakePipeOut(rd: &?windows.HANDLE, wr: &?windows.HANDLE, sattr: &const
*wr = wr_h;
}
-fn makePipe() %[2]i32 {
+fn makePipe() ![2]i32 {
var fds: [2]i32 = undefined;
const err = posix.getErrno(posix.pipe(&fds));
if (err > 0) {
@@ -757,20 +778,20 @@ fn destroyPipe(pipe: &const [2]i32) void {
// Child of fork calls this to report an error to the fork parent.
// Then the child exits.
-fn forkChildErrReport(fd: i32, err: error) noreturn {
+fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn {
_ = writeIntFd(fd, ErrInt(err));
posix.exit(1);
}
const ErrInt = @IntType(false, @sizeOf(error) * 8);
-fn writeIntFd(fd: i32, value: ErrInt) %void {
+fn writeIntFd(fd: i32, value: ErrInt) !void {
var bytes: [@sizeOf(ErrInt)]u8 = undefined;
mem.writeInt(bytes[0..], value, builtin.endian);
os.posixWrite(fd, bytes[0..]) catch return error.SystemResources;
}
-fn readIntFd(fd: i32) %ErrInt {
+fn readIntFd(fd: i32) !ErrInt {
var bytes: [@sizeOf(ErrInt)]u8 = undefined;
os.posixRead(fd, bytes[0..]) catch return error.SystemResources;
return mem.readInt(bytes[0..], ErrInt, builtin.endian);
diff --git a/std/os/file.zig b/std/os/file.zig
new file mode 100644
index 0000000000..772fbf7c73
--- /dev/null
+++ b/std/os/file.zig
@@ -0,0 +1,311 @@
+const std = @import("../index.zig");
+const builtin = @import("builtin");
+const os = std.os;
+const mem = std.mem;
+const math = std.math;
+const assert = std.debug.assert;
+const posix = os.posix;
+const windows = os.windows;
+const Os = builtin.Os;
+
+const is_posix = builtin.os != builtin.Os.windows;
+const is_windows = builtin.os == builtin.Os.windows;
+
+pub const File = struct {
+ /// The OS-specific file descriptor or file handle.
+ handle: os.FileHandle,
+
+ const OpenError = os.WindowsOpenError || os.PosixOpenError;
+
+ /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
+ /// Call close to clean up.
+ pub fn openRead(allocator: &mem.Allocator, path: []const u8) OpenError!File {
+ if (is_posix) {
+ const flags = posix.O_LARGEFILE|posix.O_RDONLY;
+ const fd = try os.posixOpen(allocator, path, flags, 0);
+ return openHandle(fd);
+ } else if (is_windows) {
+ const handle = try os.windowsOpen(allocator, path, windows.GENERIC_READ, windows.FILE_SHARE_READ,
+ windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL);
+ return openHandle(handle);
+ } else {
+ @compileError("TODO implement openRead for this OS");
+ }
+ }
+
+ /// Calls `openWriteMode` with os.default_file_mode for the mode.
+ pub fn openWrite(allocator: &mem.Allocator, path: []const u8) OpenError!File {
+ return openWriteMode(allocator, path, os.default_file_mode);
+
+ }
+
+ /// If the path does not exist it will be created.
+ /// If a file already exists in the destination it will be truncated.
+ /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
+ /// Call close to clean up.
+ pub fn openWriteMode(allocator: &mem.Allocator, path: []const u8, file_mode: os.FileMode) OpenError!File {
+ if (is_posix) {
+ const flags = posix.O_LARGEFILE|posix.O_WRONLY|posix.O_CREAT|posix.O_CLOEXEC|posix.O_TRUNC;
+ const fd = try os.posixOpen(allocator, path, flags, file_mode);
+ return openHandle(fd);
+ } else if (is_windows) {
+ const handle = try os.windowsOpen(allocator, path, windows.GENERIC_WRITE,
+ windows.FILE_SHARE_WRITE|windows.FILE_SHARE_READ|windows.FILE_SHARE_DELETE,
+ windows.CREATE_ALWAYS, windows.FILE_ATTRIBUTE_NORMAL);
+ return openHandle(handle);
+ } else {
+ @compileError("TODO implement openWriteMode for this OS");
+ }
+
+ }
+
+ /// If the path does not exist it will be created.
+ /// If a file already exists in the destination this returns OpenError.PathAlreadyExists
+ /// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
+ /// Call close to clean up.
+ pub fn openWriteNoClobber(allocator: &mem.Allocator, path: []const u8, file_mode: os.FileMode) OpenError!File {
+ if (is_posix) {
+ const flags = posix.O_LARGEFILE|posix.O_WRONLY|posix.O_CREAT|posix.O_CLOEXEC|posix.O_EXCL;
+ const fd = try os.posixOpen(allocator, path, flags, file_mode);
+ return openHandle(fd);
+ } else if (is_windows) {
+ const handle = try os.windowsOpen(allocator, path, windows.GENERIC_WRITE,
+ windows.FILE_SHARE_WRITE|windows.FILE_SHARE_READ|windows.FILE_SHARE_DELETE,
+ windows.CREATE_NEW, windows.FILE_ATTRIBUTE_NORMAL);
+ return openHandle(handle);
+ } else {
+ @compileError("TODO implement openWriteMode for this OS");
+ }
+
+ }
+
+ pub fn openHandle(handle: os.FileHandle) File {
+ return File {
+ .handle = handle,
+ };
+ }
+
+
+ /// Upon success, the stream is in an uninitialized state. To continue using it,
+ /// you must use the open() function.
+ pub fn close(self: &File) void {
+ os.close(self.handle);
+ self.handle = undefined;
+ }
+
+ /// Calls `os.isTty` on `self.handle`.
+ pub fn isTty(self: &File) bool {
+ return os.isTty(self.handle);
+ }
+
+ pub fn seekForward(self: &File, amount: isize) !void {
+ switch (builtin.os) {
+ Os.linux, Os.macosx, Os.ios => {
+ const result = posix.lseek(self.handle, amount, posix.SEEK_CUR);
+ const err = posix.getErrno(result);
+ if (err > 0) {
+ return switch (err) {
+ posix.EBADF => error.BadFd,
+ posix.EINVAL => error.Unseekable,
+ posix.EOVERFLOW => error.Unseekable,
+ posix.ESPIPE => error.Unseekable,
+ posix.ENXIO => error.Unseekable,
+ else => os.unexpectedErrorPosix(err),
+ };
+ }
+ },
+ Os.windows => {
+ if (windows.SetFilePointerEx(self.handle, amount, null, windows.FILE_CURRENT) == 0) {
+ const err = windows.GetLastError();
+ return switch (err) {
+ windows.ERROR.INVALID_PARAMETER => error.BadFd,
+ else => os.unexpectedErrorWindows(err),
+ };
+ }
+ },
+ else => @compileError("unsupported OS"),
+ }
+ }
+
+ pub fn seekTo(self: &File, pos: usize) !void {
+ switch (builtin.os) {
+ Os.linux, Os.macosx, Os.ios => {
+ const ipos = try math.cast(isize, pos);
+ const result = posix.lseek(self.handle, ipos, posix.SEEK_SET);
+ const err = posix.getErrno(result);
+ if (err > 0) {
+ return switch (err) {
+ posix.EBADF => error.BadFd,
+ posix.EINVAL => error.Unseekable,
+ posix.EOVERFLOW => error.Unseekable,
+ posix.ESPIPE => error.Unseekable,
+ posix.ENXIO => error.Unseekable,
+ else => os.unexpectedErrorPosix(err),
+ };
+ }
+ },
+ Os.windows => {
+ const ipos = try math.cast(isize, pos);
+ if (windows.SetFilePointerEx(self.handle, ipos, null, windows.FILE_BEGIN) == 0) {
+ const err = windows.GetLastError();
+ return switch (err) {
+ windows.ERROR.INVALID_PARAMETER => error.BadFd,
+ else => os.unexpectedErrorWindows(err),
+ };
+ }
+ },
+ else => @compileError("unsupported OS: " ++ @tagName(builtin.os)),
+ }
+ }
+
+ pub fn getPos(self: &File) !usize {
+ switch (builtin.os) {
+ Os.linux, Os.macosx, Os.ios => {
+ const result = posix.lseek(self.handle, 0, posix.SEEK_CUR);
+ const err = posix.getErrno(result);
+ if (err > 0) {
+ return switch (err) {
+ posix.EBADF => error.BadFd,
+ posix.EINVAL => error.Unseekable,
+ posix.EOVERFLOW => error.Unseekable,
+ posix.ESPIPE => error.Unseekable,
+ posix.ENXIO => error.Unseekable,
+ else => os.unexpectedErrorPosix(err),
+ };
+ }
+ return result;
+ },
+ Os.windows => {
+ var pos : windows.LARGE_INTEGER = undefined;
+ if (windows.SetFilePointerEx(self.handle, 0, &pos, windows.FILE_CURRENT) == 0) {
+ const err = windows.GetLastError();
+ return switch (err) {
+ windows.ERROR.INVALID_PARAMETER => error.BadFd,
+ else => os.unexpectedErrorWindows(err),
+ };
+ }
+
+ assert(pos >= 0);
+ if (@sizeOf(@typeOf(pos)) > @sizeOf(usize)) {
+ if (pos > @maxValue(usize)) {
+ return error.FilePosLargerThanPointerRange;
+ }
+ }
+
+ return usize(pos);
+ },
+ else => @compileError("unsupported OS"),
+ }
+ }
+
+ pub fn getEndPos(self: &File) !usize {
+ if (is_posix) {
+ var stat: posix.Stat = undefined;
+ const err = posix.getErrno(posix.fstat(self.handle, &stat));
+ if (err > 0) {
+ return switch (err) {
+ posix.EBADF => error.BadFd,
+ posix.ENOMEM => error.SystemResources,
+ else => os.unexpectedErrorPosix(err),
+ };
+ }
+
+ return usize(stat.size);
+ } else if (is_windows) {
+ var file_size: windows.LARGE_INTEGER = undefined;
+ if (windows.GetFileSizeEx(self.handle, &file_size) == 0) {
+ const err = windows.GetLastError();
+ return switch (err) {
+ else => os.unexpectedErrorWindows(err),
+ };
+ }
+ if (file_size < 0)
+ return error.Overflow;
+ return math.cast(usize, u64(file_size));
+ } else {
+ @compileError("TODO support getEndPos on this OS");
+ }
+ }
+
+ pub const ModeError = error {
+ BadFd,
+ SystemResources,
+ Unexpected,
+ };
+
+ fn mode(self: &File) ModeError!FileMode {
+ if (is_posix) {
+ var stat: posix.Stat = undefined;
+ const err = posix.getErrno(posix.fstat(self.handle, &stat));
+ if (err > 0) {
+ return switch (err) {
+ posix.EBADF => error.BadFd,
+ posix.ENOMEM => error.SystemResources,
+ else => os.unexpectedErrorPosix(err),
+ };
+ }
+
+ return stat.mode;
+ } else if (is_windows) {
+ return {};
+ } else {
+ @compileError("TODO support file mode on this OS");
+ }
+ }
+
+ pub const ReadError = error {};
+
+ pub fn read(self: &File, buffer: []u8) !usize {
+ if (is_posix) {
+ var index: usize = 0;
+ while (index < buffer.len) {
+ const amt_read = posix.read(self.handle, &buffer[index], buffer.len - index);
+ const read_err = posix.getErrno(amt_read);
+ if (read_err > 0) {
+ switch (read_err) {
+ posix.EINTR => continue,
+ posix.EINVAL => unreachable,
+ posix.EFAULT => unreachable,
+ posix.EBADF => return error.BadFd,
+ posix.EIO => return error.Io,
+ else => return os.unexpectedErrorPosix(read_err),
+ }
+ }
+ if (amt_read == 0) return index;
+ index += amt_read;
+ }
+ return index;
+ } else if (is_windows) {
+ var index: usize = 0;
+ while (index < buffer.len) {
+ const want_read_count = windows.DWORD(math.min(windows.DWORD(@maxValue(windows.DWORD)), buffer.len - index));
+ var amt_read: windows.DWORD = undefined;
+ if (windows.ReadFile(self.handle, @ptrCast(&c_void, &buffer[index]), want_read_count, &amt_read, null) == 0) {
+ const err = windows.GetLastError();
+ return switch (err) {
+ windows.ERROR.OPERATION_ABORTED => continue,
+ windows.ERROR.BROKEN_PIPE => return index,
+ else => os.unexpectedErrorWindows(err),
+ };
+ }
+ if (amt_read == 0) return index;
+ index += amt_read;
+ }
+ return index;
+ } else {
+ unreachable;
+ }
+ }
+
+ pub const WriteError = os.WindowsWriteError || os.PosixWriteError;
+
+ fn write(self: &File, bytes: []const u8) WriteError!void {
+ if (is_posix) {
+ try os.posixWrite(self.handle, bytes);
+ } else if (is_windows) {
+ try os.windowsWrite(self.handle, bytes);
+ } else {
+ @compileError("Unsupported OS");
+ }
+ }
+};
diff --git a/std/os/get_user_id.zig b/std/os/get_user_id.zig
index 68cb268169..11410ffa64 100644
--- a/std/os/get_user_id.zig
+++ b/std/os/get_user_id.zig
@@ -9,7 +9,7 @@ pub const UserInfo = struct {
};
/// POSIX function which gets a uid from username.
-pub fn getUserInfo(name: []const u8) %UserInfo {
+pub fn getUserInfo(name: []const u8) !UserInfo {
return switch (builtin.os) {
Os.linux, Os.macosx, Os.ios => posixGetUserInfo(name),
else => @compileError("Unsupported OS"),
@@ -24,13 +24,10 @@ const State = enum {
ReadGroupId,
};
-error UserNotFound;
-error CorruptPasswordFile;
-
// TODO this reads /etc/passwd. But sometimes the user/id mapping is in something else
// like NIS, AD, etc. See `man nss` or look at an strace for `id myuser`.
-pub fn posixGetUserInfo(name: []const u8) %UserInfo {
+pub fn posixGetUserInfo(name: []const u8) !UserInfo {
var in_stream = try io.InStream.open("/etc/passwd", null);
defer in_stream.close();
diff --git a/std/os/index.zig b/std/os/index.zig
index a543f27be4..2131e72760 100644
--- a/std/os/index.zig
+++ b/std/os/index.zig
@@ -15,13 +15,18 @@ pub const posix = switch(builtin.os) {
else => @compileError("Unsupported OS"),
};
-pub const max_noalloc_path_len = 1024;
pub const ChildProcess = @import("child_process.zig").ChildProcess;
pub const path = @import("path.zig");
+pub const File = @import("file.zig").File;
-pub const line_sep = switch (builtin.os) {
- Os.windows => "\r\n",
- else => "\n",
+pub const FileMode = switch (builtin.os) {
+ Os.windows => void,
+ else => u32,
+};
+
+pub const default_file_mode = switch (builtin.os) {
+ Os.windows => {},
+ else => 0o666,
};
pub const page_size = 4 * 1024;
@@ -38,6 +43,10 @@ pub const windowsLoadDll = windows_util.windowsLoadDll;
pub const windowsUnloadDll = windows_util.windowsUnloadDll;
pub const createWindowsEnvBlock = windows_util.createWindowsEnvBlock;
+pub const WindowsWaitError = windows_util.WaitError;
+pub const WindowsOpenError = windows_util.OpenError;
+pub const WindowsWriteError = windows_util.WriteError;
+
pub const FileHandle = if (is_windows) windows.HANDLE else i32;
const debug = std.debug;
@@ -57,25 +66,10 @@ const ArrayList = std.ArrayList;
const Buffer = std.Buffer;
const math = std.math;
-error SystemResources;
-error AccessDenied;
-error InvalidExe;
-error FileSystem;
-error IsDir;
-error FileNotFound;
-error FileBusy;
-error PathAlreadyExists;
-error SymLinkLoop;
-error ReadOnlyFileSystem;
-error LinkQuotaExceeded;
-error RenameAcrossMountPoints;
-error DirNotEmpty;
-error WouldBlock;
-
/// Fills `buf` with random bytes. If linking against libc, this calls the
/// appropriate OS-specific library call. Otherwise it uses the zig standard
/// library implementation.
-pub fn getRandomBytes(buf: []u8) %void {
+pub fn getRandomBytes(buf: []u8) !void {
switch (builtin.os) {
Os.linux => while (true) {
// TODO check libc version and potentially call c.getrandom.
@@ -188,7 +182,7 @@ pub fn close(handle: FileHandle) void {
}
/// Calls POSIX read, and keeps trying if it gets interrupted.
-pub fn posixRead(fd: i32, buf: []u8) %void {
+pub fn posixRead(fd: i32, buf: []u8) !void {
// Linux can return EINVAL when read amount is > 0x7ffff000
// See https://github.com/zig-lang/zig/pull/743#issuecomment-363158274
const max_buf_len = 0x7ffff000;
@@ -214,17 +208,21 @@ pub fn posixRead(fd: i32, buf: []u8) %void {
}
}
-error WouldBlock;
-error FileClosed;
-error DestinationAddressRequired;
-error DiskQuota;
-error FileTooBig;
-error InputOutput;
-error NoSpaceLeft;
-error BrokenPipe;
+pub const PosixWriteError = error {
+ WouldBlock,
+ FileClosed,
+ DestinationAddressRequired,
+ DiskQuota,
+ FileTooBig,
+ InputOutput,
+ NoSpaceLeft,
+ AccessDenied,
+ BrokenPipe,
+ Unexpected,
+};
/// Calls POSIX write, and keeps trying if it gets interrupted.
-pub fn posixWrite(fd: i32, bytes: []const u8) %void {
+pub fn posixWrite(fd: i32, bytes: []const u8) !void {
// Linux can return EINVAL when write amount is > 0x7ffff000
// See https://github.com/zig-lang/zig/pull/743#issuecomment-363165856
const max_bytes_len = 0x7ffff000;
@@ -238,15 +236,15 @@ pub fn posixWrite(fd: i32, bytes: []const u8) %void {
return switch (write_err) {
posix.EINTR => continue,
posix.EINVAL, posix.EFAULT => unreachable,
- posix.EAGAIN => error.WouldBlock,
- posix.EBADF => error.FileClosed,
- posix.EDESTADDRREQ => error.DestinationAddressRequired,
- posix.EDQUOT => error.DiskQuota,
- posix.EFBIG => error.FileTooBig,
- posix.EIO => error.InputOutput,
- posix.ENOSPC => error.NoSpaceLeft,
- posix.EPERM => error.AccessDenied,
- posix.EPIPE => error.BrokenPipe,
+ posix.EAGAIN => PosixWriteError.WouldBlock,
+ posix.EBADF => PosixWriteError.FileClosed,
+ posix.EDESTADDRREQ => PosixWriteError.DestinationAddressRequired,
+ posix.EDQUOT => PosixWriteError.DiskQuota,
+ posix.EFBIG => PosixWriteError.FileTooBig,
+ posix.EIO => PosixWriteError.InputOutput,
+ posix.ENOSPC => PosixWriteError.NoSpaceLeft,
+ posix.EPERM => PosixWriteError.AccessDenied,
+ posix.EPIPE => PosixWriteError.BrokenPipe,
else => unexpectedErrorPosix(write_err),
};
}
@@ -254,66 +252,66 @@ pub fn posixWrite(fd: i32, bytes: []const u8) %void {
}
}
-/// ::file_path may need to be copied in memory to add a null terminating byte. In this case
-/// a fixed size buffer of size ::max_noalloc_path_len is an attempted solution. If the fixed
-/// size buffer is too small, and the provided allocator is null, ::error.NameTooLong is returned.
-/// otherwise if the fixed size buffer is too small, allocator is used to obtain the needed memory.
+pub const PosixOpenError = error {
+ OutOfMemory,
+ AccessDenied,
+ FileTooBig,
+ IsDir,
+ SymLinkLoop,
+ ProcessFdQuotaExceeded,
+ NameTooLong,
+ SystemFdQuotaExceeded,
+ NoDevice,
+ PathNotFound,
+ SystemResources,
+ NoSpaceLeft,
+ NotDir,
+ PathAlreadyExists,
+ Unexpected,
+};
+
+/// ::file_path needs to be copied in memory to add a null terminating byte.
/// Calls POSIX open, keeps trying if it gets interrupted, and translates
/// the return value into zig errors.
-pub fn posixOpen(file_path: []const u8, flags: u32, perm: usize, allocator: ?&Allocator) %i32 {
- var stack_buf: [max_noalloc_path_len]u8 = undefined;
- var path0: []u8 = undefined;
- var need_free = false;
-
- if (file_path.len < stack_buf.len) {
- path0 = stack_buf[0..file_path.len + 1];
- } else if (allocator) |a| {
- path0 = try a.alloc(u8, file_path.len + 1);
- need_free = true;
- } else {
- return error.NameTooLong;
- }
- defer if (need_free) {
- (??allocator).free(path0);
- };
- mem.copy(u8, path0, file_path);
- path0[file_path.len] = 0;
+pub fn posixOpen(allocator: &Allocator, file_path: []const u8, flags: u32, perm: usize) PosixOpenError!i32 {
+ const path_with_null = try cstr.addNullByte(allocator, file_path);
+ defer allocator.free(path_with_null);
- return posixOpenC(path0.ptr, flags, perm);
+ return posixOpenC(path_with_null.ptr, flags, perm);
}
-pub fn posixOpenC(file_path: &const u8, flags: u32, perm: usize) %i32 {
+pub fn posixOpenC(file_path: &const u8, flags: u32, perm: usize) !i32 {
while (true) {
const result = posix.open(file_path, flags, perm);
const err = posix.getErrno(result);
if (err > 0) {
- return switch (err) {
+ switch (err) {
posix.EINTR => continue,
posix.EFAULT => unreachable,
posix.EINVAL => unreachable,
- posix.EACCES => error.AccessDenied,
- posix.EFBIG, posix.EOVERFLOW => error.FileTooBig,
- posix.EISDIR => error.IsDir,
- posix.ELOOP => error.SymLinkLoop,
- posix.EMFILE => error.ProcessFdQuotaExceeded,
- posix.ENAMETOOLONG => error.NameTooLong,
- posix.ENFILE => error.SystemFdQuotaExceeded,
- posix.ENODEV => error.NoDevice,
- posix.ENOENT => error.PathNotFound,
- posix.ENOMEM => error.SystemResources,
- posix.ENOSPC => error.NoSpaceLeft,
- posix.ENOTDIR => error.NotDir,
- posix.EPERM => error.AccessDenied,
- posix.EEXIST => error.PathAlreadyExists,
- else => unexpectedErrorPosix(err),
- };
+ posix.EACCES => return PosixOpenError.AccessDenied,
+ posix.EFBIG, posix.EOVERFLOW => return PosixOpenError.FileTooBig,
+ posix.EISDIR => return PosixOpenError.IsDir,
+ posix.ELOOP => return PosixOpenError.SymLinkLoop,
+ posix.EMFILE => return PosixOpenError.ProcessFdQuotaExceeded,
+ posix.ENAMETOOLONG => return PosixOpenError.NameTooLong,
+ posix.ENFILE => return PosixOpenError.SystemFdQuotaExceeded,
+ posix.ENODEV => return PosixOpenError.NoDevice,
+ posix.ENOENT => return PosixOpenError.PathNotFound,
+ posix.ENOMEM => return PosixOpenError.SystemResources,
+ posix.ENOSPC => return PosixOpenError.NoSpaceLeft,
+ posix.ENOTDIR => return PosixOpenError.NotDir,
+ posix.EPERM => return PosixOpenError.AccessDenied,
+ posix.EEXIST => return PosixOpenError.PathAlreadyExists,
+ else => return unexpectedErrorPosix(err),
+ }
}
return i32(result);
}
}
-pub fn posixDup2(old_fd: i32, new_fd: i32) %void {
+pub fn posixDup2(old_fd: i32, new_fd: i32) !void {
while (true) {
const err = posix.getErrno(posix.dup2(old_fd, new_fd));
if (err > 0) {
@@ -328,7 +326,7 @@ pub fn posixDup2(old_fd: i32, new_fd: i32) %void {
}
}
-pub fn createNullDelimitedEnvMap(allocator: &Allocator, env_map: &const BufMap) %[]?&u8 {
+pub fn createNullDelimitedEnvMap(allocator: &Allocator, env_map: &const BufMap) ![]?&u8 {
const envp_count = env_map.count();
const envp_buf = try allocator.alloc(?&u8, envp_count + 1);
mem.set(?&u8, envp_buf, null);
@@ -365,7 +363,7 @@ pub fn freeNullDelimitedEnvMap(allocator: &Allocator, envp_buf: []?&u8) void {
/// `argv[0]` is the executable path.
/// This function also uses the PATH environment variable to get the full path to the executable.
pub fn posixExecve(argv: []const []const u8, env_map: &const BufMap,
- allocator: &Allocator) %void
+ allocator: &Allocator) !void
{
const argv_buf = try allocator.alloc(?&u8, argv.len + 1);
mem.set(?&u8, argv_buf, null);
@@ -421,7 +419,19 @@ pub fn posixExecve(argv: []const []const u8, env_map: &const BufMap,
return posixExecveErrnoToErr(err);
}
-fn posixExecveErrnoToErr(err: usize) error {
+pub const PosixExecveError = error {
+ SystemResources,
+ AccessDenied,
+ InvalidExe,
+ FileSystem,
+ IsDir,
+ FileNotFound,
+ NotDir,
+ FileBusy,
+ Unexpected,
+};
+
+fn posixExecveErrnoToErr(err: usize) PosixExecveError {
assert(err > 0);
return switch (err) {
posix.EFAULT => unreachable,
@@ -440,7 +450,7 @@ fn posixExecveErrnoToErr(err: usize) error {
pub var posix_environ_raw: []&u8 = undefined;
/// Caller must free result when done.
-pub fn getEnvMap(allocator: &Allocator) %BufMap {
+pub fn getEnvMap(allocator: &Allocator) !BufMap {
var result = BufMap.init(allocator);
errdefer result.deinit();
@@ -501,10 +511,8 @@ pub fn getEnvPosix(key: []const u8) ?[]const u8 {
return null;
}
-error EnvironmentVariableNotFound;
-
/// Caller must free returned memory.
-pub fn getEnvVarOwned(allocator: &mem.Allocator, key: []const u8) %[]u8 {
+pub fn getEnvVarOwned(allocator: &mem.Allocator, key: []const u8) ![]u8 {
if (is_windows) {
const key_with_null = try cstr.addNullByte(allocator, key);
defer allocator.free(key_with_null);
@@ -538,7 +546,7 @@ pub fn getEnvVarOwned(allocator: &mem.Allocator, key: []const u8) %[]u8 {
}
/// Caller must free the returned memory.
-pub fn getCwd(allocator: &Allocator) %[]u8 {
+pub fn getCwd(allocator: &Allocator) ![]u8 {
switch (builtin.os) {
Os.windows => {
var buf = try allocator.alloc(u8, 256);
@@ -585,7 +593,9 @@ test "os.getCwd" {
_ = getCwd(debug.global_allocator);
}
-pub fn symLink(allocator: &Allocator, existing_path: []const u8, new_path: []const u8) %void {
+pub const SymLinkError = PosixSymLinkError || WindowsSymLinkError;
+
+pub fn symLink(allocator: &Allocator, existing_path: []const u8, new_path: []const u8) SymLinkError!void {
if (is_windows) {
return symLinkWindows(allocator, existing_path, new_path);
} else {
@@ -593,7 +603,12 @@ pub fn symLink(allocator: &Allocator, existing_path: []const u8, new_path: []con
}
}
-pub fn symLinkWindows(allocator: &Allocator, existing_path: []const u8, new_path: []const u8) %void {
+pub const WindowsSymLinkError = error {
+ OutOfMemory,
+ Unexpected,
+};
+
+pub fn symLinkWindows(allocator: &Allocator, existing_path: []const u8, new_path: []const u8) WindowsSymLinkError!void {
const existing_with_null = try cstr.addNullByte(allocator, existing_path);
defer allocator.free(existing_with_null);
const new_with_null = try cstr.addNullByte(allocator, new_path);
@@ -607,7 +622,23 @@ pub fn symLinkWindows(allocator: &Allocator, existing_path: []const u8, new_path
}
}
-pub fn symLinkPosix(allocator: &Allocator, existing_path: []const u8, new_path: []const u8) %void {
+pub const PosixSymLinkError = error {
+ OutOfMemory,
+ AccessDenied,
+ DiskQuota,
+ PathAlreadyExists,
+ FileSystem,
+ SymLinkLoop,
+ NameTooLong,
+ FileNotFound,
+ SystemResources,
+ NoSpaceLeft,
+ ReadOnlyFileSystem,
+ NotDir,
+ Unexpected,
+};
+
+pub fn symLinkPosix(allocator: &Allocator, existing_path: []const u8, new_path: []const u8) PosixSymLinkError!void {
const full_buf = try allocator.alloc(u8, existing_path.len + new_path.len + 2);
defer allocator.free(full_buf);
@@ -644,36 +675,36 @@ const b64_fs_encoder = base64.Base64Encoder.init(
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",
base64.standard_pad_char);
-pub fn atomicSymLink(allocator: &Allocator, existing_path: []const u8, new_path: []const u8) %void {
+pub fn atomicSymLink(allocator: &Allocator, existing_path: []const u8, new_path: []const u8) !void {
if (symLink(allocator, existing_path, new_path)) {
return;
- } else |err| {
- if (err != error.PathAlreadyExists) {
- return err;
- }
+ } else |err| switch (err) {
+ error.PathAlreadyExists => {},
+ else => return err, // TODO zig should know this set does not include PathAlreadyExists
}
+ const dirname = os.path.dirname(new_path);
+
var rand_buf: [12]u8 = undefined;
- const tmp_path = try allocator.alloc(u8, new_path.len + base64.Base64Encoder.calcSize(rand_buf.len));
+ const tmp_path = try allocator.alloc(u8, dirname.len + 1 + base64.Base64Encoder.calcSize(rand_buf.len));
defer allocator.free(tmp_path);
- mem.copy(u8, tmp_path[0..], new_path);
+ mem.copy(u8, tmp_path[0..], dirname);
+ tmp_path[dirname.len] = os.path.sep;
while (true) {
try getRandomBytes(rand_buf[0..]);
- b64_fs_encoder.encode(tmp_path[new_path.len..], rand_buf);
+ b64_fs_encoder.encode(tmp_path[dirname.len + 1 ..], rand_buf);
+
if (symLink(allocator, existing_path, tmp_path)) {
return rename(allocator, tmp_path, new_path);
- } else |err| {
- if (err == error.PathAlreadyExists) {
- continue;
- } else {
- return err;
- }
+ } else |err| switch (err) {
+ error.PathAlreadyExists => continue,
+ else => return err, // TODO zig should know this set does not include PathAlreadyExists
}
}
}
-pub fn deleteFile(allocator: &Allocator, file_path: []const u8) %void {
+pub fn deleteFile(allocator: &Allocator, file_path: []const u8) !void {
if (builtin.os == Os.windows) {
return deleteFileWindows(allocator, file_path);
} else {
@@ -681,10 +712,7 @@ pub fn deleteFile(allocator: &Allocator, file_path: []const u8) %void {
}
}
-error FileNotFound;
-error AccessDenied;
-
-pub fn deleteFileWindows(allocator: &Allocator, file_path: []const u8) %void {
+pub fn deleteFileWindows(allocator: &Allocator, file_path: []const u8) !void {
const buf = try allocator.alloc(u8, file_path.len + 1);
defer allocator.free(buf);
@@ -702,7 +730,7 @@ pub fn deleteFileWindows(allocator: &Allocator, file_path: []const u8) %void {
}
}
-pub fn deleteFilePosix(allocator: &Allocator, file_path: []const u8) %void {
+pub fn deleteFilePosix(allocator: &Allocator, file_path: []const u8) !void {
const buf = try allocator.alloc(u8, file_path.len + 1);
defer allocator.free(buf);
@@ -728,38 +756,109 @@ pub fn deleteFilePosix(allocator: &Allocator, file_path: []const u8) %void {
}
}
-/// Calls ::copyFileMode with 0o666 for the mode.
-pub fn copyFile(allocator: &Allocator, source_path: []const u8, dest_path: []const u8) %void {
- return copyFileMode(allocator, source_path, dest_path, 0o666);
-}
+/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is
+/// merged and readily available,
+/// there is a possibility of power loss or application termination leaving temporary files present
+/// in the same directory as dest_path.
+/// Destination file will have the same mode as the source file.
+pub fn copyFile(allocator: &Allocator, source_path: []const u8, dest_path: []const u8) !void {
+ var in_file = try os.File.openRead(allocator, source_path);
+ defer in_file.close();
-// TODO instead of accepting a mode argument, use the mode from fstat'ing the source path once open
-/// Guaranteed to be atomic.
-pub fn copyFileMode(allocator: &Allocator, source_path: []const u8, dest_path: []const u8, mode: usize) %void {
- var rand_buf: [12]u8 = undefined;
- const tmp_path = try allocator.alloc(u8, dest_path.len + base64.Base64Encoder.calcSize(rand_buf.len));
- defer allocator.free(tmp_path);
- mem.copy(u8, tmp_path[0..], dest_path);
- try getRandomBytes(rand_buf[0..]);
- b64_fs_encoder.encode(tmp_path[dest_path.len..], rand_buf);
+ const mode = try in_file.mode();
- var out_file = try io.File.openWriteMode(tmp_path, mode, allocator);
- defer out_file.close();
- errdefer _ = deleteFile(allocator, tmp_path);
+ var atomic_file = try AtomicFile.init(allocator, dest_path, mode);
+ defer atomic_file.deinit();
- var in_file = try io.File.openRead(source_path, allocator);
+ var buf: [page_size]u8 = undefined;
+ while (true) {
+ const amt = try in_file.read(buf[0..]);
+ try atomic_file.file.write(buf[0..amt]);
+ if (amt != buf.len) {
+ return atomic_file.finish();
+ }
+ }
+}
+
+/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is
+/// merged and readily available,
+/// there is a possibility of power loss or application termination leaving temporary files present
+pub fn copyFileMode(allocator: &Allocator, source_path: []const u8, dest_path: []const u8, mode: FileMode) !void {
+ var in_file = try os.File.openRead(allocator, source_path);
defer in_file.close();
+ var atomic_file = try AtomicFile.init(allocator, dest_path, mode);
+ defer atomic_file.deinit();
+
var buf: [page_size]u8 = undefined;
while (true) {
const amt = try in_file.read(buf[0..]);
- try out_file.write(buf[0..amt]);
- if (amt != buf.len)
- return rename(allocator, tmp_path, dest_path);
+ try atomic_file.file.write(buf[0..amt]);
+ if (amt != buf.len) {
+ return atomic_file.finish();
+ }
}
}
-pub fn rename(allocator: &Allocator, old_path: []const u8, new_path: []const u8) %void {
+pub const AtomicFile = struct {
+ allocator: &Allocator,
+ file: os.File,
+ tmp_path: []u8,
+ dest_path: []const u8,
+ finished: bool,
+
+ /// dest_path must remain valid for the lifetime of AtomicFile
+ /// call finish to atomically replace dest_path with contents
+ pub fn init(allocator: &Allocator, dest_path: []const u8, mode: FileMode) !AtomicFile {
+ const dirname = os.path.dirname(dest_path);
+
+ var rand_buf: [12]u8 = undefined;
+ const tmp_path = try allocator.alloc(u8, dirname.len + 1 + base64.Base64Encoder.calcSize(rand_buf.len));
+ errdefer allocator.free(tmp_path);
+ mem.copy(u8, tmp_path[0..], dirname);
+ tmp_path[dirname.len] = os.path.sep;
+
+ while (true) {
+ try getRandomBytes(rand_buf[0..]);
+ b64_fs_encoder.encode(tmp_path[dirname.len + 1 ..], rand_buf);
+
+ const file = os.File.openWriteNoClobber(allocator, tmp_path, mode) catch |err| switch (err) {
+ error.PathAlreadyExists => continue,
+ // TODO zig should figure out that this error set does not include PathAlreadyExists since
+ // it is handled in the above switch
+ else => return err,
+ };
+
+ return AtomicFile {
+ .allocator = allocator,
+ .file = file,
+ .tmp_path = tmp_path,
+ .dest_path = dest_path,
+ .finished = false,
+ };
+ }
+ }
+
+ /// always call deinit, even after successful finish()
+ pub fn deinit(self: &AtomicFile) void {
+ if (!self.finished) {
+ self.file.close();
+ deleteFile(self.allocator, self.tmp_path) catch {};
+ self.allocator.free(self.tmp_path);
+ self.finished = true;
+ }
+ }
+
+ pub fn finish(self: &AtomicFile) !void {
+ assert(!self.finished);
+ self.file.close();
+ try rename(self.allocator, self.tmp_path, self.dest_path);
+ self.allocator.free(self.tmp_path);
+ self.finished = true;
+ }
+};
+
+pub fn rename(allocator: &Allocator, old_path: []const u8, new_path: []const u8) !void {
const full_buf = try allocator.alloc(u8, old_path.len + new_path.len + 2);
defer allocator.free(full_buf);
@@ -804,7 +903,7 @@ pub fn rename(allocator: &Allocator, old_path: []const u8, new_path: []const u8)
}
}
-pub fn makeDir(allocator: &Allocator, dir_path: []const u8) %void {
+pub fn makeDir(allocator: &Allocator, dir_path: []const u8) !void {
if (is_windows) {
return makeDirWindows(allocator, dir_path);
} else {
@@ -812,7 +911,7 @@ pub fn makeDir(allocator: &Allocator, dir_path: []const u8) %void {
}
}
-pub fn makeDirWindows(allocator: &Allocator, dir_path: []const u8) %void {
+pub fn makeDirWindows(allocator: &Allocator, dir_path: []const u8) !void {
const path_buf = try cstr.addNullByte(allocator, dir_path);
defer allocator.free(path_buf);
@@ -826,7 +925,7 @@ pub fn makeDirWindows(allocator: &Allocator, dir_path: []const u8) %void {
}
}
-pub fn makeDirPosix(allocator: &Allocator, dir_path: []const u8) %void {
+pub fn makeDirPosix(allocator: &Allocator, dir_path: []const u8) !void {
const path_buf = try cstr.addNullByte(allocator, dir_path);
defer allocator.free(path_buf);
@@ -852,7 +951,7 @@ pub fn makeDirPosix(allocator: &Allocator, dir_path: []const u8) %void {
/// Calls makeDir recursively to make an entire path. Returns success if the path
/// already exists and is a directory.
-pub fn makePath(allocator: &Allocator, full_path: []const u8) %void {
+pub fn makePath(allocator: &Allocator, full_path: []const u8) !void {
const resolved_path = try path.resolve(allocator, full_path);
defer allocator.free(resolved_path);
@@ -890,7 +989,7 @@ pub fn makePath(allocator: &Allocator, full_path: []const u8) %void {
/// Returns ::error.DirNotEmpty if the directory is not empty.
/// To delete a directory recursively, see ::deleteTree
-pub fn deleteDir(allocator: &Allocator, dir_path: []const u8) %void {
+pub fn deleteDir(allocator: &Allocator, dir_path: []const u8) !void {
const path_buf = try allocator.alloc(u8, dir_path.len + 1);
defer allocator.free(path_buf);
@@ -919,24 +1018,68 @@ pub fn deleteDir(allocator: &Allocator, dir_path: []const u8) %void {
/// removes it. If it cannot be removed because it is a non-empty directory,
/// this function recursively removes its entries and then tries again.
// TODO non-recursive implementation
-pub fn deleteTree(allocator: &Allocator, full_path: []const u8) %void {
+const DeleteTreeError = error {
+ OutOfMemory,
+ AccessDenied,
+ FileTooBig,
+ IsDir,
+ SymLinkLoop,
+ ProcessFdQuotaExceeded,
+ NameTooLong,
+ SystemFdQuotaExceeded,
+ NoDevice,
+ PathNotFound,
+ SystemResources,
+ NoSpaceLeft,
+ PathAlreadyExists,
+ ReadOnlyFileSystem,
+ NotDir,
+ FileNotFound,
+ FileSystem,
+ FileBusy,
+ DirNotEmpty,
+ Unexpected,
+};
+pub fn deleteTree(allocator: &Allocator, full_path: []const u8) DeleteTreeError!void {
start_over: while (true) {
// First, try deleting the item as a file. This way we don't follow sym links.
if (deleteFile(allocator, full_path)) {
return;
- } else |err| {
- if (err == error.FileNotFound)
- return;
- if (err != error.IsDir)
- return err;
+ } else |err| switch (err) {
+ error.FileNotFound => return,
+ error.IsDir => {},
+
+ error.OutOfMemory,
+ error.AccessDenied,
+ error.SymLinkLoop,
+ error.NameTooLong,
+ error.SystemResources,
+ error.ReadOnlyFileSystem,
+ error.NotDir,
+ error.FileSystem,
+ error.FileBusy,
+ error.Unexpected
+ => return err,
}
{
- var dir = Dir.open(allocator, full_path) catch |err| {
- if (err == error.FileNotFound)
- return;
- if (err == error.NotDir)
- continue :start_over;
- return err;
+ var dir = Dir.open(allocator, full_path) catch |err| switch (err) {
+ error.NotDir => continue :start_over,
+
+ error.OutOfMemory,
+ error.AccessDenied,
+ error.FileTooBig,
+ error.IsDir,
+ error.SymLinkLoop,
+ error.ProcessFdQuotaExceeded,
+ error.NameTooLong,
+ error.SystemFdQuotaExceeded,
+ error.NoDevice,
+ error.PathNotFound,
+ error.SystemResources,
+ error.NoSpaceLeft,
+ error.PathAlreadyExists,
+ error.Unexpected
+ => return err,
};
defer dir.close();
@@ -988,8 +1131,8 @@ pub const Dir = struct {
};
};
- pub fn open(allocator: &Allocator, dir_path: []const u8) %Dir {
- const fd = try posixOpen(dir_path, posix.O_RDONLY|posix.O_DIRECTORY|posix.O_CLOEXEC, 0, allocator);
+ pub fn open(allocator: &Allocator, dir_path: []const u8) !Dir {
+ const fd = try posixOpen(allocator, dir_path, posix.O_RDONLY|posix.O_DIRECTORY|posix.O_CLOEXEC, 0);
return Dir {
.allocator = allocator,
.fd = fd,
@@ -1006,7 +1149,7 @@ pub const Dir = struct {
/// Memory such as file names referenced in this returned entry becomes invalid
/// with subsequent calls to next, as well as when this ::Dir is deinitialized.
- pub fn next(self: &Dir) %?Entry {
+ pub fn next(self: &Dir) !?Entry {
start_over: while (true) {
if (self.index >= self.end_index) {
if (self.buf.len == 0) {
@@ -1063,7 +1206,7 @@ pub const Dir = struct {
}
};
-pub fn changeCurDir(allocator: &Allocator, dir_path: []const u8) %void {
+pub fn changeCurDir(allocator: &Allocator, dir_path: []const u8) !void {
const path_buf = try allocator.alloc(u8, dir_path.len + 1);
defer allocator.free(path_buf);
@@ -1087,7 +1230,7 @@ pub fn changeCurDir(allocator: &Allocator, dir_path: []const u8) %void {
}
/// Read value of a symbolic link.
-pub fn readLink(allocator: &Allocator, pathname: []const u8) %[]u8 {
+pub fn readLink(allocator: &Allocator, pathname: []const u8) ![]u8 {
const path_buf = try allocator.alloc(u8, pathname.len + 1);
defer allocator.free(path_buf);
@@ -1164,11 +1307,7 @@ test "os.sleep" {
sleep(0, 1);
}
-error ResourceLimitReached;
-error InvalidUserId;
-error PermissionDenied;
-
-pub fn posix_setuid(uid: u32) %void {
+pub fn posix_setuid(uid: u32) !void {
const err = posix.getErrno(posix.setuid(uid));
if (err == 0) return;
return switch (err) {
@@ -1179,7 +1318,7 @@ pub fn posix_setuid(uid: u32) %void {
};
}
-pub fn posix_setreuid(ruid: u32, euid: u32) %void {
+pub fn posix_setreuid(ruid: u32, euid: u32) !void {
const err = posix.getErrno(posix.setreuid(ruid, euid));
if (err == 0) return;
return switch (err) {
@@ -1190,7 +1329,7 @@ pub fn posix_setreuid(ruid: u32, euid: u32) %void {
};
}
-pub fn posix_setgid(gid: u32) %void {
+pub fn posix_setgid(gid: u32) !void {
const err = posix.getErrno(posix.setgid(gid));
if (err == 0) return;
return switch (err) {
@@ -1201,7 +1340,7 @@ pub fn posix_setgid(gid: u32) %void {
};
}
-pub fn posix_setregid(rgid: u32, egid: u32) %void {
+pub fn posix_setregid(rgid: u32, egid: u32) !void {
const err = posix.getErrno(posix.setregid(rgid, egid));
if (err == 0) return;
return switch (err) {
@@ -1212,8 +1351,12 @@ pub fn posix_setregid(rgid: u32, egid: u32) %void {
};
}
-error NoStdHandles;
-pub fn windowsGetStdHandle(handle_id: windows.DWORD) %windows.HANDLE {
+pub const WindowsGetStdHandleErrs = error {
+ NoStdHandles,
+ Unexpected,
+};
+
+pub fn windowsGetStdHandle(handle_id: windows.DWORD) WindowsGetStdHandleErrs!windows.HANDLE {
if (windows.GetStdHandle(handle_id)) |handle| {
if (handle == windows.INVALID_HANDLE_VALUE) {
const err = windows.GetLastError();
@@ -1267,6 +1410,8 @@ pub const ArgIteratorWindows = struct {
quote_count: usize,
seen_quote_count: usize,
+ pub const NextError = error{OutOfMemory};
+
pub fn init() ArgIteratorWindows {
return initWithCmdLine(windows.GetCommandLineA());
}
@@ -1282,7 +1427,7 @@ pub const ArgIteratorWindows = struct {
}
/// You must free the returned memory when done.
- pub fn next(self: &ArgIteratorWindows, allocator: &Allocator) ?%[]u8 {
+ pub fn next(self: &ArgIteratorWindows, allocator: &Allocator) ?(NextError![]u8) {
// march forward over whitespace
while (true) : (self.index += 1) {
const byte = self.cmd_line[self.index];
@@ -1335,7 +1480,7 @@ pub const ArgIteratorWindows = struct {
}
}
- fn internalNext(self: &ArgIteratorWindows, allocator: &Allocator) %[]u8 {
+ fn internalNext(self: &ArgIteratorWindows, allocator: &Allocator) NextError![]u8 {
var buf = try Buffer.initSize(allocator, 0);
defer buf.deinit();
@@ -1379,7 +1524,7 @@ pub const ArgIteratorWindows = struct {
}
}
- fn emitBackslashes(self: &ArgIteratorWindows, buf: &Buffer, emit_count: usize) %void {
+ fn emitBackslashes(self: &ArgIteratorWindows, buf: &Buffer, emit_count: usize) !void {
var i: usize = 0;
while (i < emit_count) : (i += 1) {
try buf.appendByte('\\');
@@ -1409,16 +1554,20 @@ pub const ArgIteratorWindows = struct {
};
pub const ArgIterator = struct {
- inner: if (builtin.os == Os.windows) ArgIteratorWindows else ArgIteratorPosix,
+ const InnerType = if (builtin.os == Os.windows) ArgIteratorWindows else ArgIteratorPosix;
+
+ inner: InnerType,
pub fn init() ArgIterator {
return ArgIterator {
- .inner = if (builtin.os == Os.windows) ArgIteratorWindows.init() else ArgIteratorPosix.init(),
+ .inner = InnerType.init(),
};
}
+
+ pub const NextError = ArgIteratorWindows.NextError;
/// You must free the returned memory when done.
- pub fn next(self: &ArgIterator, allocator: &Allocator) ?%[]u8 {
+ pub fn next(self: &ArgIterator, allocator: &Allocator) ?(NextError![]u8) {
if (builtin.os == Os.windows) {
return self.inner.next(allocator);
} else {
@@ -1443,7 +1592,7 @@ pub fn args() ArgIterator {
}
/// Caller must call freeArgs on result.
-pub fn argsAlloc(allocator: &mem.Allocator) %[]const []u8 {
+pub fn argsAlloc(allocator: &mem.Allocator) ![]const []u8 {
// TODO refactor to only make 1 allocation.
var it = args();
var contents = try Buffer.initSize(allocator, 0);
@@ -1525,14 +1674,12 @@ test "std.os" {
}
-error Unexpected;
-
// TODO make this a build variable that you can set
const unexpected_error_tracing = false;
/// Call this when you made a syscall or something that sets errno
/// and you get an unexpected error.
-pub fn unexpectedErrorPosix(errno: usize) error {
+pub fn unexpectedErrorPosix(errno: usize) (error{Unexpected}) {
if (unexpected_error_tracing) {
debug.warn("unexpected errno: {}\n", errno);
debug.dumpStackTrace();
@@ -1542,7 +1689,7 @@ pub fn unexpectedErrorPosix(errno: usize) error {
/// Call this when you made a windows DLL call or something that does SetLastError
/// and you get an unexpected error.
-pub fn unexpectedErrorWindows(err: windows.DWORD) error {
+pub fn unexpectedErrorWindows(err: windows.DWORD) (error{Unexpected}) {
if (unexpected_error_tracing) {
debug.warn("unexpected GetLastError(): {}\n", err);
debug.dumpStackTrace();
@@ -1550,25 +1697,38 @@ pub fn unexpectedErrorWindows(err: windows.DWORD) error {
return error.Unexpected;
}
-pub fn openSelfExe() %io.File {
+pub fn openSelfExe() !os.File {
switch (builtin.os) {
Os.linux => {
- return io.File.openRead("/proc/self/exe", null);
+ const proc_file_path = "/proc/self/exe";
+ var fixed_buffer_mem: [proc_file_path.len + 1]u8 = undefined;
+ var fixed_allocator = mem.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
+ return os.File.openRead(&fixed_allocator.allocator, proc_file_path);
},
Os.macosx, Os.ios => {
- @panic("TODO: openSelfExe on Darwin");
+ var fixed_buffer_mem: [darwin.PATH_MAX * 2]u8 = undefined;
+ var fixed_allocator = mem.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
+ const self_exe_path = try selfExePath(&fixed_allocator.allocator);
+ return os.File.openRead(&fixed_allocator.allocator, self_exe_path);
},
else => @compileError("Unsupported OS"),
}
}
+test "openSelfExe" {
+ switch (builtin.os) {
+ Os.linux, Os.macosx, Os.ios => (try openSelfExe()).close(),
+ else => return, // Unsupported OS.
+ }
+}
+
/// Get the path to the current executable.
/// If you only need the directory, use selfExeDirPath.
/// If you only want an open file handle, use openSelfExe.
/// This function may return an error if the current executable
/// was deleted after spawning.
/// Caller owns returned memory.
-pub fn selfExePath(allocator: &mem.Allocator) %[]u8 {
+pub fn selfExePath(allocator: &mem.Allocator) ![]u8 {
switch (builtin.os) {
Os.linux => {
// If the currently executing binary has been deleted,
@@ -1611,7 +1771,7 @@ pub fn selfExePath(allocator: &mem.Allocator) %[]u8 {
/// Get the directory path that contains the current executable.
/// Caller owns returned memory.
-pub fn selfExeDirPath(allocator: &mem.Allocator) %[]u8 {
+pub fn selfExeDirPath(allocator: &mem.Allocator) ![]u8 {
switch (builtin.os) {
Os.linux => {
// If the currently executing binary has been deleted,
diff --git a/std/os/linux/index.zig b/std/os/linux/index.zig
index 43d175b74d..113f2ef454 100644
--- a/std/os/linux/index.zig
+++ b/std/os/linux/index.zig
@@ -720,7 +720,7 @@ pub fn accept4(fd: i32, noalias addr: &sockaddr, noalias len: &socklen_t, flags:
// error SystemResources;
// error Io;
//
-// pub fn if_nametoindex(name: []u8) %u32 {
+// pub fn if_nametoindex(name: []u8) !u32 {
// var ifr: ifreq = undefined;
//
// if (name.len >= ifr.ifr_name.len) {
diff --git a/std/os/path.zig b/std/os/path.zig
index eb95f83f45..0ea5d5a753 100644
--- a/std/os/path.zig
+++ b/std/os/path.zig
@@ -32,7 +32,7 @@ pub fn isSep(byte: u8) bool {
/// Naively combines a series of paths with the native path seperator.
/// Allocates memory for the result, which must be freed by the caller.
-pub fn join(allocator: &Allocator, paths: ...) %[]u8 {
+pub fn join(allocator: &Allocator, paths: ...) ![]u8 {
if (is_windows) {
return joinWindows(allocator, paths);
} else {
@@ -40,11 +40,11 @@ pub fn join(allocator: &Allocator, paths: ...) %[]u8 {
}
}
-pub fn joinWindows(allocator: &Allocator, paths: ...) %[]u8 {
+pub fn joinWindows(allocator: &Allocator, paths: ...) ![]u8 {
return mem.join(allocator, sep_windows, paths);
}
-pub fn joinPosix(allocator: &Allocator, paths: ...) %[]u8 {
+pub fn joinPosix(allocator: &Allocator, paths: ...) ![]u8 {
return mem.join(allocator, sep_posix, paths);
}
@@ -313,7 +313,7 @@ fn asciiEqlIgnoreCase(s1: []const u8, s2: []const u8) bool {
}
/// Converts the command line arguments into a slice and calls `resolveSlice`.
-pub fn resolve(allocator: &Allocator, args: ...) %[]u8 {
+pub fn resolve(allocator: &Allocator, args: ...) ![]u8 {
var paths: [args.len][]const u8 = undefined;
comptime var arg_i = 0;
inline while (arg_i < args.len) : (arg_i += 1) {
@@ -323,7 +323,7 @@ pub fn resolve(allocator: &Allocator, args: ...) %[]u8 {
}
/// On Windows, this calls `resolveWindows` and on POSIX it calls `resolvePosix`.
-pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) %[]u8 {
+pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) ![]u8 {
if (is_windows) {
return resolveWindows(allocator, paths);
} else {
@@ -337,7 +337,7 @@ pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) %[]u8 {
/// If all paths are relative it uses the current working directory as a starting point.
/// Each drive has its own current working directory.
/// Path separators are canonicalized to '\\' and drives are canonicalized to capital letters.
-pub fn resolveWindows(allocator: &Allocator, paths: []const []const u8) %[]u8 {
+pub fn resolveWindows(allocator: &Allocator, paths: []const []const u8) ![]u8 {
if (paths.len == 0) {
assert(is_windows); // resolveWindows called on non windows can't use getCwd
return os.getCwd(allocator);
@@ -520,7 +520,7 @@ pub fn resolveWindows(allocator: &Allocator, paths: []const []const u8) %[]u8 {
/// It resolves "." and "..".
/// The result does not have a trailing path separator.
/// If all paths are relative it uses the current working directory as a starting point.
-pub fn resolvePosix(allocator: &Allocator, paths: []const []const u8) %[]u8 {
+pub fn resolvePosix(allocator: &Allocator, paths: []const []const u8) ![]u8 {
if (paths.len == 0) {
assert(!is_windows); // resolvePosix called on windows can't use getCwd
return os.getCwd(allocator);
@@ -890,7 +890,7 @@ fn testBasenameWindows(input: []const u8, expected_output: []const u8) void {
/// resolve to the same path (after calling `resolve` on each), a zero-length
/// string is returned.
/// On Windows this canonicalizes the drive to a capital letter and paths to `\\`.
-pub fn relative(allocator: &Allocator, from: []const u8, to: []const u8) %[]u8 {
+pub fn relative(allocator: &Allocator, from: []const u8, to: []const u8) ![]u8 {
if (is_windows) {
return relativeWindows(allocator, from, to);
} else {
@@ -898,7 +898,7 @@ pub fn relative(allocator: &Allocator, from: []const u8, to: []const u8) %[]u8 {
}
}
-pub fn relativeWindows(allocator: &Allocator, from: []const u8, to: []const u8) %[]u8 {
+pub fn relativeWindows(allocator: &Allocator, from: []const u8, to: []const u8) ![]u8 {
const resolved_from = try resolveWindows(allocator, [][]const u8{from});
defer allocator.free(resolved_from);
@@ -971,7 +971,7 @@ pub fn relativeWindows(allocator: &Allocator, from: []const u8, to: []const u8)
return []u8{};
}
-pub fn relativePosix(allocator: &Allocator, from: []const u8, to: []const u8) %[]u8 {
+pub fn relativePosix(allocator: &Allocator, from: []const u8, to: []const u8) ![]u8 {
const resolved_from = try resolvePosix(allocator, [][]const u8{from});
defer allocator.free(resolved_from);
@@ -1066,18 +1066,11 @@ fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []cons
assert(mem.eql(u8, result, expected_output));
}
-error AccessDenied;
-error FileNotFound;
-error NotSupported;
-error NotDir;
-error NameTooLong;
-error SymLinkLoop;
-error InputOutput;
/// Return the canonicalized absolute pathname.
/// Expands all symbolic links and resolves references to `.`, `..`, and
/// extra `/` characters in ::pathname.
/// Caller must deallocate result.
-pub fn real(allocator: &Allocator, pathname: []const u8) %[]u8 {
+pub fn real(allocator: &Allocator, pathname: []const u8) ![]u8 {
switch (builtin.os) {
Os.windows => {
const pathname_buf = try allocator.alloc(u8, pathname.len + 1);
@@ -1168,7 +1161,7 @@ pub fn real(allocator: &Allocator, pathname: []const u8) %[]u8 {
return allocator.shrink(u8, result_buf, cstr.len(result_buf.ptr));
},
Os.linux => {
- const fd = try os.posixOpen(pathname, posix.O_PATH|posix.O_NONBLOCK|posix.O_CLOEXEC, 0, allocator);
+ const fd = try os.posixOpen(allocator, pathname, posix.O_PATH|posix.O_NONBLOCK|posix.O_CLOEXEC, 0);
defer os.close(fd);
var buf: ["/proc/self/fd/-2147483648".len]u8 = undefined;
diff --git a/std/os/windows/util.zig b/std/os/windows/util.zig
index a8a2e3fcd5..5af318b7b0 100644
--- a/std/os/windows/util.zig
+++ b/std/os/windows/util.zig
@@ -1,4 +1,5 @@
const std = @import("../../index.zig");
+const builtin = @import("builtin");
const os = std.os;
const windows = std.os.windows;
const assert = std.debug.assert;
@@ -6,11 +7,13 @@ const mem = std.mem;
const BufMap = std.BufMap;
const cstr = std.cstr;
-error WaitAbandoned;
-error WaitTimeOut;
-error Unexpected;
+pub const WaitError = error {
+ WaitAbandoned,
+ WaitTimeOut,
+ Unexpected,
+};
-pub fn windowsWaitSingle(handle: windows.HANDLE, milliseconds: windows.DWORD) %void {
+pub fn windowsWaitSingle(handle: windows.HANDLE, milliseconds: windows.DWORD) WaitError!void {
const result = windows.WaitForSingleObject(handle, milliseconds);
return switch (result) {
windows.WAIT_ABANDONED => error.WaitAbandoned,
@@ -30,21 +33,24 @@ pub fn windowsClose(handle: windows.HANDLE) void {
assert(windows.CloseHandle(handle) != 0);
}
-error SystemResources;
-error OperationAborted;
-error IoPending;
-error BrokenPipe;
+pub const WriteError = error {
+ SystemResources,
+ OperationAborted,
+ IoPending,
+ BrokenPipe,
+ Unexpected,
+};
-pub fn windowsWrite(handle: windows.HANDLE, bytes: []const u8) %void {
+pub fn windowsWrite(handle: windows.HANDLE, bytes: []const u8) WriteError!void {
if (windows.WriteFile(handle, @ptrCast(&const c_void, bytes.ptr), u32(bytes.len), null, null) == 0) {
const err = windows.GetLastError();
return switch (err) {
- windows.ERROR.INVALID_USER_BUFFER => error.SystemResources,
- windows.ERROR.NOT_ENOUGH_MEMORY => error.SystemResources,
- windows.ERROR.OPERATION_ABORTED => error.OperationAborted,
- windows.ERROR.NOT_ENOUGH_QUOTA => error.SystemResources,
- windows.ERROR.IO_PENDING => error.IoPending,
- windows.ERROR.BROKEN_PIPE => error.BrokenPipe,
+ windows.ERROR.INVALID_USER_BUFFER => WriteError.SystemResources,
+ windows.ERROR.NOT_ENOUGH_MEMORY => WriteError.SystemResources,
+ windows.ERROR.OPERATION_ABORTED => WriteError.OperationAborted,
+ windows.ERROR.NOT_ENOUGH_QUOTA => WriteError.SystemResources,
+ windows.ERROR.IO_PENDING => WriteError.IoPending,
+ windows.ERROR.BROKEN_PIPE => WriteError.BrokenPipe,
else => os.unexpectedErrorWindows(err),
};
}
@@ -75,43 +81,35 @@ pub fn windowsIsCygwinPty(handle: windows.HANDLE) bool {
mem.indexOf(u16, name_wide, []u16{'-','p','t','y'}) != null;
}
-error SharingViolation;
-error PipeBusy;
-
-/// `file_path` may need to be copied in memory to add a null terminating byte. In this case
-/// a fixed size buffer of size ::max_noalloc_path_len is an attempted solution. If the fixed
-/// size buffer is too small, and the provided allocator is null, ::error.NameTooLong is returned.
-/// otherwise if the fixed size buffer is too small, allocator is used to obtain the needed memory.
-pub fn windowsOpen(file_path: []const u8, desired_access: windows.DWORD, share_mode: windows.DWORD,
- creation_disposition: windows.DWORD, flags_and_attrs: windows.DWORD, allocator: ?&mem.Allocator) %windows.HANDLE
+pub const OpenError = error {
+ SharingViolation,
+ PathAlreadyExists,
+ FileNotFound,
+ AccessDenied,
+ PipeBusy,
+ Unexpected,
+ OutOfMemory,
+};
+
+/// `file_path` needs to be copied in memory to add a null terminating byte, hence the allocator.
+pub fn windowsOpen(allocator: &mem.Allocator, file_path: []const u8, desired_access: windows.DWORD, share_mode: windows.DWORD,
+ creation_disposition: windows.DWORD, flags_and_attrs: windows.DWORD)
+ OpenError!windows.HANDLE
{
- var stack_buf: [os.max_noalloc_path_len]u8 = undefined;
- var path0: []u8 = undefined;
- var need_free = false;
- defer if (need_free) (??allocator).free(path0);
-
- if (file_path.len < stack_buf.len) {
- path0 = stack_buf[0..file_path.len + 1];
- } else if (allocator) |a| {
- path0 = try a.alloc(u8, file_path.len + 1);
- need_free = true;
- } else {
- return error.NameTooLong;
- }
- mem.copy(u8, path0, file_path);
- path0[file_path.len] = 0;
+ const path_with_null = try cstr.addNullByte(allocator, file_path);
+ defer allocator.free(path_with_null);
- const result = windows.CreateFileA(path0.ptr, desired_access, share_mode, null, creation_disposition,
+ const result = windows.CreateFileA(path_with_null.ptr, desired_access, share_mode, null, creation_disposition,
flags_and_attrs, null);
if (result == windows.INVALID_HANDLE_VALUE) {
const err = windows.GetLastError();
return switch (err) {
- windows.ERROR.SHARING_VIOLATION => error.SharingViolation,
- windows.ERROR.ALREADY_EXISTS, windows.ERROR.FILE_EXISTS => error.PathAlreadyExists,
- windows.ERROR.FILE_NOT_FOUND => error.FileNotFound,
- windows.ERROR.ACCESS_DENIED => error.AccessDenied,
- windows.ERROR.PIPE_BUSY => error.PipeBusy,
+ windows.ERROR.SHARING_VIOLATION => OpenError.SharingViolation,
+ windows.ERROR.ALREADY_EXISTS, windows.ERROR.FILE_EXISTS => OpenError.PathAlreadyExists,
+ windows.ERROR.FILE_NOT_FOUND => OpenError.FileNotFound,
+ windows.ERROR.ACCESS_DENIED => OpenError.AccessDenied,
+ windows.ERROR.PIPE_BUSY => OpenError.PipeBusy,
else => os.unexpectedErrorWindows(err),
};
}
@@ -120,7 +118,7 @@ pub fn windowsOpen(file_path: []const u8, desired_access: windows.DWORD, share_m
}
/// Caller must free result.
-pub fn createWindowsEnvBlock(allocator: &mem.Allocator, env_map: &const BufMap) %[]u8 {
+pub fn createWindowsEnvBlock(allocator: &mem.Allocator, env_map: &const BufMap) ![]u8 {
// count bytes needed
const bytes_needed = x: {
var bytes_needed: usize = 1; // 1 for the final null byte
@@ -151,8 +149,7 @@ pub fn createWindowsEnvBlock(allocator: &mem.Allocator, env_map: &const BufMap)
return result;
}
-error DllNotFound;
-pub fn windowsLoadDll(allocator: &mem.Allocator, dll_path: []const u8) %windows.HMODULE {
+pub fn windowsLoadDll(allocator: &mem.Allocator, dll_path: []const u8) !windows.HMODULE {
const padded_buff = try cstr.addNullByte(allocator, dll_path);
defer allocator.free(padded_buff);
return windows.LoadLibraryA(padded_buff.ptr) ?? error.DllNotFound;
@@ -164,6 +161,8 @@ pub fn windowsUnloadDll(hModule: windows.HMODULE) void {
test "InvalidDll" {
+ if (builtin.os != builtin.Os.windows) return;
+
const DllName = "asdf.dll";
const allocator = std.debug.global_allocator;
const handle = os.windowsLoadDll(allocator, DllName) catch |err| {
diff --git a/std/special/bootstrap.zig b/std/special/bootstrap.zig
index bcb3456353..f5754638b0 100644
--- a/std/special/bootstrap.zig
+++ b/std/special/bootstrap.zig
@@ -77,7 +77,7 @@ fn callMain() u8 {
},
builtin.TypeId.Int => {
if (@typeOf(root.main).ReturnType.bit_count != 8) {
- @compileError("expected return type of main to be 'u8', 'noreturn', 'void', or '%void'");
+ @compileError("expected return type of main to be 'u8', 'noreturn', 'void', or '!void'");
}
return root.main();
},
@@ -91,6 +91,6 @@ fn callMain() u8 {
};
return 0;
},
- else => @compileError("expected return type of main to be 'u8', 'noreturn', 'void', or '%void'"),
+ else => @compileError("expected return type of main to be 'u8', 'noreturn', 'void', or '!void'"),
}
}
diff --git a/std/special/build_file_template.zig b/std/special/build_file_template.zig
index 282759bedb..2edfdadf50 100644
--- a/std/special/build_file_template.zig
+++ b/std/special/build_file_template.zig
@@ -1,6 +1,6 @@
const Builder = @import("std").build.Builder;
-pub fn build(b: &Builder) %void {
+pub fn build(b: &Builder) !void {
const mode = b.standardReleaseOptions();
const exe = b.addExecutable("YOUR_NAME_HERE", "src/main.zig");
exe.setBuildMode(mode);
diff --git a/std/special/build_runner.zig b/std/special/build_runner.zig
index e1648276aa..4805240db7 100644
--- a/std/special/build_runner.zig
+++ b/std/special/build_runner.zig
@@ -1,5 +1,6 @@
const root = @import("@build");
const std = @import("std");
+const builtin = @import("builtin");
const io = std.io;
const fmt = std.fmt;
const os = std.os;
@@ -8,9 +9,7 @@ const mem = std.mem;
const ArrayList = std.ArrayList;
const warn = std.debug.warn;
-error InvalidArgs;
-
-pub fn main() %void {
+pub fn main() !void {
var arg_it = os.args();
// TODO use a more general purpose allocator here
@@ -45,14 +44,14 @@ pub fn main() %void {
var stderr_file = io.getStdErr();
var stderr_file_stream: io.FileOutStream = undefined;
- var stderr_stream: %&io.OutStream = if (stderr_file) |*f| x: {
+ var stderr_stream = if (stderr_file) |*f| x: {
stderr_file_stream = io.FileOutStream.init(f);
break :x &stderr_file_stream.stream;
} else |err| err;
var stdout_file = io.getStdOut();
var stdout_file_stream: io.FileOutStream = undefined;
- var stdout_stream: %&io.OutStream = if (stdout_file) |*f| x: {
+ var stdout_stream = if (stdout_file) |*f| x: {
stdout_file_stream = io.FileOutStream.init(f);
break :x &stdout_file_stream.stream;
} else |err| err;
@@ -112,7 +111,7 @@ pub fn main() %void {
}
builder.setInstallPrefix(prefix);
- try root.build(&builder);
+ try runBuild(&builder);
if (builder.validateUserInputDidItFail())
return usageAndErr(&builder, true, try stderr_stream);
@@ -125,11 +124,19 @@ pub fn main() %void {
};
}
-fn usage(builder: &Builder, already_ran_build: bool, out_stream: &io.OutStream) %void {
+fn runBuild(builder: &Builder) error!void {
+ switch (@typeId(@typeOf(root.build).ReturnType)) {
+ builtin.TypeId.Void => root.build(builder),
+ builtin.TypeId.ErrorUnion => try root.build(builder),
+ else => @compileError("expected return type of build to be 'void' or '!void'"),
+ }
+}
+
+fn usage(builder: &Builder, already_ran_build: bool, out_stream: var) !void {
// run the build script to collect the options
if (!already_ran_build) {
builder.setInstallPrefix(null);
- try root.build(builder);
+ try runBuild(builder);
}
// This usage text has to be synchronized with src/main.cpp
@@ -149,6 +156,7 @@ fn usage(builder: &Builder, already_ran_build: bool, out_stream: &io.OutStream)
\\
\\General Options:
\\ --help Print this help and exit
+ \\ --init Generate a build.zig template
\\ --verbose Print commands before executing them
\\ --prefix [path] Override default install prefix
\\ --search-prefix [path] Add a path to look for binaries, libraries, headers
@@ -183,12 +191,14 @@ fn usage(builder: &Builder, already_ran_build: bool, out_stream: &io.OutStream)
);
}
-fn usageAndErr(builder: &Builder, already_ran_build: bool, out_stream: &io.OutStream) error {
+fn usageAndErr(builder: &Builder, already_ran_build: bool, out_stream: var) error {
usage(builder, already_ran_build, out_stream) catch {};
return error.InvalidArgs;
}
-fn unwrapArg(arg: %[]u8) %[]u8 {
+const UnwrapArgError = error {OutOfMemory};
+
+fn unwrapArg(arg: UnwrapArgError![]u8) UnwrapArgError![]u8 {
return arg catch |err| {
warn("Unable to parse command line: {}\n", err);
return err;
diff --git a/std/special/test_runner.zig b/std/special/test_runner.zig
index 3284f740b0..76a54a5018 100644
--- a/std/special/test_runner.zig
+++ b/std/special/test_runner.zig
@@ -4,7 +4,7 @@ const builtin = @import("builtin");
const test_fn_list = builtin.__zig_test_fn_slice;
const warn = std.debug.warn;
-pub fn main() %void {
+pub fn main() !void {
for (test_fn_list) |test_fn, i| {
warn("Test {}/{} {}...", i + 1, test_fn_list.len, test_fn.name);
diff --git a/std/unicode.zig b/std/unicode.zig
index 235ac4ceac..df62e9162f 100644
--- a/std/unicode.zig
+++ b/std/unicode.zig
@@ -1,11 +1,9 @@
const std = @import("./index.zig");
-error Utf8InvalidStartByte;
-
/// Given the first byte of a UTF-8 codepoint,
/// returns a number 1-4 indicating the total length of the codepoint in bytes.
/// If this byte does not match the form of a UTF-8 start byte, returns Utf8InvalidStartByte.
-pub fn utf8ByteSequenceLength(first_byte: u8) %u3 {
+pub fn utf8ByteSequenceLength(first_byte: u8) !u3 {
if (first_byte < 0b10000000) return u3(1);
if (first_byte & 0b11100000 == 0b11000000) return u3(2);
if (first_byte & 0b11110000 == 0b11100000) return u3(3);
@@ -13,16 +11,11 @@ pub fn utf8ByteSequenceLength(first_byte: u8) %u3 {
return error.Utf8InvalidStartByte;
}
-error Utf8OverlongEncoding;
-error Utf8ExpectedContinuation;
-error Utf8EncodesSurrogateHalf;
-error Utf8CodepointTooLarge;
-
/// Decodes the UTF-8 codepoint encoded in the given slice of bytes.
/// bytes.len must be equal to utf8ByteSequenceLength(bytes[0]) catch unreachable.
/// If you already know the length at comptime, you can call one of
/// utf8Decode2,utf8Decode3,utf8Decode4 directly instead of this function.
-pub fn utf8Decode(bytes: []const u8) %u32 {
+pub fn utf8Decode(bytes: []const u8) !u32 {
return switch (bytes.len) {
1 => u32(bytes[0]),
2 => utf8Decode2(bytes),
@@ -31,7 +24,7 @@ pub fn utf8Decode(bytes: []const u8) %u32 {
else => unreachable,
};
}
-pub fn utf8Decode2(bytes: []const u8) %u32 {
+pub fn utf8Decode2(bytes: []const u8) !u32 {
std.debug.assert(bytes.len == 2);
std.debug.assert(bytes[0] & 0b11100000 == 0b11000000);
var value: u32 = bytes[0] & 0b00011111;
@@ -44,7 +37,7 @@ pub fn utf8Decode2(bytes: []const u8) %u32 {
return value;
}
-pub fn utf8Decode3(bytes: []const u8) %u32 {
+pub fn utf8Decode3(bytes: []const u8) !u32 {
std.debug.assert(bytes.len == 3);
std.debug.assert(bytes[0] & 0b11110000 == 0b11100000);
var value: u32 = bytes[0] & 0b00001111;
@@ -62,7 +55,7 @@ pub fn utf8Decode3(bytes: []const u8) %u32 {
return value;
}
-pub fn utf8Decode4(bytes: []const u8) %u32 {
+pub fn utf8Decode4(bytes: []const u8) !u32 {
std.debug.assert(bytes.len == 4);
std.debug.assert(bytes[0] & 0b11111000 == 0b11110000);
var value: u32 = bytes[0] & 0b00000111;
@@ -85,7 +78,6 @@ pub fn utf8Decode4(bytes: []const u8) %u32 {
return value;
}
-error UnexpectedEof;
test "valid utf8" {
testValid("\x00", 0x0);
testValid("\x20", 0x20);
@@ -161,7 +153,7 @@ fn testValid(bytes: []const u8, expected_codepoint: u32) void {
std.debug.assert((testDecode(bytes) catch unreachable) == expected_codepoint);
}
-fn testDecode(bytes: []const u8) %u32 {
+fn testDecode(bytes: []const u8) !u32 {
const length = try utf8ByteSequenceLength(bytes[0]);
if (bytes.len < length) return error.UnexpectedEof;
std.debug.assert(bytes.len == length);
diff --git a/std/zig/ast.zig b/std/zig/ast.zig
new file mode 100644
index 0000000000..a966c0316e
--- /dev/null
+++ b/std/zig/ast.zig
@@ -0,0 +1,271 @@
+const std = @import("../index.zig");
+const assert = std.debug.assert;
+const ArrayList = std.ArrayList;
+const Token = std.zig.Token;
+const mem = std.mem;
+
+pub const Node = struct {
+ id: Id,
+
+ pub const Id = enum {
+ Root,
+ VarDecl,
+ Identifier,
+ FnProto,
+ ParamDecl,
+ Block,
+ InfixOp,
+ PrefixOp,
+ IntegerLiteral,
+ FloatLiteral,
+ };
+
+ pub fn iterate(base: &Node, index: usize) ?&Node {
+ return switch (base.id) {
+ Id.Root => @fieldParentPtr(NodeRoot, "base", base).iterate(index),
+ Id.VarDecl => @fieldParentPtr(NodeVarDecl, "base", base).iterate(index),
+ Id.Identifier => @fieldParentPtr(NodeIdentifier, "base", base).iterate(index),
+ Id.FnProto => @fieldParentPtr(NodeFnProto, "base", base).iterate(index),
+ Id.ParamDecl => @fieldParentPtr(NodeParamDecl, "base", base).iterate(index),
+ Id.Block => @fieldParentPtr(NodeBlock, "base", base).iterate(index),
+ Id.InfixOp => @fieldParentPtr(NodeInfixOp, "base", base).iterate(index),
+ Id.PrefixOp => @fieldParentPtr(NodePrefixOp, "base", base).iterate(index),
+ Id.IntegerLiteral => @fieldParentPtr(NodeIntegerLiteral, "base", base).iterate(index),
+ Id.FloatLiteral => @fieldParentPtr(NodeFloatLiteral, "base", base).iterate(index),
+ };
+ }
+
+ pub fn destroy(base: &Node, allocator: &mem.Allocator) void {
+ return switch (base.id) {
+ Id.Root => allocator.destroy(@fieldParentPtr(NodeRoot, "base", base)),
+ Id.VarDecl => allocator.destroy(@fieldParentPtr(NodeVarDecl, "base", base)),
+ Id.Identifier => allocator.destroy(@fieldParentPtr(NodeIdentifier, "base", base)),
+ Id.FnProto => allocator.destroy(@fieldParentPtr(NodeFnProto, "base", base)),
+ Id.ParamDecl => allocator.destroy(@fieldParentPtr(NodeParamDecl, "base", base)),
+ Id.Block => allocator.destroy(@fieldParentPtr(NodeBlock, "base", base)),
+ Id.InfixOp => allocator.destroy(@fieldParentPtr(NodeInfixOp, "base", base)),
+ Id.PrefixOp => allocator.destroy(@fieldParentPtr(NodePrefixOp, "base", base)),
+ Id.IntegerLiteral => allocator.destroy(@fieldParentPtr(NodeIntegerLiteral, "base", base)),
+ Id.FloatLiteral => allocator.destroy(@fieldParentPtr(NodeFloatLiteral, "base", base)),
+ };
+ }
+};
+
+pub const NodeRoot = struct {
+ base: Node,
+ decls: ArrayList(&Node),
+
+ pub fn iterate(self: &NodeRoot, index: usize) ?&Node {
+ if (index < self.decls.len) {
+ return self.decls.items[self.decls.len - index - 1];
+ }
+ return null;
+ }
+};
+
+pub const NodeVarDecl = struct {
+ base: Node,
+ visib_token: ?Token,
+ name_token: Token,
+ eq_token: Token,
+ mut_token: Token,
+ comptime_token: ?Token,
+ extern_token: ?Token,
+ lib_name: ?&Node,
+ type_node: ?&Node,
+ align_node: ?&Node,
+ init_node: ?&Node,
+
+ pub fn iterate(self: &NodeVarDecl, index: usize) ?&Node {
+ var i = index;
+
+ if (self.type_node) |type_node| {
+ if (i < 1) return type_node;
+ i -= 1;
+ }
+
+ if (self.align_node) |align_node| {
+ if (i < 1) return align_node;
+ i -= 1;
+ }
+
+ if (self.init_node) |init_node| {
+ if (i < 1) return init_node;
+ i -= 1;
+ }
+
+ return null;
+ }
+};
+
+pub const NodeIdentifier = struct {
+ base: Node,
+ name_token: Token,
+
+ pub fn iterate(self: &NodeIdentifier, index: usize) ?&Node {
+ return null;
+ }
+};
+
+pub const NodeFnProto = struct {
+ base: Node,
+ visib_token: ?Token,
+ fn_token: Token,
+ name_token: ?Token,
+ params: ArrayList(&Node),
+ return_type: &Node,
+ var_args_token: ?Token,
+ extern_token: ?Token,
+ inline_token: ?Token,
+ cc_token: ?Token,
+ body_node: ?&Node,
+ lib_name: ?&Node, // populated if this is an extern declaration
+ align_expr: ?&Node, // populated if align(A) is present
+
+ pub fn iterate(self: &NodeFnProto, index: usize) ?&Node {
+ var i = index;
+
+ if (self.body_node) |body_node| {
+ if (i < 1) return body_node;
+ i -= 1;
+ }
+
+ if (i < 1) return self.return_type;
+ i -= 1;
+
+ if (self.align_expr) |align_expr| {
+ if (i < 1) return align_expr;
+ i -= 1;
+ }
+
+ if (i < self.params.len) return self.params.items[self.params.len - i - 1];
+ i -= self.params.len;
+
+ if (self.lib_name) |lib_name| {
+ if (i < 1) return lib_name;
+ i -= 1;
+ }
+
+ return null;
+ }
+};
+
+pub const NodeParamDecl = struct {
+ base: Node,
+ comptime_token: ?Token,
+ noalias_token: ?Token,
+ name_token: ?Token,
+ type_node: &Node,
+ var_args_token: ?Token,
+
+ pub fn iterate(self: &NodeParamDecl, index: usize) ?&Node {
+ var i = index;
+
+ if (i < 1) return self.type_node;
+ i -= 1;
+
+ return null;
+ }
+};
+
+pub const NodeBlock = struct {
+ base: Node,
+ begin_token: Token,
+ end_token: Token,
+ statements: ArrayList(&Node),
+
+ pub fn iterate(self: &NodeBlock, index: usize) ?&Node {
+ var i = index;
+
+ if (i < self.statements.len) return self.statements.items[i];
+ i -= self.statements.len;
+
+ return null;
+ }
+};
+
+pub const NodeInfixOp = struct {
+ base: Node,
+ op_token: Token,
+ lhs: &Node,
+ op: InfixOp,
+ rhs: &Node,
+
+ const InfixOp = enum {
+ EqualEqual,
+ BangEqual,
+ };
+
+ pub fn iterate(self: &NodeInfixOp, index: usize) ?&Node {
+ var i = index;
+
+ if (i < 1) return self.lhs;
+ i -= 1;
+
+ switch (self.op) {
+ InfixOp.EqualEqual => {},
+ InfixOp.BangEqual => {},
+ }
+
+ if (i < 1) return self.rhs;
+ i -= 1;
+
+ return null;
+ }
+};
+
+pub const NodePrefixOp = struct {
+ base: Node,
+ op_token: Token,
+ op: PrefixOp,
+ rhs: &Node,
+
+ const PrefixOp = union(enum) {
+ Return,
+ AddrOf: AddrOfInfo,
+ };
+ const AddrOfInfo = struct {
+ align_expr: ?&Node,
+ bit_offset_start_token: ?Token,
+ bit_offset_end_token: ?Token,
+ const_token: ?Token,
+ volatile_token: ?Token,
+ };
+
+ pub fn iterate(self: &NodePrefixOp, index: usize) ?&Node {
+ var i = index;
+
+ switch (self.op) {
+ PrefixOp.Return => {},
+ PrefixOp.AddrOf => |addr_of_info| {
+ if (addr_of_info.align_expr) |align_expr| {
+ if (i < 1) return align_expr;
+ i -= 1;
+ }
+ },
+ }
+
+ if (i < 1) return self.rhs;
+ i -= 1;
+
+ return null;
+ }
+};
+
+pub const NodeIntegerLiteral = struct {
+ base: Node,
+ token: Token,
+
+ pub fn iterate(self: &NodeIntegerLiteral, index: usize) ?&Node {
+ return null;
+ }
+};
+
+pub const NodeFloatLiteral = struct {
+ base: Node,
+ token: Token,
+
+ pub fn iterate(self: &NodeFloatLiteral, index: usize) ?&Node {
+ return null;
+ }
+};
diff --git a/std/zig/index.zig b/std/zig/index.zig
new file mode 100644
index 0000000000..32699935d9
--- /dev/null
+++ b/std/zig/index.zig
@@ -0,0 +1,11 @@
+const tokenizer = @import("tokenizer.zig");
+pub const Token = tokenizer.Token;
+pub const Tokenizer = tokenizer.Tokenizer;
+pub const Parser = @import("parser.zig").Parser;
+pub const ast = @import("ast.zig");
+
+test "std.zig tests" {
+ _ = @import("tokenizer.zig");
+ _ = @import("parser.zig");
+ _ = @import("ast.zig");
+}
diff --git a/std/zig/parser.zig b/std/zig/parser.zig
new file mode 100644
index 0000000000..601e91fe7f
--- /dev/null
+++ b/std/zig/parser.zig
@@ -0,0 +1,1158 @@
+const std = @import("../index.zig");
+const assert = std.debug.assert;
+const ArrayList = std.ArrayList;
+const mem = std.mem;
+const ast = std.zig.ast;
+const Tokenizer = std.zig.Tokenizer;
+const Token = std.zig.Token;
+const builtin = @import("builtin");
+const io = std.io;
+
+// TODO when we make parse errors into error types instead of printing directly,
+// get rid of this
+const warn = std.debug.warn;
+
+pub const Parser = struct {
+ allocator: &mem.Allocator,
+ tokenizer: &Tokenizer,
+ put_back_tokens: [2]Token,
+ put_back_count: usize,
+ source_file_name: []const u8,
+
+ pub const Tree = struct {
+ root_node: &ast.NodeRoot,
+
+ pub fn deinit(self: &const Tree) void {
+ // TODO free the whole arena
+ }
+ };
+
+ // This memory contents are used only during a function call. It's used to repurpose memory;
+ // we reuse the same bytes for the stack data structure used by parsing, tree rendering, and
+ // source rendering.
+ const utility_bytes_align = @alignOf( union { a: RenderAstFrame, b: State, c: RenderState } );
+ utility_bytes: []align(utility_bytes_align) u8,
+
+ /// `allocator` should be an arena allocator. Parser never calls free on anything. After you're
+ /// done with a Parser, free the arena. After the arena is freed, no member functions of Parser
+ /// may be called.
+ pub fn init(tokenizer: &Tokenizer, allocator: &mem.Allocator, source_file_name: []const u8) Parser {
+ return Parser {
+ .allocator = allocator,
+ .tokenizer = tokenizer,
+ .put_back_tokens = undefined,
+ .put_back_count = 0,
+ .source_file_name = source_file_name,
+ .utility_bytes = []align(utility_bytes_align) u8{},
+ };
+ }
+
+ pub fn deinit(self: &Parser) void {
+ self.allocator.free(self.utility_bytes);
+ }
+
+ const TopLevelDeclCtx = struct {
+ visib_token: ?Token,
+ extern_token: ?Token,
+ };
+
+ const DestPtr = union(enum) {
+ Field: &&ast.Node,
+ NullableField: &?&ast.Node,
+ List: &ArrayList(&ast.Node),
+
+ pub fn store(self: &const DestPtr, value: &ast.Node) !void {
+ switch (*self) {
+ DestPtr.Field => |ptr| *ptr = value,
+ DestPtr.NullableField => |ptr| *ptr = value,
+ DestPtr.List => |list| try list.append(value),
+ }
+ }
+ };
+
+ const State = union(enum) {
+ TopLevel,
+ TopLevelExtern: ?Token,
+ TopLevelDecl: TopLevelDeclCtx,
+ Expression: DestPtr,
+ ExpectOperand,
+ Operand: &ast.Node,
+ AfterOperand,
+ InfixOp: &ast.NodeInfixOp,
+ PrefixOp: &ast.NodePrefixOp,
+ AddrOfModifiers: &ast.NodePrefixOp.AddrOfInfo,
+ TypeExpr: DestPtr,
+ VarDecl: &ast.NodeVarDecl,
+ VarDeclAlign: &ast.NodeVarDecl,
+ VarDeclEq: &ast.NodeVarDecl,
+ ExpectToken: @TagType(Token.Id),
+ FnProto: &ast.NodeFnProto,
+ FnProtoAlign: &ast.NodeFnProto,
+ ParamDecl: &ast.NodeFnProto,
+ ParamDeclComma,
+ FnDef: &ast.NodeFnProto,
+ Block: &ast.NodeBlock,
+ Statement: &ast.NodeBlock,
+ };
+
+ /// Returns an AST tree, allocated with the parser's allocator.
+ /// Result should be freed with `freeAst` when done.
+ pub fn parse(self: &Parser) !Tree {
+ var stack = self.initUtilityArrayList(State);
+ defer self.deinitUtilityArrayList(stack);
+
+ const root_node = try self.createRoot();
+ // TODO errdefer arena free root node
+
+ try stack.append(State.TopLevel);
+
+ while (true) {
+ //{
+ // const token = self.getNextToken();
+ // warn("{} ", @tagName(token.id));
+ // self.putBackToken(token);
+ // var i: usize = stack.len;
+ // while (i != 0) {
+ // i -= 1;
+ // warn("{} ", @tagName(stack.items[i]));
+ // }
+ // warn("\n");
+ //}
+
+ // This gives us 1 free append that can't fail
+ const state = stack.pop();
+
+ switch (state) {
+ State.TopLevel => {
+ const token = self.getNextToken();
+ switch (token.id) {
+ Token.Id.Keyword_pub, Token.Id.Keyword_export => {
+ stack.append(State { .TopLevelExtern = token }) catch unreachable;
+ continue;
+ },
+ Token.Id.Eof => return Tree {.root_node = root_node},
+ else => {
+ self.putBackToken(token);
+ stack.append(State { .TopLevelExtern = null }) catch unreachable;
+ continue;
+ },
+ }
+ },
+ State.TopLevelExtern => |visib_token| {
+ const token = self.getNextToken();
+ if (token.id == Token.Id.Keyword_extern) {
+ stack.append(State {
+ .TopLevelDecl = TopLevelDeclCtx {
+ .visib_token = visib_token,
+ .extern_token = token,
+ },
+ }) catch unreachable;
+ continue;
+ }
+ self.putBackToken(token);
+ stack.append(State {
+ .TopLevelDecl = TopLevelDeclCtx {
+ .visib_token = visib_token,
+ .extern_token = null,
+ },
+ }) catch unreachable;
+ continue;
+ },
+ State.TopLevelDecl => |ctx| {
+ const token = self.getNextToken();
+ switch (token.id) {
+ Token.Id.Keyword_var, Token.Id.Keyword_const => {
+ stack.append(State.TopLevel) catch unreachable;
+ // TODO shouldn't need these casts
+ const var_decl_node = try self.createAttachVarDecl(&root_node.decls, ctx.visib_token,
+ token, (?Token)(null), ctx.extern_token);
+ try stack.append(State { .VarDecl = var_decl_node });
+ continue;
+ },
+ Token.Id.Keyword_fn => {
+ stack.append(State.TopLevel) catch unreachable;
+ // TODO shouldn't need these casts
+ const fn_proto = try self.createAttachFnProto(&root_node.decls, token,
+ ctx.extern_token, (?Token)(null), (?Token)(null), (?Token)(null));
+ try stack.append(State { .FnDef = fn_proto });
+ try stack.append(State { .FnProto = fn_proto });
+ continue;
+ },
+ Token.Id.StringLiteral => {
+ @panic("TODO extern with string literal");
+ },
+ Token.Id.Keyword_nakedcc, Token.Id.Keyword_stdcallcc => {
+ stack.append(State.TopLevel) catch unreachable;
+ const fn_token = try self.eatToken(Token.Id.Keyword_fn);
+ // TODO shouldn't need this cast
+ const fn_proto = try self.createAttachFnProto(&root_node.decls, fn_token,
+ ctx.extern_token, (?Token)(token), (?Token)(null), (?Token)(null));
+ try stack.append(State { .FnDef = fn_proto });
+ try stack.append(State { .FnProto = fn_proto });
+ continue;
+ },
+ else => return self.parseError(token, "expected variable declaration or function, found {}", @tagName(token.id)),
+ }
+ },
+ State.VarDecl => |var_decl| {
+ var_decl.name_token = try self.eatToken(Token.Id.Identifier);
+ stack.append(State { .VarDeclAlign = var_decl }) catch unreachable;
+
+ const next_token = self.getNextToken();
+ if (next_token.id == Token.Id.Colon) {
+ try stack.append(State { .TypeExpr = DestPtr {.NullableField = &var_decl.type_node} });
+ continue;
+ }
+
+ self.putBackToken(next_token);
+ continue;
+ },
+ State.VarDeclAlign => |var_decl| {
+ stack.append(State { .VarDeclEq = var_decl }) catch unreachable;
+
+ const next_token = self.getNextToken();
+ if (next_token.id == Token.Id.Keyword_align) {
+ _ = try self.eatToken(Token.Id.LParen);
+ try stack.append(State { .ExpectToken = Token.Id.RParen });
+ try stack.append(State { .Expression = DestPtr{.NullableField = &var_decl.align_node} });
+ continue;
+ }
+
+ self.putBackToken(next_token);
+ continue;
+ },
+ State.VarDeclEq => |var_decl| {
+ const token = self.getNextToken();
+ if (token.id == Token.Id.Equal) {
+ var_decl.eq_token = token;
+ stack.append(State { .ExpectToken = Token.Id.Semicolon }) catch unreachable;
+ try stack.append(State {
+ .Expression = DestPtr {.NullableField = &var_decl.init_node},
+ });
+ continue;
+ }
+ if (token.id == Token.Id.Semicolon) {
+ continue;
+ }
+ return self.parseError(token, "expected '=' or ';', found {}", @tagName(token.id));
+ },
+ State.ExpectToken => |token_id| {
+ _ = try self.eatToken(token_id);
+ continue;
+ },
+
+ State.Expression => |dest_ptr| {
+ // save the dest_ptr for later
+ stack.append(state) catch unreachable;
+ try stack.append(State.ExpectOperand);
+ continue;
+ },
+ State.ExpectOperand => {
+ // we'll either get an operand (like 1 or x),
+ // or a prefix operator (like ~ or return).
+ const token = self.getNextToken();
+ switch (token.id) {
+ Token.Id.Keyword_return => {
+ try stack.append(State { .PrefixOp = try self.createPrefixOp(token,
+ ast.NodePrefixOp.PrefixOp.Return) });
+ try stack.append(State.ExpectOperand);
+ continue;
+ },
+ Token.Id.Ampersand => {
+ const prefix_op = try self.createPrefixOp(token, ast.NodePrefixOp.PrefixOp{
+ .AddrOf = ast.NodePrefixOp.AddrOfInfo {
+ .align_expr = null,
+ .bit_offset_start_token = null,
+ .bit_offset_end_token = null,
+ .const_token = null,
+ .volatile_token = null,
+ }
+ });
+ try stack.append(State { .PrefixOp = prefix_op });
+ try stack.append(State.ExpectOperand);
+ try stack.append(State { .AddrOfModifiers = &prefix_op.op.AddrOf });
+ continue;
+ },
+ Token.Id.Identifier => {
+ try stack.append(State {
+ .Operand = &(try self.createIdentifier(token)).base
+ });
+ try stack.append(State.AfterOperand);
+ continue;
+ },
+ Token.Id.IntegerLiteral => {
+ try stack.append(State {
+ .Operand = &(try self.createIntegerLiteral(token)).base
+ });
+ try stack.append(State.AfterOperand);
+ continue;
+ },
+ Token.Id.FloatLiteral => {
+ try stack.append(State {
+ .Operand = &(try self.createFloatLiteral(token)).base
+ });
+ try stack.append(State.AfterOperand);
+ continue;
+ },
+ else => return self.parseError(token, "expected primary expression, found {}", @tagName(token.id)),
+ }
+ },
+
+ State.AfterOperand => {
+ // we'll either get an infix operator (like != or ^),
+ // or a postfix operator (like () or {}),
+ // otherwise this expression is done (like on a ; or else).
+ var token = self.getNextToken();
+ switch (token.id) {
+ Token.Id.EqualEqual => {
+ try stack.append(State {
+ .InfixOp = try self.createInfixOp(token, ast.NodeInfixOp.InfixOp.EqualEqual)
+ });
+ try stack.append(State.ExpectOperand);
+ continue;
+ },
+ Token.Id.BangEqual => {
+ try stack.append(State {
+ .InfixOp = try self.createInfixOp(token, ast.NodeInfixOp.InfixOp.BangEqual)
+ });
+ try stack.append(State.ExpectOperand);
+ continue;
+ },
+ else => {
+ // no postfix/infix operator after this operand.
+ self.putBackToken(token);
+ // reduce the stack
+ var expression: &ast.Node = stack.pop().Operand;
+ while (true) {
+ switch (stack.pop()) {
+ State.Expression => |dest_ptr| {
+ // we're done
+ try dest_ptr.store(expression);
+ break;
+ },
+ State.InfixOp => |infix_op| {
+ infix_op.rhs = expression;
+ infix_op.lhs = stack.pop().Operand;
+ expression = &infix_op.base;
+ continue;
+ },
+ State.PrefixOp => |prefix_op| {
+ prefix_op.rhs = expression;
+ expression = &prefix_op.base;
+ continue;
+ },
+ else => unreachable,
+ }
+ }
+ continue;
+ },
+ }
+ },
+
+ State.AddrOfModifiers => |addr_of_info| {
+ var token = self.getNextToken();
+ switch (token.id) {
+ Token.Id.Keyword_align => {
+ stack.append(state) catch unreachable;
+ if (addr_of_info.align_expr != null) return self.parseError(token, "multiple align qualifiers");
+ _ = try self.eatToken(Token.Id.LParen);
+ try stack.append(State { .ExpectToken = Token.Id.RParen });
+ try stack.append(State { .Expression = DestPtr{.NullableField = &addr_of_info.align_expr} });
+ continue;
+ },
+ Token.Id.Keyword_const => {
+ stack.append(state) catch unreachable;
+ if (addr_of_info.const_token != null) return self.parseError(token, "duplicate qualifier: const");
+ addr_of_info.const_token = token;
+ continue;
+ },
+ Token.Id.Keyword_volatile => {
+ stack.append(state) catch unreachable;
+ if (addr_of_info.volatile_token != null) return self.parseError(token, "duplicate qualifier: volatile");
+ addr_of_info.volatile_token = token;
+ continue;
+ },
+ else => {
+ self.putBackToken(token);
+ continue;
+ },
+ }
+ },
+
+ State.TypeExpr => |dest_ptr| {
+ const token = self.getNextToken();
+ if (token.id == Token.Id.Keyword_var) {
+ @panic("TODO param with type var");
+ }
+ self.putBackToken(token);
+
+ stack.append(State { .Expression = dest_ptr }) catch unreachable;
+ continue;
+ },
+
+ State.FnProto => |fn_proto| {
+ stack.append(State { .FnProtoAlign = fn_proto }) catch unreachable;
+ try stack.append(State { .ParamDecl = fn_proto });
+ try stack.append(State { .ExpectToken = Token.Id.LParen });
+
+ const next_token = self.getNextToken();
+ if (next_token.id == Token.Id.Identifier) {
+ fn_proto.name_token = next_token;
+ continue;
+ }
+ self.putBackToken(next_token);
+ continue;
+ },
+
+ State.FnProtoAlign => |fn_proto| {
+ const token = self.getNextToken();
+ if (token.id == Token.Id.Keyword_align) {
+ @panic("TODO fn proto align");
+ }
+ self.putBackToken(token);
+ stack.append(State {
+ .TypeExpr = DestPtr {.Field = &fn_proto.return_type},
+ }) catch unreachable;
+ continue;
+ },
+
+ State.ParamDecl => |fn_proto| {
+ var token = self.getNextToken();
+ if (token.id == Token.Id.RParen) {
+ continue;
+ }
+ const param_decl = try self.createAttachParamDecl(&fn_proto.params);
+ if (token.id == Token.Id.Keyword_comptime) {
+ param_decl.comptime_token = token;
+ token = self.getNextToken();
+ } else if (token.id == Token.Id.Keyword_noalias) {
+ param_decl.noalias_token = token;
+ token = self.getNextToken();
+ }
+ if (token.id == Token.Id.Identifier) {
+ const next_token = self.getNextToken();
+ if (next_token.id == Token.Id.Colon) {
+ param_decl.name_token = token;
+ token = self.getNextToken();
+ } else {
+ self.putBackToken(next_token);
+ }
+ }
+ if (token.id == Token.Id.Ellipsis3) {
+ param_decl.var_args_token = token;
+ stack.append(State { .ExpectToken = Token.Id.RParen }) catch unreachable;
+ continue;
+ } else {
+ self.putBackToken(token);
+ }
+
+ stack.append(State { .ParamDecl = fn_proto }) catch unreachable;
+ try stack.append(State.ParamDeclComma);
+ try stack.append(State {
+ .TypeExpr = DestPtr {.Field = &param_decl.type_node}
+ });
+ continue;
+ },
+
+ State.ParamDeclComma => {
+ const token = self.getNextToken();
+ switch (token.id) {
+ Token.Id.RParen => {
+ _ = stack.pop(); // pop off the ParamDecl
+ continue;
+ },
+ Token.Id.Comma => continue,
+ else => return self.parseError(token, "expected ',' or ')', found {}", @tagName(token.id)),
+ }
+ },
+
+ State.FnDef => |fn_proto| {
+ const token = self.getNextToken();
+ switch(token.id) {
+ Token.Id.LBrace => {
+ const block = try self.createBlock(token);
+ fn_proto.body_node = &block.base;
+ stack.append(State { .Block = block }) catch unreachable;
+ continue;
+ },
+ Token.Id.Semicolon => continue,
+ else => return self.parseError(token, "expected ';' or '{{', found {}", @tagName(token.id)),
+ }
+ },
+
+ State.Block => |block| {
+ const token = self.getNextToken();
+ switch (token.id) {
+ Token.Id.RBrace => {
+ block.end_token = token;
+ continue;
+ },
+ else => {
+ self.putBackToken(token);
+ stack.append(State { .Block = block }) catch unreachable;
+ try stack.append(State { .Statement = block });
+ continue;
+ },
+ }
+ },
+
+ State.Statement => |block| {
+ {
+ // Look for comptime var, comptime const
+ const comptime_token = self.getNextToken();
+ if (comptime_token.id == Token.Id.Keyword_comptime) {
+ const mut_token = self.getNextToken();
+ if (mut_token.id == Token.Id.Keyword_var or mut_token.id == Token.Id.Keyword_const) {
+ // TODO shouldn't need these casts
+ const var_decl = try self.createAttachVarDecl(&block.statements, (?Token)(null),
+ mut_token, (?Token)(comptime_token), (?Token)(null));
+ try stack.append(State { .VarDecl = var_decl });
+ continue;
+ }
+ self.putBackToken(mut_token);
+ }
+ self.putBackToken(comptime_token);
+ }
+ {
+ // Look for const, var
+ const mut_token = self.getNextToken();
+ if (mut_token.id == Token.Id.Keyword_var or mut_token.id == Token.Id.Keyword_const) {
+ // TODO shouldn't need these casts
+ const var_decl = try self.createAttachVarDecl(&block.statements, (?Token)(null),
+ mut_token, (?Token)(null), (?Token)(null));
+ try stack.append(State { .VarDecl = var_decl });
+ continue;
+ }
+ self.putBackToken(mut_token);
+ }
+
+ stack.append(State { .ExpectToken = Token.Id.Semicolon }) catch unreachable;
+ try stack.append(State { .Expression = DestPtr{.List = &block.statements} });
+ continue;
+ },
+
+ // These are data, not control flow.
+ State.InfixOp => unreachable,
+ State.PrefixOp => unreachable,
+ State.Operand => unreachable,
+ }
+ @import("std").debug.panic("{}", @tagName(state));
+ //unreachable;
+ }
+ }
+
+ fn createRoot(self: &Parser) !&ast.NodeRoot {
+ const node = try self.allocator.create(ast.NodeRoot);
+
+ *node = ast.NodeRoot {
+ .base = ast.Node {.id = ast.Node.Id.Root},
+ .decls = ArrayList(&ast.Node).init(self.allocator),
+ };
+ return node;
+ }
+
+ fn createVarDecl(self: &Parser, visib_token: &const ?Token, mut_token: &const Token, comptime_token: &const ?Token,
+ extern_token: &const ?Token) !&ast.NodeVarDecl
+ {
+ const node = try self.allocator.create(ast.NodeVarDecl);
+
+ *node = ast.NodeVarDecl {
+ .base = ast.Node {.id = ast.Node.Id.VarDecl},
+ .visib_token = *visib_token,
+ .mut_token = *mut_token,
+ .comptime_token = *comptime_token,
+ .extern_token = *extern_token,
+ .type_node = null,
+ .align_node = null,
+ .init_node = null,
+ .lib_name = null,
+ // initialized later
+ .name_token = undefined,
+ .eq_token = undefined,
+ };
+ return node;
+ }
+
+ fn createFnProto(self: &Parser, fn_token: &const Token, extern_token: &const ?Token,
+ cc_token: &const ?Token, visib_token: &const ?Token, inline_token: &const ?Token) !&ast.NodeFnProto
+ {
+ const node = try self.allocator.create(ast.NodeFnProto);
+
+ *node = ast.NodeFnProto {
+ .base = ast.Node {.id = ast.Node.Id.FnProto},
+ .visib_token = *visib_token,
+ .name_token = null,
+ .fn_token = *fn_token,
+ .params = ArrayList(&ast.Node).init(self.allocator),
+ .return_type = undefined,
+ .var_args_token = null,
+ .extern_token = *extern_token,
+ .inline_token = *inline_token,
+ .cc_token = *cc_token,
+ .body_node = null,
+ .lib_name = null,
+ .align_expr = null,
+ };
+ return node;
+ }
+
+ fn createParamDecl(self: &Parser) !&ast.NodeParamDecl {
+ const node = try self.allocator.create(ast.NodeParamDecl);
+
+ *node = ast.NodeParamDecl {
+ .base = ast.Node {.id = ast.Node.Id.ParamDecl},
+ .comptime_token = null,
+ .noalias_token = null,
+ .name_token = null,
+ .type_node = undefined,
+ .var_args_token = null,
+ };
+ return node;
+ }
+
+ fn createBlock(self: &Parser, begin_token: &const Token) !&ast.NodeBlock {
+ const node = try self.allocator.create(ast.NodeBlock);
+
+ *node = ast.NodeBlock {
+ .base = ast.Node {.id = ast.Node.Id.Block},
+ .begin_token = *begin_token,
+ .end_token = undefined,
+ .statements = ArrayList(&ast.Node).init(self.allocator),
+ };
+ return node;
+ }
+
+ fn createInfixOp(self: &Parser, op_token: &const Token, op: &const ast.NodeInfixOp.InfixOp) !&ast.NodeInfixOp {
+ const node = try self.allocator.create(ast.NodeInfixOp);
+
+ *node = ast.NodeInfixOp {
+ .base = ast.Node {.id = ast.Node.Id.InfixOp},
+ .op_token = *op_token,
+ .lhs = undefined,
+ .op = *op,
+ .rhs = undefined,
+ };
+ return node;
+ }
+
+ fn createPrefixOp(self: &Parser, op_token: &const Token, op: &const ast.NodePrefixOp.PrefixOp) !&ast.NodePrefixOp {
+ const node = try self.allocator.create(ast.NodePrefixOp);
+
+ *node = ast.NodePrefixOp {
+ .base = ast.Node {.id = ast.Node.Id.PrefixOp},
+ .op_token = *op_token,
+ .op = *op,
+ .rhs = undefined,
+ };
+ return node;
+ }
+
+ fn createIdentifier(self: &Parser, name_token: &const Token) !&ast.NodeIdentifier {
+ const node = try self.allocator.create(ast.NodeIdentifier);
+
+ *node = ast.NodeIdentifier {
+ .base = ast.Node {.id = ast.Node.Id.Identifier},
+ .name_token = *name_token,
+ };
+ return node;
+ }
+
+ fn createIntegerLiteral(self: &Parser, token: &const Token) !&ast.NodeIntegerLiteral {
+ const node = try self.allocator.create(ast.NodeIntegerLiteral);
+
+ *node = ast.NodeIntegerLiteral {
+ .base = ast.Node {.id = ast.Node.Id.IntegerLiteral},
+ .token = *token,
+ };
+ return node;
+ }
+
+ fn createFloatLiteral(self: &Parser, token: &const Token) !&ast.NodeFloatLiteral {
+ const node = try self.allocator.create(ast.NodeFloatLiteral);
+
+ *node = ast.NodeFloatLiteral {
+ .base = ast.Node {.id = ast.Node.Id.FloatLiteral},
+ .token = *token,
+ };
+ return node;
+ }
+
+ fn createAttachIdentifier(self: &Parser, dest_ptr: &const DestPtr, name_token: &const Token) !&ast.NodeIdentifier {
+ const node = try self.createIdentifier(name_token);
+ try dest_ptr.store(&node.base);
+ return node;
+ }
+
+ fn createAttachParamDecl(self: &Parser, list: &ArrayList(&ast.Node)) !&ast.NodeParamDecl {
+ const node = try self.createParamDecl();
+ try list.append(&node.base);
+ return node;
+ }
+
+ fn createAttachFnProto(self: &Parser, list: &ArrayList(&ast.Node), fn_token: &const Token,
+ extern_token: &const ?Token, cc_token: &const ?Token, visib_token: &const ?Token,
+ inline_token: &const ?Token) !&ast.NodeFnProto
+ {
+ const node = try self.createFnProto(fn_token, extern_token, cc_token, visib_token, inline_token);
+ try list.append(&node.base);
+ return node;
+ }
+
+ fn createAttachVarDecl(self: &Parser, list: &ArrayList(&ast.Node), visib_token: &const ?Token,
+ mut_token: &const Token, comptime_token: &const ?Token, extern_token: &const ?Token) !&ast.NodeVarDecl
+ {
+ const node = try self.createVarDecl(visib_token, mut_token, comptime_token, extern_token);
+ try list.append(&node.base);
+ return node;
+ }
+
+ fn parseError(self: &Parser, token: &const Token, comptime fmt: []const u8, args: ...) (error{ParseError}) {
+ const loc = self.tokenizer.getTokenLocation(token);
+ warn("{}:{}:{}: error: " ++ fmt ++ "\n", self.source_file_name, loc.line + 1, loc.column + 1, args);
+ warn("{}\n", self.tokenizer.buffer[loc.line_start..loc.line_end]);
+ {
+ var i: usize = 0;
+ while (i < loc.column) : (i += 1) {
+ warn(" ");
+ }
+ }
+ {
+ const caret_count = token.end - token.start;
+ var i: usize = 0;
+ while (i < caret_count) : (i += 1) {
+ warn("~");
+ }
+ }
+ warn("\n");
+ return error.ParseError;
+ }
+
+ fn expectToken(self: &Parser, token: &const Token, id: @TagType(Token.Id)) !void {
+ if (token.id != id) {
+ return self.parseError(token, "expected {}, found {}", @tagName(id), @tagName(token.id));
+ }
+ }
+
+ fn eatToken(self: &Parser, id: @TagType(Token.Id)) !Token {
+ const token = self.getNextToken();
+ try self.expectToken(token, id);
+ return token;
+ }
+
+ fn putBackToken(self: &Parser, token: &const Token) void {
+ self.put_back_tokens[self.put_back_count] = *token;
+ self.put_back_count += 1;
+ }
+
+ fn getNextToken(self: &Parser) Token {
+ if (self.put_back_count != 0) {
+ const put_back_index = self.put_back_count - 1;
+ const put_back_token = self.put_back_tokens[put_back_index];
+ self.put_back_count = put_back_index;
+ return put_back_token;
+ } else {
+ return self.tokenizer.next();
+ }
+ }
+
+ const RenderAstFrame = struct {
+ node: &ast.Node,
+ indent: usize,
+ };
+
+ pub fn renderAst(self: &Parser, stream: var, root_node: &ast.NodeRoot) !void {
+ var stack = self.initUtilityArrayList(RenderAstFrame);
+ defer self.deinitUtilityArrayList(stack);
+
+ try stack.append(RenderAstFrame {
+ .node = &root_node.base,
+ .indent = 0,
+ });
+
+ while (stack.popOrNull()) |frame| {
+ {
+ var i: usize = 0;
+ while (i < frame.indent) : (i += 1) {
+ try stream.print(" ");
+ }
+ }
+ try stream.print("{}\n", @tagName(frame.node.id));
+ var child_i: usize = 0;
+ while (frame.node.iterate(child_i)) |child| : (child_i += 1) {
+ try stack.append(RenderAstFrame {
+ .node = child,
+ .indent = frame.indent + 2,
+ });
+ }
+ }
+ }
+
+ const RenderState = union(enum) {
+ TopLevelDecl: &ast.Node,
+ FnProtoRParen: &ast.NodeFnProto,
+ ParamDecl: &ast.Node,
+ Text: []const u8,
+ Expression: &ast.Node,
+ VarDecl: &ast.NodeVarDecl,
+ Statement: &ast.Node,
+ PrintIndent,
+ Indent: usize,
+ };
+
+ pub fn renderSource(self: &Parser, stream: var, root_node: &ast.NodeRoot) !void {
+ var stack = self.initUtilityArrayList(RenderState);
+ defer self.deinitUtilityArrayList(stack);
+
+ {
+ var i = root_node.decls.len;
+ while (i != 0) {
+ i -= 1;
+ const decl = root_node.decls.items[i];
+ try stack.append(RenderState {.TopLevelDecl = decl});
+ }
+ }
+
+ const indent_delta = 4;
+ var indent: usize = 0;
+ while (stack.popOrNull()) |state| {
+ switch (state) {
+ RenderState.TopLevelDecl => |decl| {
+ switch (decl.id) {
+ ast.Node.Id.FnProto => {
+ const fn_proto = @fieldParentPtr(ast.NodeFnProto, "base", decl);
+ if (fn_proto.visib_token) |visib_token| {
+ switch (visib_token.id) {
+ Token.Id.Keyword_pub => try stream.print("pub "),
+ Token.Id.Keyword_export => try stream.print("export "),
+ else => unreachable,
+ }
+ }
+ if (fn_proto.extern_token) |extern_token| {
+ try stream.print("{} ", self.tokenizer.getTokenSlice(extern_token));
+ }
+ try stream.print("fn");
+
+ if (fn_proto.name_token) |name_token| {
+ try stream.print(" {}", self.tokenizer.getTokenSlice(name_token));
+ }
+
+ try stream.print("(");
+
+ try stack.append(RenderState { .Text = "\n" });
+ if (fn_proto.body_node == null) {
+ try stack.append(RenderState { .Text = ";" });
+ }
+
+ try stack.append(RenderState { .FnProtoRParen = fn_proto});
+ var i = fn_proto.params.len;
+ while (i != 0) {
+ i -= 1;
+ const param_decl_node = fn_proto.params.items[i];
+ try stack.append(RenderState { .ParamDecl = param_decl_node});
+ if (i != 0) {
+ try stack.append(RenderState { .Text = ", " });
+ }
+ }
+ },
+ ast.Node.Id.VarDecl => {
+ const var_decl = @fieldParentPtr(ast.NodeVarDecl, "base", decl);
+ try stack.append(RenderState { .Text = "\n"});
+ try stack.append(RenderState { .VarDecl = var_decl});
+
+ },
+ else => unreachable,
+ }
+ },
+
+ RenderState.VarDecl => |var_decl| {
+ if (var_decl.visib_token) |visib_token| {
+ try stream.print("{} ", self.tokenizer.getTokenSlice(visib_token));
+ }
+ if (var_decl.extern_token) |extern_token| {
+ try stream.print("{} ", self.tokenizer.getTokenSlice(extern_token));
+ if (var_decl.lib_name != null) {
+ @panic("TODO");
+ }
+ }
+ if (var_decl.comptime_token) |comptime_token| {
+ try stream.print("{} ", self.tokenizer.getTokenSlice(comptime_token));
+ }
+ try stream.print("{} ", self.tokenizer.getTokenSlice(var_decl.mut_token));
+ try stream.print("{}", self.tokenizer.getTokenSlice(var_decl.name_token));
+
+ try stack.append(RenderState { .Text = ";" });
+ if (var_decl.init_node) |init_node| {
+ try stack.append(RenderState { .Expression = init_node });
+ try stack.append(RenderState { .Text = " = " });
+ }
+ if (var_decl.align_node) |align_node| {
+ try stack.append(RenderState { .Text = ")" });
+ try stack.append(RenderState { .Expression = align_node });
+ try stack.append(RenderState { .Text = " align(" });
+ }
+ if (var_decl.type_node) |type_node| {
+ try stream.print(": ");
+ try stack.append(RenderState { .Expression = type_node });
+ }
+ },
+
+ RenderState.ParamDecl => |base| {
+ const param_decl = @fieldParentPtr(ast.NodeParamDecl, "base", base);
+ if (param_decl.comptime_token) |comptime_token| {
+ try stream.print("{} ", self.tokenizer.getTokenSlice(comptime_token));
+ }
+ if (param_decl.noalias_token) |noalias_token| {
+ try stream.print("{} ", self.tokenizer.getTokenSlice(noalias_token));
+ }
+ if (param_decl.name_token) |name_token| {
+ try stream.print("{}: ", self.tokenizer.getTokenSlice(name_token));
+ }
+ if (param_decl.var_args_token) |var_args_token| {
+ try stream.print("{}", self.tokenizer.getTokenSlice(var_args_token));
+ } else {
+ try stack.append(RenderState { .Expression = param_decl.type_node});
+ }
+ },
+ RenderState.Text => |bytes| {
+ try stream.write(bytes);
+ },
+ RenderState.Expression => |base| switch (base.id) {
+ ast.Node.Id.Identifier => {
+ const identifier = @fieldParentPtr(ast.NodeIdentifier, "base", base);
+ try stream.print("{}", self.tokenizer.getTokenSlice(identifier.name_token));
+ },
+ ast.Node.Id.Block => {
+ const block = @fieldParentPtr(ast.NodeBlock, "base", base);
+ try stream.write("{");
+ try stack.append(RenderState { .Text = "}"});
+ try stack.append(RenderState.PrintIndent);
+ try stack.append(RenderState { .Indent = indent});
+ try stack.append(RenderState { .Text = "\n"});
+ var i = block.statements.len;
+ while (i != 0) {
+ i -= 1;
+ const statement_node = block.statements.items[i];
+ try stack.append(RenderState { .Statement = statement_node});
+ try stack.append(RenderState.PrintIndent);
+ try stack.append(RenderState { .Indent = indent + indent_delta});
+ try stack.append(RenderState { .Text = "\n" });
+ }
+ },
+ ast.Node.Id.InfixOp => {
+ const prefix_op_node = @fieldParentPtr(ast.NodeInfixOp, "base", base);
+ try stack.append(RenderState { .Expression = prefix_op_node.rhs });
+ switch (prefix_op_node.op) {
+ ast.NodeInfixOp.InfixOp.EqualEqual => {
+ try stack.append(RenderState { .Text = " == "});
+ },
+ ast.NodeInfixOp.InfixOp.BangEqual => {
+ try stack.append(RenderState { .Text = " != "});
+ },
+ else => unreachable,
+ }
+ try stack.append(RenderState { .Expression = prefix_op_node.lhs });
+ },
+ ast.Node.Id.PrefixOp => {
+ const prefix_op_node = @fieldParentPtr(ast.NodePrefixOp, "base", base);
+ try stack.append(RenderState { .Expression = prefix_op_node.rhs });
+ switch (prefix_op_node.op) {
+ ast.NodePrefixOp.PrefixOp.Return => {
+ try stream.write("return ");
+ },
+ ast.NodePrefixOp.PrefixOp.AddrOf => |addr_of_info| {
+ try stream.write("&");
+ if (addr_of_info.volatile_token != null) {
+ try stack.append(RenderState { .Text = "volatile "});
+ }
+ if (addr_of_info.const_token != null) {
+ try stack.append(RenderState { .Text = "const "});
+ }
+ if (addr_of_info.align_expr) |align_expr| {
+ try stream.print("align(");
+ try stack.append(RenderState { .Text = ") "});
+ try stack.append(RenderState { .Expression = align_expr});
+ }
+ },
+ else => unreachable,
+ }
+ },
+ ast.Node.Id.IntegerLiteral => {
+ const integer_literal = @fieldParentPtr(ast.NodeIntegerLiteral, "base", base);
+ try stream.print("{}", self.tokenizer.getTokenSlice(integer_literal.token));
+ },
+ ast.Node.Id.FloatLiteral => {
+ const float_literal = @fieldParentPtr(ast.NodeFloatLiteral, "base", base);
+ try stream.print("{}", self.tokenizer.getTokenSlice(float_literal.token));
+ },
+ else => unreachable,
+ },
+ RenderState.FnProtoRParen => |fn_proto| {
+ try stream.print(")");
+ if (fn_proto.align_expr != null) {
+ @panic("TODO");
+ }
+ try stream.print(" ");
+ if (fn_proto.body_node) |body_node| {
+ try stack.append(RenderState { .Expression = body_node});
+ try stack.append(RenderState { .Text = " "});
+ }
+ try stack.append(RenderState { .Expression = fn_proto.return_type});
+ },
+ RenderState.Statement => |base| {
+ switch (base.id) {
+ ast.Node.Id.VarDecl => {
+ const var_decl = @fieldParentPtr(ast.NodeVarDecl, "base", base);
+ try stack.append(RenderState { .VarDecl = var_decl});
+ },
+ else => {
+ try stack.append(RenderState { .Text = ";"});
+ try stack.append(RenderState { .Expression = base});
+ },
+ }
+ },
+ RenderState.Indent => |new_indent| indent = new_indent,
+ RenderState.PrintIndent => try stream.writeByteNTimes(' ', indent),
+ }
+ }
+ }
+
+ fn initUtilityArrayList(self: &Parser, comptime T: type) ArrayList(T) {
+ const new_byte_count = self.utility_bytes.len - self.utility_bytes.len % @sizeOf(T);
+ self.utility_bytes = self.allocator.alignedShrink(u8, utility_bytes_align, self.utility_bytes, new_byte_count);
+ const typed_slice = ([]T)(self.utility_bytes);
+ return ArrayList(T) {
+ .allocator = self.allocator,
+ .items = typed_slice,
+ .len = 0,
+ };
+ }
+
+ fn deinitUtilityArrayList(self: &Parser, list: var) void {
+ self.utility_bytes = ([]align(utility_bytes_align) u8)(list.items);
+ }
+
+};
+
+var fixed_buffer_mem: [100 * 1024]u8 = undefined;
+
+fn testParse(source: []const u8, allocator: &mem.Allocator) ![]u8 {
+ var padded_source: [0x100]u8 = undefined;
+ std.mem.copy(u8, padded_source[0..source.len], source);
+
+ var tokenizer = Tokenizer.init(padded_source[0..source.len]);
+ var parser = Parser.init(&tokenizer, allocator, "(memory buffer)");
+ defer parser.deinit();
+
+ const tree = try parser.parse();
+ defer tree.deinit();
+
+ var buffer = try std.Buffer.initSize(allocator, 0);
+ var buffer_out_stream = io.BufferOutStream.init(&buffer);
+ try parser.renderSource(&buffer_out_stream.stream, tree.root_node);
+ return buffer.toOwnedSlice();
+}
+
+// TODO test for memory leaks
+// TODO test for valid frees
+fn testCanonical(source: []const u8) !void {
+ const needed_alloc_count = x: {
+ // Try it once with unlimited memory, make sure it works
+ var fixed_allocator = mem.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
+ var failing_allocator = std.debug.FailingAllocator.init(&fixed_allocator.allocator, @maxValue(usize));
+ const result_source = try testParse(source, &failing_allocator.allocator);
+ if (!mem.eql(u8, result_source, source)) {
+ warn("\n====== expected this output: =========\n");
+ warn("{}", source);
+ warn("\n======== instead found this: =========\n");
+ warn("{}", result_source);
+ warn("\n======================================\n");
+ return error.TestFailed;
+ }
+ failing_allocator.allocator.free(result_source);
+ break :x failing_allocator.index;
+ };
+
+ var fail_index: usize = 0;
+ while (fail_index < needed_alloc_count) : (fail_index += 1) {
+ var fixed_allocator = mem.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
+ var failing_allocator = std.debug.FailingAllocator.init(&fixed_allocator.allocator, fail_index);
+ if (testParse(source, &failing_allocator.allocator)) |_| {
+ return error.NondeterministicMemoryUsage;
+ } else |err| switch (err) {
+ error.OutOfMemory => {
+ // TODO make this pass
+ //if (failing_allocator.allocated_bytes != failing_allocator.freed_bytes) {
+ // warn("\nfail_index: {}/{}\nallocated bytes: {}\nfreed bytes: {}\nallocations: {}\ndeallocations: {}\n",
+ // fail_index, needed_alloc_count,
+ // failing_allocator.allocated_bytes, failing_allocator.freed_bytes,
+ // failing_allocator.index, failing_allocator.deallocations);
+ // return error.MemoryLeakDetected;
+ //}
+ },
+ error.ParseError => @panic("test failed"),
+ }
+ }
+}
+
+test "zig fmt" {
+ try testCanonical(
+ \\extern fn puts(s: &const u8) c_int;
+ \\
+ );
+
+ try testCanonical(
+ \\const a = b;
+ \\pub const a = b;
+ \\var a = b;
+ \\pub var a = b;
+ \\const a: i32 = b;
+ \\pub const a: i32 = b;
+ \\var a: i32 = b;
+ \\pub var a: i32 = b;
+ \\
+ );
+
+ try testCanonical(
+ \\extern var foo: c_int;
+ \\
+ );
+
+ try testCanonical(
+ \\var foo: c_int align(1);
+ \\
+ );
+
+ try testCanonical(
+ \\fn main(argc: c_int, argv: &&u8) c_int {
+ \\ const a = b;
+ \\}
+ \\
+ );
+
+ try testCanonical(
+ \\fn foo(argc: c_int, argv: &&u8) c_int {
+ \\ return 0;
+ \\}
+ \\
+ );
+
+ try testCanonical(
+ \\extern fn f1(s: &align(&u8) u8) c_int;
+ \\
+ );
+
+ try testCanonical(
+ \\extern fn f1(s: &&align(1) &const &volatile u8) c_int;
+ \\extern fn f2(s: &align(1) const &align(1) volatile &const volatile u8) c_int;
+ \\extern fn f3(s: &align(1) const volatile u8) c_int;
+ \\
+ );
+
+ try testCanonical(
+ \\fn f1(a: bool, b: bool) bool {
+ \\ a != b;
+ \\ return a == b;
+ \\}
+ \\
+ );
+}
diff --git a/std/zig/tokenizer.zig b/std/zig/tokenizer.zig
new file mode 100644
index 0000000000..546356caa3
--- /dev/null
+++ b/std/zig/tokenizer.zig
@@ -0,0 +1,655 @@
+const std = @import("../index.zig");
+const mem = std.mem;
+
+pub const Token = struct {
+ id: Id,
+ start: usize,
+ end: usize,
+
+ const KeywordId = struct {
+ bytes: []const u8,
+ id: Id,
+ };
+
+ const keywords = []KeywordId {
+ KeywordId{.bytes="align", .id = Id.Keyword_align},
+ KeywordId{.bytes="and", .id = Id.Keyword_and},
+ KeywordId{.bytes="asm", .id = Id.Keyword_asm},
+ KeywordId{.bytes="break", .id = Id.Keyword_break},
+ KeywordId{.bytes="comptime", .id = Id.Keyword_comptime},
+ KeywordId{.bytes="const", .id = Id.Keyword_const},
+ KeywordId{.bytes="continue", .id = Id.Keyword_continue},
+ KeywordId{.bytes="defer", .id = Id.Keyword_defer},
+ KeywordId{.bytes="else", .id = Id.Keyword_else},
+ KeywordId{.bytes="enum", .id = Id.Keyword_enum},
+ KeywordId{.bytes="error", .id = Id.Keyword_error},
+ KeywordId{.bytes="export", .id = Id.Keyword_export},
+ KeywordId{.bytes="extern", .id = Id.Keyword_extern},
+ KeywordId{.bytes="false", .id = Id.Keyword_false},
+ KeywordId{.bytes="fn", .id = Id.Keyword_fn},
+ KeywordId{.bytes="for", .id = Id.Keyword_for},
+ KeywordId{.bytes="goto", .id = Id.Keyword_goto},
+ KeywordId{.bytes="if", .id = Id.Keyword_if},
+ KeywordId{.bytes="inline", .id = Id.Keyword_inline},
+ KeywordId{.bytes="nakedcc", .id = Id.Keyword_nakedcc},
+ KeywordId{.bytes="noalias", .id = Id.Keyword_noalias},
+ KeywordId{.bytes="null", .id = Id.Keyword_null},
+ KeywordId{.bytes="or", .id = Id.Keyword_or},
+ KeywordId{.bytes="packed", .id = Id.Keyword_packed},
+ KeywordId{.bytes="pub", .id = Id.Keyword_pub},
+ KeywordId{.bytes="return", .id = Id.Keyword_return},
+ KeywordId{.bytes="stdcallcc", .id = Id.Keyword_stdcallcc},
+ KeywordId{.bytes="struct", .id = Id.Keyword_struct},
+ KeywordId{.bytes="switch", .id = Id.Keyword_switch},
+ KeywordId{.bytes="test", .id = Id.Keyword_test},
+ KeywordId{.bytes="this", .id = Id.Keyword_this},
+ KeywordId{.bytes="true", .id = Id.Keyword_true},
+ KeywordId{.bytes="undefined", .id = Id.Keyword_undefined},
+ KeywordId{.bytes="union", .id = Id.Keyword_union},
+ KeywordId{.bytes="unreachable", .id = Id.Keyword_unreachable},
+ KeywordId{.bytes="use", .id = Id.Keyword_use},
+ KeywordId{.bytes="var", .id = Id.Keyword_var},
+ KeywordId{.bytes="volatile", .id = Id.Keyword_volatile},
+ KeywordId{.bytes="while", .id = Id.Keyword_while},
+ };
+
+ fn getKeyword(bytes: []const u8) ?Id {
+ for (keywords) |kw| {
+ if (mem.eql(u8, kw.bytes, bytes)) {
+ return kw.id;
+ }
+ }
+ return null;
+ }
+
+ const StrLitKind = enum {Normal, C};
+
+ pub const Id = union(enum) {
+ Invalid,
+ Identifier,
+ StringLiteral: StrLitKind,
+ Eof,
+ Builtin,
+ Bang,
+ Equal,
+ EqualEqual,
+ BangEqual,
+ LParen,
+ RParen,
+ Semicolon,
+ Percent,
+ LBrace,
+ RBrace,
+ Period,
+ Ellipsis2,
+ Ellipsis3,
+ Minus,
+ Arrow,
+ Colon,
+ Slash,
+ Comma,
+ Ampersand,
+ AmpersandEqual,
+ IntegerLiteral,
+ FloatLiteral,
+ Keyword_align,
+ Keyword_and,
+ Keyword_asm,
+ Keyword_break,
+ Keyword_comptime,
+ Keyword_const,
+ Keyword_continue,
+ Keyword_defer,
+ Keyword_else,
+ Keyword_enum,
+ Keyword_error,
+ Keyword_export,
+ Keyword_extern,
+ Keyword_false,
+ Keyword_fn,
+ Keyword_for,
+ Keyword_goto,
+ Keyword_if,
+ Keyword_inline,
+ Keyword_nakedcc,
+ Keyword_noalias,
+ Keyword_null,
+ Keyword_or,
+ Keyword_packed,
+ Keyword_pub,
+ Keyword_return,
+ Keyword_stdcallcc,
+ Keyword_struct,
+ Keyword_switch,
+ Keyword_test,
+ Keyword_this,
+ Keyword_true,
+ Keyword_undefined,
+ Keyword_union,
+ Keyword_unreachable,
+ Keyword_use,
+ Keyword_var,
+ Keyword_volatile,
+ Keyword_while,
+ };
+};
+
+pub const Tokenizer = struct {
+ buffer: []const u8,
+ index: usize,
+ pending_invalid_token: ?Token,
+
+ pub const Location = struct {
+ line: usize,
+ column: usize,
+ line_start: usize,
+ line_end: usize,
+ };
+
+ pub fn getTokenLocation(self: &Tokenizer, token: &const Token) Location {
+ var loc = Location {
+ .line = 0,
+ .column = 0,
+ .line_start = 0,
+ .line_end = 0,
+ };
+ for (self.buffer) |c, i| {
+ if (i == token.start) {
+ loc.line_end = i;
+ while (loc.line_end < self.buffer.len and self.buffer[loc.line_end] != '\n') : (loc.line_end += 1) {}
+ return loc;
+ }
+ if (c == '\n') {
+ loc.line += 1;
+ loc.column = 0;
+ loc.line_start = i + 1;
+ } else {
+ loc.column += 1;
+ }
+ }
+ return loc;
+ }
+
+ /// For debugging purposes
+ pub fn dump(self: &Tokenizer, token: &const Token) void {
+ std.debug.warn("{} \"{}\"\n", @tagName(token.id), self.buffer[token.start..token.end]);
+ }
+
+ pub fn init(buffer: []const u8) Tokenizer {
+ return Tokenizer {
+ .buffer = buffer,
+ .index = 0,
+ .pending_invalid_token = null,
+ };
+ }
+
+ const State = enum {
+ Start,
+ Identifier,
+ Builtin,
+ C,
+ StringLiteral,
+ StringLiteralBackslash,
+ Equal,
+ Bang,
+ Minus,
+ Slash,
+ LineComment,
+ Zero,
+ IntegerLiteral,
+ IntegerLiteralWithRadix,
+ NumberDot,
+ FloatFraction,
+ FloatExponentUnsigned,
+ FloatExponentNumber,
+ Ampersand,
+ Period,
+ Period2,
+ };
+
+ pub fn next(self: &Tokenizer) Token {
+ if (self.pending_invalid_token) |token| {
+ self.pending_invalid_token = null;
+ return token;
+ }
+ var state = State.Start;
+ var result = Token {
+ .id = Token.Id.Eof,
+ .start = self.index,
+ .end = undefined,
+ };
+ while (self.index < self.buffer.len) : (self.index += 1) {
+ const c = self.buffer[self.index];
+ switch (state) {
+ State.Start => switch (c) {
+ ' ', '\n' => {
+ result.start = self.index + 1;
+ },
+ 'c' => {
+ state = State.C;
+ result.id = Token.Id.Identifier;
+ },
+ '"' => {
+ state = State.StringLiteral;
+ result.id = Token.Id { .StringLiteral = Token.StrLitKind.Normal };
+ },
+ 'a'...'b', 'd'...'z', 'A'...'Z', '_' => {
+ state = State.Identifier;
+ result.id = Token.Id.Identifier;
+ },
+ '@' => {
+ state = State.Builtin;
+ result.id = Token.Id.Builtin;
+ },
+ '=' => {
+ state = State.Equal;
+ },
+ '!' => {
+ state = State.Bang;
+ },
+ '(' => {
+ result.id = Token.Id.LParen;
+ self.index += 1;
+ break;
+ },
+ ')' => {
+ result.id = Token.Id.RParen;
+ self.index += 1;
+ break;
+ },
+ ';' => {
+ result.id = Token.Id.Semicolon;
+ self.index += 1;
+ break;
+ },
+ ',' => {
+ result.id = Token.Id.Comma;
+ self.index += 1;
+ break;
+ },
+ ':' => {
+ result.id = Token.Id.Colon;
+ self.index += 1;
+ break;
+ },
+ '%' => {
+ result.id = Token.Id.Percent;
+ self.index += 1;
+ break;
+ },
+ '{' => {
+ result.id = Token.Id.LBrace;
+ self.index += 1;
+ break;
+ },
+ '}' => {
+ result.id = Token.Id.RBrace;
+ self.index += 1;
+ break;
+ },
+ '.' => {
+ state = State.Period;
+ },
+ '-' => {
+ state = State.Minus;
+ },
+ '/' => {
+ state = State.Slash;
+ },
+ '&' => {
+ state = State.Ampersand;
+ },
+ '0' => {
+ state = State.Zero;
+ result.id = Token.Id.IntegerLiteral;
+ },
+ '1'...'9' => {
+ state = State.IntegerLiteral;
+ result.id = Token.Id.IntegerLiteral;
+ },
+ else => {
+ result.id = Token.Id.Invalid;
+ self.index += 1;
+ break;
+ },
+ },
+ State.Ampersand => switch (c) {
+ '=' => {
+ result.id = Token.Id.AmpersandEqual;
+ self.index += 1;
+ break;
+ },
+ else => {
+ result.id = Token.Id.Ampersand;
+ break;
+ },
+ },
+ State.Identifier => switch (c) {
+ 'a'...'z', 'A'...'Z', '_', '0'...'9' => {},
+ else => {
+ if (Token.getKeyword(self.buffer[result.start..self.index])) |id| {
+ result.id = id;
+ }
+ break;
+ },
+ },
+ State.Builtin => switch (c) {
+ 'a'...'z', 'A'...'Z', '_', '0'...'9' => {},
+ else => break,
+ },
+ State.C => switch (c) {
+ '\\' => @panic("TODO"),
+ '"' => {
+ state = State.StringLiteral;
+ result.id = Token.Id { .StringLiteral = Token.StrLitKind.C };
+ },
+ 'a'...'z', 'A'...'Z', '_', '0'...'9' => {
+ state = State.Identifier;
+ },
+ else => break,
+ },
+ State.StringLiteral => switch (c) {
+ '\\' => {
+ state = State.StringLiteralBackslash;
+ },
+ '"' => {
+ self.index += 1;
+ break;
+ },
+ '\n' => break, // Look for this error later.
+ else => self.checkLiteralCharacter(),
+ },
+
+ State.StringLiteralBackslash => switch (c) {
+ '\n' => break, // Look for this error later.
+ else => {
+ state = State.StringLiteral;
+ },
+ },
+
+ State.Bang => switch (c) {
+ '=' => {
+ result.id = Token.Id.BangEqual;
+ self.index += 1;
+ break;
+ },
+ else => {
+ result.id = Token.Id.Bang;
+ break;
+ },
+ },
+
+ State.Equal => switch (c) {
+ '=' => {
+ result.id = Token.Id.EqualEqual;
+ self.index += 1;
+ break;
+ },
+ else => {
+ result.id = Token.Id.Equal;
+ break;
+ },
+ },
+
+ State.Minus => switch (c) {
+ '>' => {
+ result.id = Token.Id.Arrow;
+ self.index += 1;
+ break;
+ },
+ else => {
+ result.id = Token.Id.Minus;
+ break;
+ },
+ },
+
+ State.Period => switch (c) {
+ '.' => {
+ state = State.Period2;
+ },
+ else => {
+ result.id = Token.Id.Period;
+ break;
+ },
+ },
+
+ State.Period2 => switch (c) {
+ '.' => {
+ result.id = Token.Id.Ellipsis3;
+ self.index += 1;
+ break;
+ },
+ else => {
+ result.id = Token.Id.Ellipsis2;
+ break;
+ },
+ },
+
+ State.Slash => switch (c) {
+ '/' => {
+ result.id = undefined;
+ state = State.LineComment;
+ },
+ else => {
+ result.id = Token.Id.Slash;
+ break;
+ },
+ },
+ State.LineComment => switch (c) {
+ '\n' => {
+ state = State.Start;
+ result = Token {
+ .id = Token.Id.Eof,
+ .start = self.index + 1,
+ .end = undefined,
+ };
+ },
+ else => self.checkLiteralCharacter(),
+ },
+ State.Zero => switch (c) {
+ 'b', 'o', 'x' => {
+ state = State.IntegerLiteralWithRadix;
+ },
+ else => {
+ // reinterpret as a normal number
+ self.index -= 1;
+ state = State.IntegerLiteral;
+ },
+ },
+ State.IntegerLiteral => switch (c) {
+ '.' => {
+ state = State.NumberDot;
+ },
+ 'p', 'P', 'e', 'E' => {
+ state = State.FloatExponentUnsigned;
+ },
+ '0'...'9' => {},
+ else => break,
+ },
+ State.IntegerLiteralWithRadix => switch (c) {
+ '.' => {
+ state = State.NumberDot;
+ },
+ 'p', 'P' => {
+ state = State.FloatExponentUnsigned;
+ },
+ '0'...'9', 'a'...'f', 'A'...'F' => {},
+ else => break,
+ },
+ State.NumberDot => switch (c) {
+ '.' => {
+ self.index -= 1;
+ state = State.Start;
+ break;
+ },
+ else => {
+ self.index -= 1;
+ result.id = Token.Id.FloatLiteral;
+ state = State.FloatFraction;
+ },
+ },
+ State.FloatFraction => switch (c) {
+ 'p', 'P' => {
+ state = State.FloatExponentUnsigned;
+ },
+ '0'...'9', 'a'...'f', 'A'...'F' => {},
+ else => break,
+ },
+ State.FloatExponentUnsigned => switch (c) {
+ '+', '-' => {
+ state = State.FloatExponentNumber;
+ },
+ else => {
+ // reinterpret as a normal exponent number
+ self.index -= 1;
+ state = State.FloatExponentNumber;
+ }
+ },
+ State.FloatExponentNumber => switch (c) {
+ '0'...'9', 'a'...'f', 'A'...'F' => {},
+ else => break,
+ },
+ }
+ }
+ result.end = self.index;
+
+ if (result.id == Token.Id.Eof) {
+ if (self.pending_invalid_token) |token| {
+ self.pending_invalid_token = null;
+ return token;
+ }
+ }
+
+ return result;
+ }
+
+ pub fn getTokenSlice(self: &const Tokenizer, token: &const Token) []const u8 {
+ return self.buffer[token.start..token.end];
+ }
+
+ fn checkLiteralCharacter(self: &Tokenizer) void {
+ if (self.pending_invalid_token != null) return;
+ const invalid_length = self.getInvalidCharacterLength();
+ if (invalid_length == 0) return;
+ self.pending_invalid_token = Token {
+ .id = Token.Id.Invalid,
+ .start = self.index,
+ .end = self.index + invalid_length,
+ };
+ }
+
+ fn getInvalidCharacterLength(self: &Tokenizer) u3 {
+ const c0 = self.buffer[self.index];
+ if (c0 < 0x80) {
+ if (c0 < 0x20 or c0 == 0x7f) {
+ // ascii control codes are never allowed
+ // (note that \n was checked before we got here)
+ return 1;
+ }
+ // looks fine to me.
+ return 0;
+ } else {
+ // check utf8-encoded character.
+ const length = std.unicode.utf8ByteSequenceLength(c0) catch return 1;
+ if (self.index + length >= self.buffer.len) {
+ return u3(self.buffer.len - self.index);
+ }
+ const bytes = self.buffer[self.index..self.index + length];
+ switch (length) {
+ 2 => {
+ const value = std.unicode.utf8Decode2(bytes) catch return length;
+ if (value == 0x85) return length; // U+0085 (NEL)
+ },
+ 3 => {
+ const value = std.unicode.utf8Decode3(bytes) catch return length;
+ if (value == 0x2028) return length; // U+2028 (LS)
+ if (value == 0x2029) return length; // U+2029 (PS)
+ },
+ 4 => {
+ _ = std.unicode.utf8Decode4(bytes) catch return length;
+ },
+ else => unreachable,
+ }
+ self.index += length - 1;
+ return 0;
+ }
+ }
+};
+
+
+
+test "tokenizer" {
+ testTokenize("test", []Token.Id {
+ Token.Id.Keyword_test,
+ });
+}
+
+test "tokenizer - invalid token characters" {
+ testTokenize("#", []Token.Id{Token.Id.Invalid});
+ testTokenize("`", []Token.Id{Token.Id.Invalid});
+}
+
+test "tokenizer - invalid literal/comment characters" {
+ testTokenize("\"\x00\"", []Token.Id {
+ Token.Id { .StringLiteral = Token.StrLitKind.Normal },
+ Token.Id.Invalid,
+ });
+ testTokenize("//\x00", []Token.Id {
+ Token.Id.Invalid,
+ });
+ testTokenize("//\x1f", []Token.Id {
+ Token.Id.Invalid,
+ });
+ testTokenize("//\x7f", []Token.Id {
+ Token.Id.Invalid,
+ });
+}
+
+test "tokenizer - utf8" {
+ testTokenize("//\xc2\x80", []Token.Id{});
+ testTokenize("//\xf4\x8f\xbf\xbf", []Token.Id{});
+}
+
+test "tokenizer - invalid utf8" {
+ testTokenize("//\x80", []Token.Id{Token.Id.Invalid});
+ testTokenize("//\xbf", []Token.Id{Token.Id.Invalid});
+ testTokenize("//\xf8", []Token.Id{Token.Id.Invalid});
+ testTokenize("//\xff", []Token.Id{Token.Id.Invalid});
+ testTokenize("//\xc2\xc0", []Token.Id{Token.Id.Invalid});
+ testTokenize("//\xe0", []Token.Id{Token.Id.Invalid});
+ testTokenize("//\xf0", []Token.Id{Token.Id.Invalid});
+ testTokenize("//\xf0\x90\x80\xc0", []Token.Id{Token.Id.Invalid});
+}
+
+test "tokenizer - illegal unicode codepoints" {
+ // unicode newline characters.U+0085, U+2028, U+2029
+ testTokenize("//\xc2\x84", []Token.Id{});
+ testTokenize("//\xc2\x85", []Token.Id{Token.Id.Invalid});
+ testTokenize("//\xc2\x86", []Token.Id{});
+ testTokenize("//\xe2\x80\xa7", []Token.Id{});
+ testTokenize("//\xe2\x80\xa8", []Token.Id{Token.Id.Invalid});
+ testTokenize("//\xe2\x80\xa9", []Token.Id{Token.Id.Invalid});
+ testTokenize("//\xe2\x80\xaa", []Token.Id{});
+}
+
+fn testTokenize(source: []const u8, expected_tokens: []const Token.Id) void {
+ // (test authors, just make this bigger if you need it)
+ var padded_source: [0x100]u8 = undefined;
+ std.mem.copy(u8, padded_source[0..source.len], source);
+ padded_source[source.len + 0] = '\n';
+ padded_source[source.len + 1] = '\n';
+ padded_source[source.len + 2] = '\n';
+
+ var tokenizer = Tokenizer.init(padded_source[0..source.len + 3]);
+ for (expected_tokens) |expected_token_id| {
+ const token = tokenizer.next();
+ std.debug.assert(@TagType(Token.Id)(token.id) == @TagType(Token.Id)(expected_token_id));
+ switch (expected_token_id) {
+ Token.Id.StringLiteral => |expected_kind| {
+ std.debug.assert(expected_kind == switch (token.id) { Token.Id.StringLiteral => |kind| kind, else => unreachable });
+ },
+ else => {},
+ }
+ }
+ std.debug.assert(tokenizer.next().id == Token.Id.Eof);
+}