diff options
| author | Andrew Kelley <superjoe30@gmail.com> | 2018-02-11 23:49:20 -0500 |
|---|---|---|
| committer | Andrew Kelley <superjoe30@gmail.com> | 2018-02-11 23:49:20 -0500 |
| commit | ef6260b3a7ed09e5dc5d8383ad20f229411bd9ff (patch) | |
| tree | 23f2b5396a95cae6c730cdfafb7dfcdba7eca8f2 /std | |
| parent | 5d9e3cb77f864ccdbcae43329e13a9c8e1f8494e (diff) | |
| parent | f2d601661d286b135293373a83ce1a8628272379 (diff) | |
| download | zig-ef6260b3a7ed09e5dc5d8383ad20f229411bd9ff.tar.gz zig-ef6260b3a7ed09e5dc5d8383ad20f229411bd9ff.zip | |
Merge remote-tracking branch 'origin/master' into llvm6
Diffstat (limited to 'std')
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 = ¶m_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); +} |
