diff --git a/CMakeLists.txt b/CMakeLists.txt index b75876fd24..8906798c69 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -521,6 +521,7 @@ install(FILES "${CMAKE_SOURCE_DIR}/std/fmt/errol/index.zig" DESTINATION "${ZIG_S install(FILES "${CMAKE_SOURCE_DIR}/std/fmt/errol/lookup.zig" DESTINATION "${ZIG_STD_DEST}/fmt/errol") install(FILES "${CMAKE_SOURCE_DIR}/std/fmt/index.zig" DESTINATION "${ZIG_STD_DEST}/fmt") install(FILES "${CMAKE_SOURCE_DIR}/std/hash_map.zig" DESTINATION "${ZIG_STD_DEST}") +install(FILES "${CMAKE_SOURCE_DIR}/std/heap.zig" DESTINATION "${ZIG_STD_DEST}") install(FILES "${CMAKE_SOURCE_DIR}/std/index.zig" DESTINATION "${ZIG_STD_DEST}") install(FILES "${CMAKE_SOURCE_DIR}/std/io.zig" DESTINATION "${ZIG_STD_DEST}") install(FILES "${CMAKE_SOURCE_DIR}/std/linked_list.zig" DESTINATION "${ZIG_STD_DEST}") @@ -605,10 +606,10 @@ install(FILES "${CMAKE_SOURCE_DIR}/std/special/compiler_rt/fixunstfdi.zig" DESTI install(FILES "${CMAKE_SOURCE_DIR}/std/special/compiler_rt/fixunstfsi.zig" DESTINATION "${ZIG_STD_DEST}/special/compiler_rt") install(FILES "${CMAKE_SOURCE_DIR}/std/special/compiler_rt/fixunstfti.zig" DESTINATION "${ZIG_STD_DEST}/special/compiler_rt") install(FILES "${CMAKE_SOURCE_DIR}/std/special/compiler_rt/index.zig" DESTINATION "${ZIG_STD_DEST}/special/compiler_rt") -install(FILES "${CMAKE_SOURCE_DIR}/std/special/compiler_rt/udivti3.zig" DESTINATION "${ZIG_STD_DEST}/special/compiler_rt") install(FILES "${CMAKE_SOURCE_DIR}/std/special/compiler_rt/udivmod.zig" DESTINATION "${ZIG_STD_DEST}/special/compiler_rt") install(FILES "${CMAKE_SOURCE_DIR}/std/special/compiler_rt/udivmoddi4.zig" DESTINATION "${ZIG_STD_DEST}/special/compiler_rt") install(FILES "${CMAKE_SOURCE_DIR}/std/special/compiler_rt/udivmodti4.zig" DESTINATION "${ZIG_STD_DEST}/special/compiler_rt") +install(FILES "${CMAKE_SOURCE_DIR}/std/special/compiler_rt/udivti3.zig" DESTINATION "${ZIG_STD_DEST}/special/compiler_rt") install(FILES "${CMAKE_SOURCE_DIR}/std/special/compiler_rt/umodti3.zig" DESTINATION "${ZIG_STD_DEST}/special/compiler_rt") install(FILES "${CMAKE_SOURCE_DIR}/std/special/panic.zig" DESTINATION "${ZIG_STD_DEST}/special") install(FILES "${CMAKE_SOURCE_DIR}/std/special/test_runner.zig" DESTINATION "${ZIG_STD_DEST}/special") diff --git a/example/cat/main.zig b/example/cat/main.zig index ce72000ffc..de23a16445 100644 --- a/example/cat/main.zig +++ b/example/cat/main.zig @@ -2,47 +2,52 @@ const std = @import("std"); const io = std.io; const mem = std.mem; const os = std.os; +const warn = std.debug.warn; pub fn main() -> %void { const allocator = &std.debug.global_allocator; var args_it = os.args(); const exe = %return unwrapArg(??args_it.next(allocator)); var catted_anything = false; + var stdout_file = %return io.getStdOut(); + const stdout = &stdout_file.out_stream; + while (args_it.next(allocator)) |arg_or_err| { const arg = %return unwrapArg(arg_or_err); if (mem.eql(u8, arg, "-")) { catted_anything = true; - %return cat_stream(&io.stdin); + var stdin_file = %return io.getStdIn(); + %return cat_stream(stdout, &stdin_file.in_stream); } else if (arg[0] == '-') { return usage(exe); } else { - var is = io.InStream.open(arg, null) %% |err| { - %%io.stderr.printf("Unable to open file: {}\n", @errorName(err)); + var file = io.File.openRead(arg, null) %% |err| { + warn("Unable to open file: {}\n", @errorName(err)); return err; }; - defer is.close(); + defer file.close(); catted_anything = true; - %return cat_stream(&is); + %return cat_stream(stdout, &file.in_stream); } } if (!catted_anything) { - %return cat_stream(&io.stdin); + var stdin_file = %return io.getStdIn(); + %return cat_stream(stdout, &stdin_file.in_stream); } - %return io.stdout.flush(); } fn usage(exe: []const u8) -> %void { - %%io.stderr.printf("Usage: {} [FILE]...\n", exe); + warn("Usage: {} [FILE]...\n", exe); return error.Invalid; } -fn cat_stream(is: &io.InStream) -> %void { +fn cat_stream(stdout: &io.OutStream, is: &io.InStream) -> %void { var buf: [1024 * 4]u8 = undefined; while (true) { const bytes_read = is.read(buf[0..]) %% |err| { - %%io.stderr.printf("Unable to read from stream: {}\n", @errorName(err)); + warn("Unable to read from stream: {}\n", @errorName(err)); return err; }; @@ -50,8 +55,8 @@ fn cat_stream(is: &io.InStream) -> %void { break; } - io.stdout.write(buf[0..bytes_read]) %% |err| { - %%io.stderr.printf("Unable to write to stdout: {}\n", @errorName(err)); + stdout.write(buf[0..bytes_read]) %% |err| { + warn("Unable to write to stdout: {}\n", @errorName(err)); return err; }; } @@ -59,7 +64,7 @@ fn cat_stream(is: &io.InStream) -> %void { fn unwrapArg(arg: %[]u8) -> %[]u8 { return arg %% |err| { - %%io.stderr.printf("Unable to parse command line: {}\n", err); + warn("Unable to parse command line: {}\n", err); return err; }; } diff --git a/example/guess_number/main.zig b/example/guess_number/main.zig index 1d111aaf08..ef7dbb3b9f 100644 --- a/example/guess_number/main.zig +++ b/example/guess_number/main.zig @@ -5,7 +5,13 @@ const Rand = std.rand.Rand; const os = std.os; pub fn main() -> %void { - %%io.stdout.printf("Welcome to the Guess Number Game in Zig.\n"); + var stdout_file = %return io.getStdOut(); + const stdout = &stdout_file.out_stream; + + var stdin_file = %return io.getStdIn(); + const stdin = &stdin_file.in_stream; + + %return stdout.print("Welcome to the Guess Number Game in Zig.\n"); var seed_bytes: [@sizeOf(usize)]u8 = undefined; %%os.getRandomBytes(seed_bytes[0..]); @@ -15,24 +21,24 @@ pub fn main() -> %void { const answer = rand.range(u8, 0, 100) + 1; while (true) { - %%io.stdout.printf("\nGuess a number between 1 and 100: "); + %return stdout.print("\nGuess a number between 1 and 100: "); var line_buf : [20]u8 = undefined; - const line_len = io.stdin.read(line_buf[0..]) %% |err| { - %%io.stdout.printf("Unable to read from stdin: {}\n", @errorName(err)); + const line_len = stdin.read(line_buf[0..]) %% |err| { + %return stdout.print("Unable to read from stdin: {}\n", @errorName(err)); return err; }; const guess = fmt.parseUnsigned(u8, line_buf[0..line_len - 1], 10) %% { - %%io.stdout.printf("Invalid number.\n"); + %return stdout.print("Invalid number.\n"); continue; }; if (guess > answer) { - %%io.stdout.printf("Guess lower.\n"); + %return stdout.print("Guess lower.\n"); } else if (guess < answer) { - %%io.stdout.printf("Guess higher.\n"); + %return stdout.print("Guess higher.\n"); } else { - %%io.stdout.printf("You win!\n"); + %return stdout.print("You win!\n"); return; } } diff --git a/example/hello_world/hello.zig b/example/hello_world/hello.zig index d723ad284c..94b0e2e531 100644 --- a/example/hello_world/hello.zig +++ b/example/hello_world/hello.zig @@ -1,5 +1,10 @@ -const io = @import("std").io; +const std = @import("std"); pub fn main() -> %void { - %return io.stdout.printf("Hello, world!\n"); + // If this program is run without stdout attached, exit with an error. + var stdout_file = %return std.io.getStdOut(); + const stdout = &stdout_file.out_stream; + // If this program encounters pipe failure when printing to stdout, exit + // with an error. + %return stdout.print("Hello, world!\n"); } diff --git a/std/array_list.zig b/std/array_list.zig index e844095c67..842cc048cf 100644 --- a/std/array_list.zig +++ b/std/array_list.zig @@ -70,7 +70,7 @@ pub fn ArrayList(comptime T: type) -> type{ l.len = new_len; } - pub fn resizeDown(l: &Self, new_len: usize) { + pub fn shrink(l: &Self, new_len: usize) { assert(new_len <= l.len); l.len = new_len; } diff --git a/std/buffer.zig b/std/buffer.zig index 2686a5cf22..5eb67beef3 100644 --- a/std/buffer.zig +++ b/std/buffer.zig @@ -71,6 +71,12 @@ pub const Buffer = struct { return self.list.toSliceConst()[0..self.len()]; } + pub fn shrink(self: &Buffer, new_len: usize) { + assert(new_len <= self.len()); + self.list.shrink(new_len + 1); + self.list.items[self.len()] = 0; + } + pub fn resize(self: &Buffer, new_len: usize) -> %void { %return self.list.resize(new_len + 1); self.list.items[self.len()] = 0; diff --git a/std/build.zig b/std/build.zig index 7cf2ac5987..0b953262eb 100644 --- a/std/build.zig +++ b/std/build.zig @@ -1,17 +1,19 @@ +const std = @import("index.zig"); const builtin = @import("builtin"); -const io = @import("io.zig"); -const mem = @import("mem.zig"); -const debug = @import("debug.zig"); +const io = std.io; +const mem = std.mem; +const debug = std.debug; const assert = debug.assert; -const ArrayList = @import("array_list.zig").ArrayList; -const HashMap = @import("hash_map.zig").HashMap; -const Allocator = @import("mem.zig").Allocator; -const os = @import("os/index.zig"); +const warn = std.debug.warn; +const ArrayList = std.ArrayList; +const HashMap = std.HashMap; +const Allocator = mem.Allocator; +const os = std.os; const StdIo = os.ChildProcess.StdIo; const Term = os.ChildProcess.Term; -const BufSet = @import("buf_set.zig").BufSet; -const BufMap = @import("buf_map.zig").BufMap; -const fmt_lib = @import("fmt/index.zig"); +const BufSet = std.BufSet; +const BufMap = std.BufMap; +const fmt_lib = std.fmt; error ExtraArg; error UncleanExit; @@ -280,7 +282,7 @@ pub const Builder = struct { for (self.installed_files.toSliceConst()) |installed_file| { if (self.verbose) { - %%io.stderr.printf("rm {}\n", installed_file); + warn("rm {}\n", installed_file); } _ = os.deleteFile(self.allocator, installed_file); } @@ -290,7 +292,7 @@ pub const Builder = struct { fn makeOneStep(self: &Builder, s: &Step) -> %void { if (s.loop_flag) { - %%io.stderr.printf("Dependency loop detected:\n {}\n", s.name); + warn("Dependency loop detected:\n {}\n", s.name); return error.DependencyLoopDetected; } s.loop_flag = true; @@ -298,7 +300,7 @@ pub const Builder = struct { for (s.dependencies.toSlice()) |dep| { self.makeOneStep(dep) %% |err| { if (err == error.DependencyLoopDetected) { - %%io.stderr.printf(" {}\n", s.name); + warn(" {}\n", s.name); } return err; }; @@ -315,7 +317,7 @@ pub const Builder = struct { return &top_level_step.step; } } - %%io.stderr.printf("Cannot run step '{}' because it does not exist\n", name); + warn("Cannot run step '{}' because it does not exist\n", name); return error.InvalidStepName; } @@ -326,12 +328,12 @@ pub const Builder = struct { const word = it.next() ?? break; if (mem.eql(u8, word, "-isystem")) { const include_path = it.next() ?? { - %%io.stderr.printf("Expected argument after -isystem in NIX_CFLAGS_COMPILE\n"); + warn("Expected argument after -isystem in NIX_CFLAGS_COMPILE\n"); break; }; self.addCIncludePath(include_path); } else { - %%io.stderr.printf("Unrecognized C flag from NIX_CFLAGS_COMPILE: {}\n", word); + warn("Unrecognized C flag from NIX_CFLAGS_COMPILE: {}\n", word); break; } } @@ -344,7 +346,7 @@ pub const Builder = struct { const word = it.next() ?? break; if (mem.eql(u8, word, "-rpath")) { const rpath = it.next() ?? { - %%io.stderr.printf("Expected argument after -rpath in NIX_LDFLAGS\n"); + warn("Expected argument after -rpath in NIX_LDFLAGS\n"); break; }; self.addRPath(rpath); @@ -352,7 +354,7 @@ pub const Builder = struct { const lib_path = word[2..]; self.addLibPath(lib_path); } else { - %%io.stderr.printf("Unrecognized C flag from NIX_LDFLAGS: {}\n", word); + warn("Unrecognized C flag from NIX_LDFLAGS: {}\n", word); break; } } @@ -384,13 +386,13 @@ pub const Builder = struct { } else if (mem.eql(u8, s, "false")) { return false; } else { - %%io.stderr.printf("Expected -D{} to be a boolean, but received '{}'\n", name, s); + warn("Expected -D{} to be a boolean, but received '{}'\n", name, s); self.markInvalidUserInput(); return null; } }, UserValue.List => { - %%io.stderr.printf("Expected -D{} to be a boolean, but received a list.\n", name); + warn("Expected -D{} to be a boolean, but received a list.\n", name); self.markInvalidUserInput(); return null; }, @@ -399,12 +401,12 @@ pub const Builder = struct { TypeId.Float => debug.panic("TODO float options to build script"), TypeId.String => switch (entry.value.value) { UserValue.Flag => { - %%io.stderr.printf("Expected -D{} to be a string, but received a boolean.\n", name); + warn("Expected -D{} to be a string, but received a boolean.\n", name); self.markInvalidUserInput(); return null; }, UserValue.List => { - %%io.stderr.printf("Expected -D{} to be a string, but received a list.\n", name); + warn("Expected -D{} to be a string, but received a list.\n", name); self.markInvalidUserInput(); return null; }, @@ -437,7 +439,7 @@ pub const Builder = struct { } else if (!release_fast and !release_safe) { builtin.Mode.Debug } else { - %%io.stderr.printf("Both -Drelease-safe and -Drelease-fast specified"); + warn("Both -Drelease-safe and -Drelease-fast specified"); self.markInvalidUserInput(); builtin.Mode.Debug }; @@ -474,7 +476,7 @@ pub const Builder = struct { }); }, UserValue.Flag => { - %%io.stderr.printf("Option '-D{}={}' conflicts with flag '-D{}'.\n", name, value, name); + warn("Option '-D{}={}' conflicts with flag '-D{}'.\n", name, value, name); return true; }, } @@ -490,11 +492,11 @@ pub const Builder = struct { })) |*prev_value| { switch (prev_value.value) { UserValue.Scalar => |s| { - %%io.stderr.printf("Flag '-D{}' conflicts with option '-D{}={}'.\n", name, name, s); + warn("Flag '-D{}' conflicts with option '-D{}={}'.\n", name, name, s); return true; }, UserValue.List => { - %%io.stderr.printf("Flag '-D{}' conflicts with multiple options of the same name.\n", name); + warn("Flag '-D{}' conflicts with multiple options of the same name.\n", name); return true; }, UserValue.Flag => {}, @@ -536,7 +538,7 @@ pub const Builder = struct { while (true) { const entry = it.next() ?? break; if (!entry.value.used) { - %%io.stderr.printf("Invalid option: -D{}\n\n", entry.key); + warn("Invalid option: -D{}\n\n", entry.key); self.markInvalidUserInput(); } } @@ -549,11 +551,11 @@ pub const Builder = struct { } fn printCmd(cwd: ?[]const u8, argv: []const []const u8) { - if (cwd) |yes_cwd| %%io.stderr.print("cd {} && ", yes_cwd); + if (cwd) |yes_cwd| warn("cd {} && ", yes_cwd); for (argv) |arg| { - %%io.stderr.print("{} ", arg); + warn("{} ", arg); } - %%io.stderr.printf("\n"); + warn("\n"); } fn spawnChildEnvMap(self: &Builder, cwd: ?[]const u8, env_map: &const BufMap, @@ -570,20 +572,20 @@ pub const Builder = struct { child.env_map = env_map; const term = child.spawnAndWait() %% |err| { - %%io.stderr.printf("Unable to spawn {}: {}\n", argv[0], @errorName(err)); + warn("Unable to spawn {}: {}\n", argv[0], @errorName(err)); return err; }; switch (term) { Term.Exited => |code| { if (code != 0) { - %%io.stderr.printf("The following command exited with error code {}:\n", code); + warn("The following command exited with error code {}:\n", code); printCmd(cwd, argv); return error.UncleanExit; } }, else => { - %%io.stderr.printf("The following command terminated unexpectedly:\n"); + warn("The following command terminated unexpectedly:\n"); printCmd(cwd, argv); return error.UncleanExit; @@ -594,7 +596,7 @@ pub const Builder = struct { pub fn makePath(self: &Builder, path: []const u8) -> %void { os.makePath(self.allocator, self.pathFromRoot(path)) %% |err| { - %%io.stderr.printf("Unable to create path {}: {}\n", path, @errorName(err)); + warn("Unable to create path {}: {}\n", path, @errorName(err)); return err; }; } @@ -633,17 +635,17 @@ pub const Builder = struct { fn copyFileMode(self: &Builder, source_path: []const u8, dest_path: []const u8, mode: usize) -> %void { if (self.verbose) { - %%io.stderr.printf("cp {} {}\n", source_path, dest_path); + warn("cp {} {}\n", source_path, dest_path); } const dirname = os.path.dirname(dest_path); const abs_source_path = self.pathFromRoot(source_path); os.makePath(self.allocator, dirname) %% |err| { - %%io.stderr.printf("Unable to create path {}: {}\n", dirname, @errorName(err)); + warn("Unable to create path {}: {}\n", dirname, @errorName(err)); return err; }; os.copyFileMode(self.allocator, abs_source_path, dest_path, mode) %% |err| { - %%io.stderr.printf("Unable to copy {} to {}: {}\n", abs_source_path, dest_path, @errorName(err)); + warn("Unable to copy {} to {}: {}\n", abs_source_path, dest_path, @errorName(err)); return err; }; } @@ -1103,7 +1105,7 @@ pub const LibExeObjStep = struct { assert(self.is_zig); if (self.root_src == null and self.object_files.len == 0 and self.assembly_files.len == 0) { - %%io.stderr.printf("{}: linker needs 1 or more objects to link\n", self.step.name); + warn("{}: linker needs 1 or more objects to link\n", self.step.name); return error.NeedAnObject; } @@ -1799,11 +1801,11 @@ pub const WriteFileStep = struct { const full_path = self.builder.pathFromRoot(self.file_path); const full_path_dir = os.path.dirname(full_path); os.makePath(self.builder.allocator, full_path_dir) %% |err| { - %%io.stderr.printf("unable to make path {}: {}\n", full_path_dir, @errorName(err)); + warn("unable to make path {}: {}\n", full_path_dir, @errorName(err)); return err; }; io.writeFile(full_path, self.data, self.builder.allocator) %% |err| { - %%io.stderr.printf("unable to write {}: {}\n", full_path, @errorName(err)); + warn("unable to write {}: {}\n", full_path, @errorName(err)); return err; }; } @@ -1824,8 +1826,7 @@ pub const LogStep = struct { fn make(step: &Step) -> %void { const self = @fieldParentPtr(LogStep, "step", step); - %%io.stderr.write(self.data); - %%io.stderr.flush(); + warn("{}", self.data); } }; @@ -1847,7 +1848,7 @@ pub const RemoveDirStep = struct { const full_path = self.builder.pathFromRoot(self.dir_path); os.deleteTree(self.builder.allocator, full_path) %% |err| { - %%io.stderr.printf("Unable to remove {}: {}\n", full_path, @errorName(err)); + warn("Unable to remove {}: {}\n", full_path, @errorName(err)); return err; }; } @@ -1896,13 +1897,13 @@ fn doAtomicSymLinks(allocator: &Allocator, output_path: []const u8, filename_maj // sym link for libfoo.so.1 to libfoo.so.1.2.3 const major_only_path = %%os.path.join(allocator, out_dir, filename_major_only); os.atomicSymLink(allocator, out_basename, major_only_path) %% |err| { - %%io.stderr.printf("Unable to symlink {} -> {}\n", major_only_path, out_basename); + warn("Unable to symlink {} -> {}\n", major_only_path, out_basename); return err; }; // sym link for libfoo.so to libfoo.so.1 const name_only_path = %%os.path.join(allocator, out_dir, filename_name_only); os.atomicSymLink(allocator, filename_major_only, name_only_path) %% |err| { - %%io.stderr.printf("Unable to symlink {} -> {}\n", name_only_path, filename_major_only); + warn("Unable to symlink {} -> {}\n", name_only_path, filename_major_only); return err; }; } diff --git a/std/debug.zig b/std/debug.zig index 24f4be9fe1..5cdb7f8a85 100644 --- a/std/debug.zig +++ b/std/debug.zig @@ -1,10 +1,11 @@ -const math = @import("math/index.zig"); -const mem = @import("mem.zig"); -const io = @import("io.zig"); -const os = @import("os/index.zig"); +const std = @import("index.zig"); +const math = std.math; +const mem = std.mem; +const io = std.io; +const os = std.os; const elf = @import("elf.zig"); const DW = @import("dwarf.zig"); -const ArrayList = @import("array_list.zig").ArrayList; +const ArrayList = std.ArrayList; const builtin = @import("builtin"); error MissingDebugInfo; @@ -12,10 +13,42 @@ error InvalidDebugInfo; error UnsupportedDebugInfo; +/// 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_stream: ?&io.OutStream = null; +pub fn warn(comptime fmt: []const u8, args: ...) { + const stderr = getStderrStream() %% return; + stderr.print(fmt, args) %% return; +} +fn getStderrStream() -> %&io.OutStream { + if (stderr_stream) |st| { + return st; + } else { + stderr_file = %return io.getStdErr(); + const st = &stderr_file.out_stream; + stderr_stream = st; + return st; + }; +} + +/// Tries to print a stack trace to stderr, unbuffered, and ignores any error returned. +pub fn dumpStackTrace() { + const stderr = getStderrStream() %% return; + writeStackTrace(stderr, &global_allocator, stderr_file.isTty(), 1) %% return; +} + +/// This function invokes undefined behavior when `ok` is `false`. +/// In Debug and ReleaseSafe modes, calls to this function are always +/// generated, and the `unreachable` statement triggers a panic. +/// In ReleaseFast and ReleaseSmall modes, calls to this function can be +/// optimized away. pub fn assert(ok: bool) { if (!ok) { // In ReleaseFast test mode, we still want assert(false) to crash, so // we insert an explicit call to @panic instead of unreachable. + // TODO we should use `assertOrPanic` in tests and remove this logic. if (builtin.is_test) { @panic("assertion failure") } else { @@ -24,6 +57,14 @@ pub fn assert(ok: bool) { } } +/// Call this function when you want to panic if the condition is not true. +/// If `ok` is `false`, this function will panic in every release mode. +pub fn assertOrPanic(ok: bool) { + if (!ok) { + @panic("assertion failure"); + } +} + var panicking = false; /// This is the default panic implementation. pub fn panic(comptime format: []const u8, args: ...) -> noreturn { @@ -41,18 +82,13 @@ pub fn panic(comptime format: []const u8, args: ...) -> noreturn { panicking = true; } - %%io.stderr.printf(format ++ "\n", args); - %%writeStackTrace(&io.stderr, &global_allocator, io.stderr.isTty() %% false, 1); - %%io.stderr.flush(); + const stderr = getStderrStream() %% os.abort(); + stderr.print(format ++ "\n", args) %% os.abort(); + writeStackTrace(stderr, &global_allocator, stderr_file.isTty(), 1) %% os.abort(); os.abort(); } -pub fn printStackTrace() -> %void { - %return writeStackTrace(&io.stderr, &global_allocator, io.stderr.isTty() %% false, 1); - %return io.stderr.flush(); -} - const GREEN = "\x1b[32;1m"; const WHITE = "\x1b[37;1m"; const DIM = "\x1b[2m"; @@ -69,7 +105,7 @@ pub fn writeStackTrace(out_stream: &io.OutStream, allocator: &mem.Allocator, tty switch (builtin.object_format) { builtin.ObjectFormat.elf => { var stack_trace = ElfStackTrace { - .self_exe_stream = undefined, + .self_exe_file = undefined, .elf = undefined, .debug_info = undefined, .debug_abbrev = undefined, @@ -79,10 +115,10 @@ pub fn writeStackTrace(out_stream: &io.OutStream, allocator: &mem.Allocator, tty .compile_unit_list = ArrayList(CompileUnit).init(allocator), }; const st = &stack_trace; - st.self_exe_stream = %return io.openSelfExe(); - defer st.self_exe_stream.close(); + st.self_exe_file = %return os.openSelfExe(); + defer st.self_exe_file.close(); - %return st.elf.openStream(allocator, &st.self_exe_stream); + %return st.elf.openFile(allocator, &st.self_exe_file); defer st.elf.close(); st.debug_info = (%return st.elf.findSection(".debug_info")) ?? return error.MissingDebugInfo; @@ -109,7 +145,6 @@ pub fn writeStackTrace(out_stream: &io.OutStream, allocator: &mem.Allocator, tty const compile_unit = findCompileUnit(st, return_address) ?? { %return out_stream.print("???:?:?: " ++ DIM ++ ptr_hex ++ " in ??? (???)" ++ RESET ++ "\n ???\n\n", return_address); - %return out_stream.flush(); continue; }; const compile_unit_name = %return compile_unit.die.getAttrString(st, DW.AT_name); @@ -139,7 +174,6 @@ pub fn writeStackTrace(out_stream: &io.OutStream, allocator: &mem.Allocator, tty }, else => return err, }; - %return out_stream.flush(); } }, builtin.ObjectFormat.coff => { @@ -158,7 +192,7 @@ pub fn writeStackTrace(out_stream: &io.OutStream, allocator: &mem.Allocator, tty } fn printLineFromFile(allocator: &mem.Allocator, out_stream: &io.OutStream, line_info: &const LineInfo) -> %void { - var f = %return io.InStream.open(line_info.file_name, allocator); + var f = %return io.File.openRead(line_info.file_name, allocator); defer f.close(); // TODO fstat and make sure that the file has the correct size @@ -167,7 +201,7 @@ fn printLineFromFile(allocator: &mem.Allocator, out_stream: &io.OutStream, line_ var column: usize = 1; var abs_index: usize = 0; while (true) { - const amt_read = %return f.read(buf[0..]); + const amt_read = %return f.in_stream.read(buf[0..]); const slice = buf[0..amt_read]; for (slice) |byte| { @@ -191,7 +225,7 @@ fn printLineFromFile(allocator: &mem.Allocator, out_stream: &io.OutStream, line_ } const ElfStackTrace = struct { - self_exe_stream: io.InStream, + self_exe_file: io.File, elf: elf.Elf, debug_info: &elf.SectionHeader, debug_abbrev: &elf.SectionHeader, @@ -205,7 +239,7 @@ const ElfStackTrace = struct { } pub fn readString(self: &ElfStackTrace) -> %[]u8 { - return readStringRaw(self.allocator(), &self.self_exe_stream); + return readStringRaw(self.allocator(), &self.self_exe_file.in_stream); } }; @@ -424,7 +458,7 @@ fn readStringRaw(allocator: &mem.Allocator, in_stream: &io.InStream) -> %[]u8 { fn getString(st: &ElfStackTrace, offset: u64) -> %[]u8 { const pos = st.debug_str.offset + offset; - %return st.self_exe_stream.seekTo(pos); + %return st.self_exe_file.seekTo(pos); return st.readString(); } @@ -533,7 +567,7 @@ fn parseFormValue(allocator: &mem.Allocator, in_stream: &io.InStream, form_id: u } fn parseAbbrevTable(st: &ElfStackTrace) -> %AbbrevTable { - const in_stream = &st.self_exe_stream; + const in_stream = &st.self_exe_file.in_stream; var result = AbbrevTable.init(st.allocator()); while (true) { const abbrev_code = %return readULeb128(in_stream); @@ -568,7 +602,7 @@ fn getAbbrevTable(st: &ElfStackTrace, abbrev_offset: u64) -> %&const AbbrevTable return &header.table; } } - %return st.self_exe_stream.seekTo(st.debug_abbrev.offset + abbrev_offset); + %return st.self_exe_file.seekTo(st.debug_abbrev.offset + abbrev_offset); %return st.abbrev_table_list.append(AbbrevTableHeader { .offset = abbrev_offset, .table = %return parseAbbrevTable(st), @@ -585,8 +619,8 @@ fn getAbbrevTableEntry(abbrev_table: &const AbbrevTable, abbrev_code: u64) -> ?& } fn parseDie(st: &ElfStackTrace, abbrev_table: &const AbbrevTable, is_64: bool) -> %Die { - const in_stream = &st.self_exe_stream; - const abbrev_code = %return readULeb128(in_stream); + const in_file = &st.self_exe_file; + const abbrev_code = %return readULeb128(&in_file.in_stream); const table_entry = getAbbrevTableEntry(abbrev_table, abbrev_code) ?? return error.InvalidDebugInfo; var result = Die { @@ -598,7 +632,7 @@ fn parseDie(st: &ElfStackTrace, abbrev_table: &const AbbrevTable, is_64: bool) - for (table_entry.attrs.toSliceConst()) |attr, i| { result.attrs.items[i] = Die.Attr { .id = attr.attr_id, - .value = %return parseFormValue(st.allocator(), &st.self_exe_stream, attr.form_id, is_64), + .value = %return parseFormValue(st.allocator(), &st.self_exe_file.in_stream, attr.form_id, is_64), }; } return result; @@ -607,16 +641,16 @@ fn parseDie(st: &ElfStackTrace, abbrev_table: &const AbbrevTable, is_64: bool) - fn getLineNumberInfo(st: &ElfStackTrace, compile_unit: &const CompileUnit, target_address: usize) -> %LineInfo { const compile_unit_cwd = %return compile_unit.die.getAttrString(st, DW.AT_comp_dir); - const in_stream = &st.self_exe_stream; + const in_file = &st.self_exe_file; const debug_line_end = st.debug_line.offset + st.debug_line.size; var this_offset = st.debug_line.offset; var this_index: usize = 0; while (this_offset < debug_line_end) : (this_index += 1) { - %return in_stream.seekTo(this_offset); + %return in_file.seekTo(this_offset); var is_64: bool = undefined; - const unit_length = %return readInitialLength(in_stream, &is_64); + const unit_length = %return readInitialLength(&in_file.in_stream, &is_64); if (unit_length == 0) return error.MissingDebugInfo; const next_offset = unit_length + (if (is_64) usize(12) else usize(4)); @@ -626,28 +660,28 @@ fn getLineNumberInfo(st: &ElfStackTrace, compile_unit: &const CompileUnit, targe continue; } - const version = %return in_stream.readInt(st.elf.is_big_endian, u16); + const version = %return in_file.in_stream.readInt(st.elf.is_big_endian, u16); if (version != 2) return error.InvalidDebugInfo; - const prologue_length = %return in_stream.readInt(st.elf.is_big_endian, u32); - const prog_start_offset = (%return in_stream.getPos()) + prologue_length; + const prologue_length = %return in_file.in_stream.readInt(st.elf.is_big_endian, u32); + const prog_start_offset = (%return in_file.getPos()) + prologue_length; - const minimum_instruction_length = %return in_stream.readByte(); + const minimum_instruction_length = %return in_file.in_stream.readByte(); if (minimum_instruction_length == 0) return error.InvalidDebugInfo; - const default_is_stmt = (%return in_stream.readByte()) != 0; - const line_base = %return in_stream.readByteSigned(); + const default_is_stmt = (%return in_file.in_stream.readByte()) != 0; + const line_base = %return in_file.in_stream.readByteSigned(); - const line_range = %return in_stream.readByte(); + const line_range = %return in_file.in_stream.readByte(); if (line_range == 0) return error.InvalidDebugInfo; - const opcode_base = %return in_stream.readByte(); + const opcode_base = %return in_file.in_stream.readByte(); const standard_opcode_lengths = %return st.allocator().alloc(u8, opcode_base - 1); {var i: usize = 0; while (i < opcode_base - 1) : (i += 1) { - standard_opcode_lengths[i] = %return in_stream.readByte(); + standard_opcode_lengths[i] = %return in_file.in_stream.readByte(); }} var include_directories = ArrayList([]u8).init(st.allocator()); @@ -667,9 +701,9 @@ fn getLineNumberInfo(st: &ElfStackTrace, compile_unit: &const CompileUnit, targe const file_name = %return st.readString(); if (file_name.len == 0) break; - const dir_index = %return readULeb128(in_stream); - const mtime = %return readULeb128(in_stream); - const len_bytes = %return readULeb128(in_stream); + const dir_index = %return readULeb128(&in_file.in_stream); + const mtime = %return readULeb128(&in_file.in_stream); + const len_bytes = %return readULeb128(&in_file.in_stream); %return file_entries.append(FileEntry { .file_name = file_name, .dir_index = dir_index, @@ -678,42 +712,32 @@ fn getLineNumberInfo(st: &ElfStackTrace, compile_unit: &const CompileUnit, targe }); } - %return in_stream.seekTo(prog_start_offset); + %return in_file.seekTo(prog_start_offset); while (true) { - //const pos = (%return in_stream.getPos()) - this_offset; - //if (pos == 0x1a3) @breakpoint(); - //%%io.stderr.printf("\n{x8}\n", pos); - - const opcode = %return in_stream.readByte(); + const opcode = %return in_file.in_stream.readByte(); var sub_op: u8 = undefined; // TODO move this to the correct scope and fix the compiler crash if (opcode == DW.LNS_extended_op) { - const op_size = %return readULeb128(in_stream); + const op_size = %return readULeb128(&in_file.in_stream); if (op_size < 1) return error.InvalidDebugInfo; - sub_op = %return in_stream.readByte(); + sub_op = %return in_file.in_stream.readByte(); switch (sub_op) { DW.LNE_end_sequence => { - //%%io.stdout.printf(" [0x{x8}] End Sequence\n", pos); prog.end_sequence = true; if (%return prog.checkLineMatch()) |info| return info; return error.MissingDebugInfo; }, DW.LNE_set_address => { - const addr = %return in_stream.readInt(st.elf.is_big_endian, usize); + const addr = %return in_file.in_stream.readInt(st.elf.is_big_endian, usize); prog.address = addr; - - //%%io.stdout.printf(" [0x{x8}] Extended opcode {}: set Address to 0x{x}\n", - // pos, sub_op, addr); }, DW.LNE_define_file => { - //%%io.stdout.printf(" [0x{x8}] Define File\n", pos); - const file_name = %return st.readString(); - const dir_index = %return readULeb128(in_stream); - const mtime = %return readULeb128(in_stream); - const len_bytes = %return readULeb128(in_stream); + const dir_index = %return readULeb128(&in_file.in_stream); + const mtime = %return readULeb128(&in_file.in_stream); + const len_bytes = %return readULeb128(&in_file.in_stream); %return file_entries.append(FileEntry { .file_name = file_name, .dir_index = dir_index, @@ -723,7 +747,7 @@ fn getLineNumberInfo(st: &ElfStackTrace, compile_unit: &const CompileUnit, targe }, else => { const fwd_amt = math.cast(isize, op_size - 1) %% return error.InvalidDebugInfo; - %return in_stream.seekForward(fwd_amt); + %return in_file.seekForward(fwd_amt); }, } } else if (opcode >= opcode_base) { @@ -733,48 +757,32 @@ fn getLineNumberInfo(st: &ElfStackTrace, compile_unit: &const CompileUnit, targe const inc_line = i32(line_base) + i32(adjusted_opcode % line_range); prog.line += inc_line; prog.address += inc_addr; - //%%io.stdout.printf( - // " [0x{x8}] Special opcode {}: advance Address by {} to 0x{x} and Line by {} to {}\n", - // pos, adjusted_opcode, inc_addr, prog.address, inc_line, prog.line); if (%return prog.checkLineMatch()) |info| return info; prog.basic_block = false; } else { switch (opcode) { DW.LNS_copy => { - //%%io.stdout.printf(" [0x{x8}] Copy\n", pos); - if (%return prog.checkLineMatch()) |info| return info; prog.basic_block = false; }, DW.LNS_advance_pc => { - const arg = %return readULeb128(in_stream); + const arg = %return readULeb128(&in_file.in_stream); prog.address += arg * minimum_instruction_length; - - //%%io.stdout.printf(" [0x{x8}] Advance PC by {} to 0x{x}\n", pos, arg, prog.address); }, DW.LNS_advance_line => { - const arg = %return readILeb128(in_stream); + const arg = %return readILeb128(&in_file.in_stream); prog.line += arg; - - //%%io.stdout.printf(" [0x{x8}] Advance Line by {} to {}\n", pos, arg, prog.line); }, DW.LNS_set_file => { - const arg = %return readULeb128(in_stream); + const arg = %return readULeb128(&in_file.in_stream); prog.file = arg; - - //%%io.stdout.printf(" [0x{x8}] Set File Name to entry {} in the File Name Table\n", - // pos, arg); }, DW.LNS_set_column => { - const arg = %return readULeb128(in_stream); + const arg = %return readULeb128(&in_file.in_stream); prog.column = arg; - - //%%io.stdout.printf(" [0x{x8}] Set column to {}\n", pos, arg); }, DW.LNS_negate_stmt => { prog.is_stmt = !prog.is_stmt; - - //%%io.stdout.printf(" [0x{x8}] Set is_stmt to {}\n", pos, if (prog.is_stmt) u8(1) else u8(0)); }, DW.LNS_set_basic_block => { prog.basic_block = true; @@ -782,23 +790,18 @@ fn getLineNumberInfo(st: &ElfStackTrace, compile_unit: &const CompileUnit, targe DW.LNS_const_add_pc => { const inc_addr = minimum_instruction_length * ((255 - opcode_base) / line_range); prog.address += inc_addr; - - //%%io.stdout.printf(" [0x{x8}] Advance PC by constant {} to 0x{x}\n", - // pos, inc_addr, prog.address); }, DW.LNS_fixed_advance_pc => { - const arg = %return in_stream.readInt(st.elf.is_big_endian, u16); + const arg = %return in_file.in_stream.readInt(st.elf.is_big_endian, u16); prog.address += arg; }, DW.LNS_set_prologue_end => { - //%%io.stdout.printf(" [0x{x8}] Set prologue_end to true\n", pos); }, else => { if (opcode - 1 >= standard_opcode_lengths.len) return error.InvalidDebugInfo; - //%%io.stdout.printf(" [0x{x8}] unknown op code {}\n", pos, opcode); const len_bytes = standard_opcode_lengths[opcode - 1]; - %return in_stream.seekForward(len_bytes); + %return in_file.seekForward(len_bytes); }, } } @@ -815,30 +818,30 @@ fn scanAllCompileUnits(st: &ElfStackTrace) -> %void { var this_unit_offset = st.debug_info.offset; var cu_index: usize = 0; while (this_unit_offset < debug_info_end) { - %return st.self_exe_stream.seekTo(this_unit_offset); + %return st.self_exe_file.seekTo(this_unit_offset); var is_64: bool = undefined; - const unit_length = %return readInitialLength(&st.self_exe_stream, &is_64); + const unit_length = %return readInitialLength(&st.self_exe_file.in_stream, &is_64); if (unit_length == 0) return; const next_offset = unit_length + (if (is_64) usize(12) else usize(4)); - const version = %return st.self_exe_stream.readInt(st.elf.is_big_endian, u16); + const version = %return st.self_exe_file.in_stream.readInt(st.elf.is_big_endian, u16); if (version < 2 or version > 5) return error.InvalidDebugInfo; const debug_abbrev_offset = if (is_64) { - %return st.self_exe_stream.readInt(st.elf.is_big_endian, u64) + %return st.self_exe_file.in_stream.readInt(st.elf.is_big_endian, u64) } else { - %return st.self_exe_stream.readInt(st.elf.is_big_endian, u32) + %return st.self_exe_file.in_stream.readInt(st.elf.is_big_endian, u32) }; - const address_size = %return st.self_exe_stream.readByte(); + const address_size = %return st.self_exe_file.in_stream.readByte(); if (address_size != @sizeOf(usize)) return error.InvalidDebugInfo; - const compile_unit_pos = %return st.self_exe_stream.getPos(); + const compile_unit_pos = %return st.self_exe_file.getPos(); const abbrev_table = %return getAbbrevTable(st, debug_abbrev_offset); - %return st.self_exe_stream.seekTo(compile_unit_pos); + %return st.self_exe_file.seekTo(compile_unit_pos); const compile_unit_die = %return st.allocator().create(Die); *compile_unit_die = %return parseDie(st, abbrev_table, is_64); diff --git a/std/elf.zig b/std/elf.zig index 36f5bd80f5..732ad28d2c 100644 --- a/std/elf.zig +++ b/std/elf.zig @@ -1,7 +1,9 @@ -const io = @import("io.zig"); -const math = @import("math/index.zig"); -const mem = @import("mem.zig"); -const debug = @import("debug.zig"); +const std = @import("index.zig"); +const io = std.io; +const math = std.math; +const mem = std.mem; +const debug = std.debug; +const InStream = std.stream.InStream; error InvalidFormat; @@ -62,7 +64,7 @@ pub const SectionHeader = struct { }; pub const Elf = struct { - in_stream: &io.InStream, + in_file: &io.File, auto_close_stream: bool, is_64: bool, is_big_endian: bool, @@ -75,44 +77,44 @@ pub const Elf = struct { string_section: &SectionHeader, section_headers: []SectionHeader, allocator: &mem.Allocator, - prealloc_stream: io.InStream, + prealloc_file: io.File, /// Call close when done. - pub fn openFile(elf: &Elf, allocator: &mem.Allocator, path: []const u8) -> %void { - %return elf.prealloc_stream.open(path); - %return elf.openStream(allocator, &elf.prealloc_stream); + pub fn openPath(elf: &Elf, allocator: &mem.Allocator, path: []const u8) -> %void { + %return elf.prealloc_file.open(path); + %return elf.openFile(allocator, &elf.prealloc_file); elf.auto_close_stream = true; } /// Call close when done. - pub fn openStream(elf: &Elf, allocator: &mem.Allocator, stream: &io.InStream) -> %void { + pub fn openFile(elf: &Elf, allocator: &mem.Allocator, file: &io.File) -> %void { elf.allocator = allocator; - elf.in_stream = stream; + elf.in_file = file; elf.auto_close_stream = false; var magic: [4]u8 = undefined; - %return elf.in_stream.readNoEof(magic[0..]); + %return elf.in_file.in_stream.readNoEof(magic[0..]); if (!mem.eql(u8, magic, "\x7fELF")) return error.InvalidFormat; - elf.is_64 = switch (%return elf.in_stream.readByte()) { + elf.is_64 = switch (%return elf.in_file.in_stream.readByte()) { 1 => false, 2 => true, else => return error.InvalidFormat, }; - elf.is_big_endian = switch (%return elf.in_stream.readByte()) { + elf.is_big_endian = switch (%return elf.in_file.in_stream.readByte()) { 1 => false, 2 => true, else => return error.InvalidFormat, }; - const version_byte = %return elf.in_stream.readByte(); + const version_byte = %return elf.in_file.in_stream.readByte(); if (version_byte != 1) return error.InvalidFormat; // skip over padding - %return elf.in_stream.seekForward(9); + %return elf.in_file.seekForward(9); - elf.file_type = switch (%return elf.in_stream.readInt(elf.is_big_endian, u16)) { + elf.file_type = switch (%return elf.in_file.in_stream.readInt(elf.is_big_endian, u16)) { 1 => FileType.Relocatable, 2 => FileType.Executable, 3 => FileType.Shared, @@ -120,7 +122,7 @@ pub const Elf = struct { else => return error.InvalidFormat, }; - elf.arch = switch (%return elf.in_stream.readInt(elf.is_big_endian, u16)) { + elf.arch = switch (%return elf.in_file.in_stream.readInt(elf.is_big_endian, u16)) { 0x02 => Arch.Sparc, 0x03 => Arch.x86, 0x08 => Arch.Mips, @@ -133,34 +135,34 @@ pub const Elf = struct { else => return error.InvalidFormat, }; - const elf_version = %return elf.in_stream.readInt(elf.is_big_endian, u32); + const elf_version = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u32); if (elf_version != 1) return error.InvalidFormat; if (elf.is_64) { - elf.entry_addr = %return elf.in_stream.readInt(elf.is_big_endian, u64); - elf.program_header_offset = %return elf.in_stream.readInt(elf.is_big_endian, u64); - elf.section_header_offset = %return elf.in_stream.readInt(elf.is_big_endian, u64); + elf.entry_addr = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u64); + elf.program_header_offset = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u64); + elf.section_header_offset = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u64); } else { - elf.entry_addr = u64(%return elf.in_stream.readInt(elf.is_big_endian, u32)); - elf.program_header_offset = u64(%return elf.in_stream.readInt(elf.is_big_endian, u32)); - elf.section_header_offset = u64(%return elf.in_stream.readInt(elf.is_big_endian, u32)); + elf.entry_addr = u64(%return elf.in_file.in_stream.readInt(elf.is_big_endian, u32)); + elf.program_header_offset = u64(%return elf.in_file.in_stream.readInt(elf.is_big_endian, u32)); + elf.section_header_offset = u64(%return elf.in_file.in_stream.readInt(elf.is_big_endian, u32)); } // skip over flags - %return elf.in_stream.seekForward(4); + %return elf.in_file.seekForward(4); - const header_size = %return elf.in_stream.readInt(elf.is_big_endian, u16); + const header_size = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u16); if ((elf.is_64 and header_size != 64) or (!elf.is_64 and header_size != 52)) { return error.InvalidFormat; } - const ph_entry_size = %return elf.in_stream.readInt(elf.is_big_endian, u16); - const ph_entry_count = %return elf.in_stream.readInt(elf.is_big_endian, u16); - const sh_entry_size = %return elf.in_stream.readInt(elf.is_big_endian, u16); - const sh_entry_count = %return elf.in_stream.readInt(elf.is_big_endian, u16); - elf.string_section_index = u64(%return elf.in_stream.readInt(elf.is_big_endian, u16)); + const ph_entry_size = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u16); + const ph_entry_count = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u16); + const sh_entry_size = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u16); + const sh_entry_count = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u16); + elf.string_section_index = u64(%return elf.in_file.in_stream.readInt(elf.is_big_endian, u16)); if (elf.string_section_index >= sh_entry_count) return error.InvalidFormat; @@ -169,12 +171,12 @@ pub const Elf = struct { const ph_byte_count = u64(ph_entry_size) * u64(ph_entry_count); const end_ph = %return math.add(u64, elf.program_header_offset, ph_byte_count); - const stream_end = %return elf.in_stream.getEndPos(); + const stream_end = %return elf.in_file.getEndPos(); if (stream_end < end_sh or stream_end < end_ph) { return error.InvalidFormat; } - %return elf.in_stream.seekTo(elf.section_header_offset); + %return elf.in_file.seekTo(elf.section_header_offset); elf.section_headers = %return elf.allocator.alloc(SectionHeader, sh_entry_count); %defer elf.allocator.free(elf.section_headers); @@ -183,32 +185,32 @@ pub const Elf = struct { if (sh_entry_size != 64) return error.InvalidFormat; for (elf.section_headers) |*section| { - section.name = %return elf.in_stream.readInt(elf.is_big_endian, u32); - section.sh_type = %return elf.in_stream.readInt(elf.is_big_endian, u32); - section.flags = %return elf.in_stream.readInt(elf.is_big_endian, u64); - section.addr = %return elf.in_stream.readInt(elf.is_big_endian, u64); - section.offset = %return elf.in_stream.readInt(elf.is_big_endian, u64); - section.size = %return elf.in_stream.readInt(elf.is_big_endian, u64); - section.link = %return elf.in_stream.readInt(elf.is_big_endian, u32); - section.info = %return elf.in_stream.readInt(elf.is_big_endian, u32); - section.addr_align = %return elf.in_stream.readInt(elf.is_big_endian, u64); - section.ent_size = %return elf.in_stream.readInt(elf.is_big_endian, u64); + section.name = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u32); + section.sh_type = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u32); + section.flags = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u64); + section.addr = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u64); + section.offset = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u64); + section.size = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u64); + section.link = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u32); + section.info = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u32); + section.addr_align = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u64); + section.ent_size = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u64); } } else { if (sh_entry_size != 40) return error.InvalidFormat; for (elf.section_headers) |*section| { // TODO (multiple occurences) allow implicit cast from %u32 -> %u64 ? - section.name = %return elf.in_stream.readInt(elf.is_big_endian, u32); - section.sh_type = %return elf.in_stream.readInt(elf.is_big_endian, u32); - section.flags = u64(%return elf.in_stream.readInt(elf.is_big_endian, u32)); - section.addr = u64(%return elf.in_stream.readInt(elf.is_big_endian, u32)); - section.offset = u64(%return elf.in_stream.readInt(elf.is_big_endian, u32)); - section.size = u64(%return elf.in_stream.readInt(elf.is_big_endian, u32)); - section.link = %return elf.in_stream.readInt(elf.is_big_endian, u32); - section.info = %return elf.in_stream.readInt(elf.is_big_endian, u32); - section.addr_align = u64(%return elf.in_stream.readInt(elf.is_big_endian, u32)); - section.ent_size = u64(%return elf.in_stream.readInt(elf.is_big_endian, u32)); + section.name = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u32); + section.sh_type = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u32); + section.flags = u64(%return elf.in_file.in_stream.readInt(elf.is_big_endian, u32)); + section.addr = u64(%return elf.in_file.in_stream.readInt(elf.is_big_endian, u32)); + section.offset = u64(%return elf.in_file.in_stream.readInt(elf.is_big_endian, u32)); + section.size = u64(%return elf.in_file.in_stream.readInt(elf.is_big_endian, u32)); + section.link = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u32); + section.info = %return elf.in_file.in_stream.readInt(elf.is_big_endian, u32); + section.addr_align = u64(%return elf.in_file.in_stream.readInt(elf.is_big_endian, u32)); + section.ent_size = u64(%return elf.in_file.in_stream.readInt(elf.is_big_endian, u32)); } } @@ -230,7 +232,7 @@ pub const Elf = struct { elf.allocator.free(elf.section_headers); if (elf.auto_close_stream) - elf.in_stream.close(); + elf.in_file.close(); } pub fn findSection(elf: &Elf, name: []const u8) -> %?&SectionHeader { @@ -238,15 +240,15 @@ pub const Elf = struct { if (section.sh_type == SHT_NULL) continue; const name_offset = elf.string_section.offset + section.name; - %return elf.in_stream.seekTo(name_offset); + %return elf.in_file.seekTo(name_offset); for (name) |expected_c| { - const target_c = %return elf.in_stream.readByte(); + const target_c = %return elf.in_file.in_stream.readByte(); if (target_c == 0 or expected_c != target_c) goto next_section; } { - const null_byte = %return elf.in_stream.readByte(); + const null_byte = %return elf.in_file.in_stream.readByte(); if (null_byte == 0) return section; } @@ -257,6 +259,6 @@ pub const Elf = struct { } pub fn seekToSection(elf: &Elf, section: &SectionHeader) -> %void { - %return elf.in_stream.seekTo(section.offset); + %return elf.in_file.seekTo(section.offset); } }; diff --git a/std/fmt/index.zig b/std/fmt/index.zig index 56b9a2e960..59376e0458 100644 --- a/std/fmt/index.zig +++ b/std/fmt/index.zig @@ -19,10 +19,10 @@ 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. -/// Return false from output function and output will not be called again. -/// Returns false if output ever returned false, true otherwise. -pub fn format(context: var, output: fn(@typeOf(context), []const u8)->bool, - comptime fmt: []const u8, args: ...) -> bool +/// 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 { comptime var start_index = 0; comptime var state = State.Start; @@ -38,15 +38,13 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)->bool, '{' => { // TODO if you make this an if statement with `and` then it breaks if (start_index < i) { - if (!output(context, fmt[start_index..i])) - return false; + %return output(context, fmt[start_index..i]); } state = State.OpenBrace; }, '}' => { if (start_index < i) { - if (!output(context, fmt[start_index..i])) - return false; + %return output(context, fmt[start_index..i]); } state = State.CloseBrace; }, @@ -58,8 +56,7 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)->bool, start_index = i; }, '}' => { - if (!formatValue(args[next_arg], context, output)) - return false; + %return formatValue(args[next_arg], context, output); next_arg += 1; state = State.Start; start_index = i + 1; @@ -109,8 +106,7 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)->bool, }, State.Integer => switch (c) { '}' => { - if (!formatInt(args[next_arg], radix, uppercase, width, context, output)) - return false; + %return formatInt(args[next_arg], radix, uppercase, width, context, output); next_arg += 1; state = State.Start; start_index = i + 1; @@ -124,8 +120,7 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)->bool, State.IntegerWidth => switch (c) { '}' => { width = comptime %%parseUnsigned(usize, fmt[width_start..i], 10); - if (!formatInt(args[next_arg], radix, uppercase, width, context, output)) - return false; + %return formatInt(args[next_arg], radix, uppercase, width, context, output); next_arg += 1; state = State.Start; start_index = i + 1; @@ -136,8 +131,7 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)->bool, State.BufWidth => switch (c) { '}' => { width = comptime %%parseUnsigned(usize, fmt[width_start..i], 10); - if (!formatBuf(args[next_arg], width, context, output)) - return false; + %return formatBuf(args[next_arg], width, context, output); next_arg += 1; state = State.Start; start_index = i + 1; @@ -147,8 +141,7 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)->bool, }, State.Character => switch (c) { '}' => { - if (!formatAsciiChar(args[next_arg], context, output)) - return false; + %return formatAsciiChar(args[next_arg], context, output); next_arg += 1; state = State.Start; start_index = i + 1; @@ -166,14 +159,11 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)->bool, } } if (start_index < fmt.len) { - if (!output(context, fmt[start_index..])) - return false; + %return output(context, fmt[start_index..]); } - - return true; } -pub fn formatValue(value: var, context: var, output: fn(@typeOf(context), []const u8)->bool) -> bool { +pub fn formatValue(value: var, context: var, output: fn(@typeOf(context), []const u8)->%void) -> %void { const T = @typeOf(value); switch (@typeId(T)) { builtin.TypeId.Int => { @@ -203,8 +193,7 @@ pub fn formatValue(value: var, context: var, output: fn(@typeOf(context), []cons } }, builtin.TypeId.Error => { - if (!output(context, "error.")) - return false; + %return output(context, "error."); return output(context, @errorName(value)); }, builtin.TypeId.Pointer => { @@ -223,27 +212,23 @@ 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)->bool) -> bool { +pub fn formatAsciiChar(c: u8, context: var, output: fn(@typeOf(context), []const u8)->%void) -> %void { return output(context, (&c)[0..1]); } pub fn formatBuf(buf: []const u8, width: usize, - context: var, output: fn(@typeOf(context), []const u8)->bool) -> bool + context: var, output: fn(@typeOf(context), []const u8)->%void) -> %void { - if (!output(context, buf)) - return false; + %return output(context, buf); - var leftover_padding = if (width > buf.len) (width - buf.len) else return true; + var leftover_padding = if (width > buf.len) (width - buf.len) else return; const pad_byte: u8 = ' '; while (leftover_padding > 0) : (leftover_padding -= 1) { - if (!output(context, (&pad_byte)[0..1])) - return false; + %return output(context, (&pad_byte)[0..1]); } - - return true; } -pub fn formatFloat(value: var, context: var, output: fn(@typeOf(context), []const u8)->bool) -> bool { +pub fn formatFloat(value: var, context: var, output: fn(@typeOf(context), []const u8)->%void) -> %void { var x = f64(value); // Errol doesn't handle these special cases. @@ -251,8 +236,7 @@ pub fn formatFloat(value: var, context: var, output: fn(@typeOf(context), []cons return output(context, "NaN"); } if (math.signbit(x)) { - if (!output(context, "-")) - return false; + %return output(context, "-"); x = -x; } if (math.isPositiveInf(x)) { @@ -264,34 +248,27 @@ pub fn formatFloat(value: var, context: var, output: fn(@typeOf(context), []cons var buffer: [32]u8 = undefined; const float_decimal = errol3(x, buffer[0..]); - if (!output(context, float_decimal.digits[0..1])) - return false; - if (!output(context, ".")) - return false; + %return output(context, float_decimal.digits[0..1]); + %return output(context, "."); if (float_decimal.digits.len > 1) { const num_digits = if (@typeOf(value) == f32) { math.min(usize(9), float_decimal.digits.len) } else { float_decimal.digits.len }; - if (!output(context, float_decimal.digits[1 .. num_digits])) - return false; + %return output(context, float_decimal.digits[1 .. num_digits]); } else { - if (!output(context, "0")) - return false; + %return output(context, "0"); } if (float_decimal.exp != 1) { - if (!output(context, "e")) - return false; - if (!formatInt(float_decimal.exp - 1, 10, false, 0, context, output)) - return false; + %return output(context, "e"); + %return formatInt(float_decimal.exp - 1, 10, false, 0, context, output); } - return true; } pub fn formatInt(value: var, base: u8, uppercase: bool, width: usize, - context: var, output: fn(@typeOf(context), []const u8)->bool) -> bool + context: var, output: fn(@typeOf(context), []const u8)->%void) -> %void { if (@typeOf(value).is_signed) { return formatIntSigned(value, base, uppercase, width, context, output); @@ -301,13 +278,12 @@ pub fn formatInt(value: var, base: u8, uppercase: bool, width: usize, } fn formatIntSigned(value: var, base: u8, uppercase: bool, width: usize, - context: var, output: fn(@typeOf(context), []const u8)->bool) -> bool + context: var, output: fn(@typeOf(context), []const u8)->%void) -> %void { const uint = @IntType(false, @typeOf(value).bit_count); if (value < 0) { const minus_sign: u8 = '-'; - if (!output(context, (&minus_sign)[0..1])) - return false; + %return 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); @@ -315,8 +291,7 @@ fn formatIntSigned(value: var, base: u8, uppercase: bool, width: usize, return formatIntUnsigned(uint(value), base, uppercase, width, context, output); } else { const plus_sign: u8 = '+'; - if (!output(context, (&plus_sign)[0..1])) - return false; + %return 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); @@ -324,7 +299,7 @@ fn formatIntSigned(value: var, base: u8, uppercase: bool, width: usize, } fn formatIntUnsigned(value: var, base: u8, uppercase: bool, width: usize, - context: var, output: fn(@typeOf(context), []const u8)->bool) -> bool + context: var, output: fn(@typeOf(context), []const u8)->%void) -> %void { // max_int_digits accounts for the minus sign. when printing an unsigned // number we don't need to do that. @@ -348,8 +323,7 @@ fn formatIntUnsigned(value: var, base: u8, uppercase: bool, width: usize, const zero_byte: u8 = '0'; var leftover_padding = padding - index; while (true) { - if (!output(context, (&zero_byte)[0..1])) - return false; + %return output(context, (&zero_byte)[0..1]); leftover_padding -= 1; if (leftover_padding == 0) break; @@ -368,17 +342,16 @@ 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); + %%formatInt(value, base, uppercase, width, &context, formatIntCallback); return context.index; } const FormatIntBuf = struct { out_buf: []u8, index: usize, }; -fn formatIntCallback(context: &FormatIntBuf, bytes: []const u8) -> bool { +fn formatIntCallback(context: &FormatIntBuf, bytes: []const u8) -> %void { mem.copy(u8, context.out_buf[context.index..], bytes); context.index += bytes.len; - return true; } pub fn parseInt(comptime T: type, buf: []const u8, radix: u8) -> %T { @@ -440,28 +413,27 @@ const BufPrintContext = struct { remaining: []u8, }; -fn bufPrintWrite(context: &BufPrintContext, bytes: []const u8) -> bool { +fn bufPrintWrite(context: &BufPrintContext, bytes: []const u8) -> %void { mem.copy(u8, context.remaining, bytes); context.remaining = context.remaining[bytes.len..]; - return true; } pub fn bufPrint(buf: []u8, comptime fmt: []const u8, args: ...) -> []u8 { var context = BufPrintContext { .remaining = buf, }; - _ = format(&context, bufPrintWrite, fmt, args); + %%format(&context, bufPrintWrite, fmt, args); return buf[0..buf.len - context.remaining.len]; } pub fn allocPrint(allocator: &mem.Allocator, comptime fmt: []const u8, args: ...) -> %[]u8 { var size: usize = 0; - _ = format(&size, countSize, fmt, args); + // Cannot fail because `countSize` cannot fail. + %%format(&size, countSize, fmt, args); const buf = %return allocator.alloc(u8, size); return bufPrint(buf, fmt, args); } -fn countSize(size: &usize, bytes: []const u8) -> bool { +fn countSize(size: &usize, bytes: []const u8) -> %void { *size += bytes.len; - return true; } test "buf print int" { diff --git a/std/heap.zig b/std/heap.zig new file mode 100644 index 0000000000..b654f28a74 --- /dev/null +++ b/std/heap.zig @@ -0,0 +1,158 @@ +const debug = @import("debug.zig"); +const assert = debug.assert; +const mem = @import("mem.zig"); +const os = @import("os/index.zig"); +const builtin = @import("builtin"); +const Os = builtin.Os; +const c = @import("c/index.zig"); + +const Allocator = mem.Allocator; + +error OutOfMemory; + +pub var c_allocator = Allocator { + .allocFn = cAlloc, + .reallocFn = cRealloc, + .freeFn = cFree, +}; + +fn cAlloc(self: &Allocator, n: usize, alignment: usize) -> %[]u8 { + if (c.malloc(usize(n))) |mem| { + @ptrCast(&u8, mem)[0..n] + } else { + error.OutOfMemory + } +} + +fn cRealloc(self: &Allocator, old_mem: []u8, new_size: usize, alignment: usize) -> %[]u8 { + if (new_size <= old_mem.len) { + old_mem[0..new_size] + } else { + const old_ptr = @ptrCast(&c_void, old_mem.ptr); + if (c.realloc(old_ptr, usize(new_size))) |mem| { + @ptrCast(&u8, mem)[0..new_size] + } else { + error.OutOfMemory + } + } +} + +fn cFree(self: &Allocator, old_mem: []u8) { + const old_ptr = @ptrCast(&c_void, old_mem.ptr); + c.free(old_ptr); +} + +pub const IncrementingAllocator = struct { + allocator: Allocator, + bytes: []u8, + end_index: usize, + heap_handle: if (builtin.os == Os.windows) os.windows.HANDLE else void, + + fn init(capacity: usize) -> %IncrementingAllocator { + switch (builtin.os) { + Os.linux, Os.darwin, Os.macosx, Os.ios => { + const p = os.posix; + const addr = p.mmap(null, capacity, p.PROT_READ|p.PROT_WRITE, + p.MAP_PRIVATE|p.MAP_ANONYMOUS|p.MAP_NORESERVE, -1, 0); + if (addr == p.MAP_FAILED) { + return error.OutOfMemory; + } + return IncrementingAllocator { + .allocator = Allocator { + .allocFn = alloc, + .reallocFn = realloc, + .freeFn = free, + }, + .bytes = @intToPtr(&u8, addr)[0..capacity], + .end_index = 0, + .heap_handle = {}, + }; + }, + Os.windows => { + const heap_handle = os.windows.GetProcessHeap() ?? return error.OutOfMemory; + const ptr = os.windows.HeapAlloc(heap_handle, 0, capacity) ?? return error.OutOfMemory; + return IncrementingAllocator { + .allocator = Allocator { + .allocFn = alloc, + .reallocFn = realloc, + .freeFn = free, + }, + .bytes = @ptrCast(&u8, ptr)[0..capacity], + .end_index = 0, + .heap_handle = heap_handle, + }; + }, + else => @compileError("Unsupported OS"), + } + } + + fn deinit(self: &IncrementingAllocator) { + switch (builtin.os) { + Os.linux, Os.darwin, Os.macosx, Os.ios => { + _ = os.posix.munmap(self.bytes.ptr, self.bytes.len); + }, + Os.windows => { + _ = os.windows.HeapFree(self.heap_handle, 0, @ptrCast(os.windows.LPVOID, self.bytes.ptr)); + }, + else => @compileError("Unsupported OS"), + } + } + + fn reset(self: &IncrementingAllocator) { + self.end_index = 0; + } + + fn bytesLeft(self: &const IncrementingAllocator) -> usize { + return self.bytes.len - self.end_index; + } + + fn alloc(allocator: &Allocator, n: usize, alignment: usize) -> %[]u8 { + const self = @fieldParentPtr(IncrementingAllocator, "allocator", allocator); + const addr = @ptrToInt(&self.bytes[self.end_index]); + const rem = @rem(addr, alignment); + const march_forward_bytes = if (rem == 0) 0 else (alignment - rem); + const adjusted_index = self.end_index + march_forward_bytes; + const new_end_index = adjusted_index + n; + if (new_end_index > self.bytes.len) { + return error.OutOfMemory; + } + const result = self.bytes[adjusted_index .. new_end_index]; + self.end_index = new_end_index; + return result; + } + + fn realloc(allocator: &Allocator, old_mem: []u8, new_size: usize, alignment: usize) -> %[]u8 { + if (new_size <= old_mem.len) { + return old_mem[0..new_size]; + } else { + const result = %return alloc(allocator, new_size, alignment); + mem.copy(u8, result, old_mem); + return result; + } + } + + fn free(allocator: &Allocator, bytes: []u8) { + // Do nothing. That's the point of an incrementing allocator. + } +}; + +test "IncrementingAllocator" { + const total_bytes = 100 * 1024 * 1024; + var inc_allocator = %%IncrementingAllocator.init(total_bytes); + defer inc_allocator.deinit(); + + const allocator = &inc_allocator.allocator; + const slice = %%allocator.alloc(&i32, 100); + + for (slice) |*item, i| { + *item = %%allocator.create(i32); + **item = i32(i); + } + + assert(inc_allocator.bytesLeft() == total_bytes - @sizeOf(i32) * 100 - @sizeOf(usize) * 100); + + inc_allocator.reset(); + + assert(inc_allocator.bytesLeft() == total_bytes); +} + diff --git a/std/index.zig b/std/index.zig index 7c8f33473a..8033eee142 100644 --- a/std/index.zig +++ b/std/index.zig @@ -15,6 +15,7 @@ pub const elf = @import("elf.zig"); pub const empty_import = @import("empty.zig"); pub const endian = @import("endian.zig"); pub const fmt = @import("fmt/index.zig"); +pub const heap = @import("heap.zig"); pub const io = @import("io.zig"); pub const math = @import("math/index.zig"); pub const mem = @import("mem.zig"); @@ -45,6 +46,7 @@ test "std" { _ = @import("io.zig"); _ = @import("math/index.zig"); _ = @import("mem.zig"); + _ = @import("heap.zig"); _ = @import("net.zig"); _ = @import("os/index.zig"); _ = @import("rand.zig"); diff --git a/std/io.zig b/std/io.zig index 943f78e875..9778f38239 100644 --- a/std/io.zig +++ b/std/io.zig @@ -1,3 +1,4 @@ +const std = @import("index.zig"); const builtin = @import("builtin"); const Os = builtin.Os; const system = switch(builtin.os) { @@ -6,41 +7,19 @@ const system = switch(builtin.os) { Os.windows => @import("os/windows/index.zig"), else => @compileError("Unsupported OS"), }; -const c = @import("c/index.zig"); +const c = std.c; -const math = @import("math/index.zig"); -const debug = @import("debug.zig"); +const math = std.math; +const debug = std.debug; const assert = debug.assert; -const os = @import("os/index.zig"); -const mem = @import("mem.zig"); -const Buffer = @import("buffer.zig").Buffer; -const fmt = @import("fmt/index.zig"); +const os = std.os; +const mem = std.mem; +const Buffer = std.Buffer; +const fmt = std.fmt; const is_posix = builtin.os != builtin.Os.windows; const is_windows = builtin.os == builtin.Os.windows; -pub var stdin = InStream { - .fd = if (is_posix) system.STDIN_FILENO else {}, - .handle_id = if (is_windows) system.STD_INPUT_HANDLE else {}, - .handle = if (is_windows) null else {}, -}; - -pub var stdout = OutStream { - .fd = if (is_posix) system.STDOUT_FILENO else {}, - .handle_id = if (is_windows) system.STD_OUTPUT_HANDLE else {}, - .handle = if (is_windows) null else {}, - .buffer = undefined, - .index = 0, -}; - -pub var stderr = OutStream { - .fd = if (is_posix) system.STDERR_FILENO else {}, - .handle_id = if (is_windows) system.STD_ERROR_HANDLE else {}, - .handle = if (is_windows) null else {}, - .buffer = undefined, - .index = 0, -}; - /// The function received invalid input at runtime. An Invalid error means a /// bug in the program that called the function. error Invalid; @@ -63,18 +42,72 @@ error PathNotFound; error OutOfMemory; error Unseekable; error EndOfFile; -error NoStdHandles; -pub const OutStream = struct { - fd: if (is_posix) i32 else void, - handle_id: if (is_windows) system.DWORD else void, - handle: if (is_windows) ?system.HANDLE else void, - buffer: [os.page_size]u8, - index: usize, +pub fn getStdErr() -> %File { + const handle = if (is_windows) { + %return os.windowsGetStdHandle(system.STD_ERROR_HANDLE) + } else if (is_posix) { + system.STDERR_FILENO + } else { + unreachable + }; + return File.openHandle(handle); +} - /// Calls ::openMode with 0o666 for the mode. - pub fn open(path: []const u8, allocator: ?&mem.Allocator) -> %OutStream { - return openMode(path, 0o666, allocator); +pub fn getStdOut() -> %File { + const handle = if (is_windows) { + %return os.windowsGetStdHandle(system.STD_OUTPUT_HANDLE) + } else if (is_posix) { + system.STDOUT_FILENO + } else { + unreachable + }; + return File.openHandle(handle); +} + +pub fn getStdIn() -> %File { + const handle = if (is_windows) { + %return os.windowsGetStdHandle(system.STD_INPUT_HANDLE) + } else if (is_posix) { + system.STDIN_FILENO + } else { + unreachable + }; + return File.openHandle(handle); +} + +pub const File = struct { + /// The OS-specific file descriptor or file handle. + handle: os.FileHandle, + + /// A file has the `InStream` trait + in_stream: InStream, + + /// A file has the `OutStream` trait + out_stream: OutStream, + + /// `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 = %return os.posixOpen(path, flags, 0, allocator); + return openHandle(fd); + } else if (is_windows) { + const handle = %return 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); } @@ -83,286 +116,51 @@ pub const OutStream = struct { /// 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 openMode(path: []const u8, mode: usize, allocator: ?&mem.Allocator) -> %OutStream { + 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 = %return os.posixOpen(path, flags, mode, allocator); - return OutStream { - .fd = fd, - .handle = {}, - .handle_id = {}, - .index = 0, - .buffer = undefined, - }; + return openHandle(fd); } else if (is_windows) { const handle = %return 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 OutStream { - .fd = {}, - .handle = handle, - .handle_id = undefined, - .index = 0, - .buffer = undefined, - }; - + return openHandle(handle); } else { unreachable; } } - pub fn writeByte(self: &OutStream, b: u8) -> %void { - if (self.buffer.len == self.index) %return self.flush(); - self.buffer[self.index] = b; - self.index += 1; - } - - pub fn write(self: &OutStream, bytes: []const u8) -> %void { - if (bytes.len >= self.buffer.len) { - %return self.flush(); - return self.unbufferedWrite(bytes); - } - - var src_index: usize = 0; - - while (src_index < bytes.len) { - const dest_space_left = self.buffer.len - self.index; - const copy_amt = math.min(dest_space_left, bytes.len - src_index); - mem.copy(u8, self.buffer[self.index..], bytes[src_index..src_index + copy_amt]); - self.index += copy_amt; - assert(self.index <= self.buffer.len); - if (self.index == self.buffer.len) { - %return self.flush(); - } - src_index += copy_amt; - } - } - - /// Calls print and then flushes the buffer. - pub fn printf(self: &OutStream, comptime format: []const u8, args: ...) -> %void { - %return self.print(format, args); - %return self.flush(); - } - - /// Does not flush the buffer. - pub fn print(self: &OutStream, comptime format: []const u8, args: ...) -> %void { - var context = PrintContext { - .self = self, - .result = {}, + pub fn openHandle(handle: os.FileHandle) -> File { + return File { + .handle = handle, + .out_stream = OutStream { + .writeFn = writeFn, + }, + .in_stream = InStream { + .readFn = readFn, + }, }; - _ = fmt.format(&context, printOutput, format, args); - return context.result; - } - const PrintContext = struct { - self: &OutStream, - result: %void, - }; - fn printOutput(context: &PrintContext, bytes: []const u8) -> bool { - context.self.write(bytes) %% |err| { - context.result = err; - return false; - }; - return true; } - pub fn flush(self: &OutStream) -> %void { - if (self.index == 0) - return; - - %return self.unbufferedWrite(self.buffer[0..self.index]); - self.index = 0; - } - - pub fn close(self: &OutStream) { - assert(self.index == 0); // unflushed buffer - if (is_posix) { - os.posixClose(self.fd); - } else if (is_windows) { - os.windowsClose(%%self.getHandle()); - } else { - unreachable; - } - } - - pub fn isTty(self: &OutStream) -> %bool { - if (is_posix) { - if (builtin.link_libc) { - return c.isatty(self.fd) != 0; - } else { - return system.isatty(self.fd); - } - } else if (is_windows) { - return os.windowsIsTty(%return self.getHandle()); - } else { - unreachable; - } - } - - fn getHandle(self: &OutStream) -> %system.HANDLE { - if (self.handle) |handle| return handle; - if (system.GetStdHandle(self.handle_id)) |handle| { - if (handle == system.INVALID_HANDLE_VALUE) { - const err = system.GetLastError(); - return switch (err) { - else => os.unexpectedErrorWindows(err), - }; - } - self.handle = handle; - return handle; - } else { - return error.NoStdHandles; - } - } - - fn unbufferedWrite(self: &OutStream, bytes: []const u8) -> %void { - if (is_posix) { - %return os.posixWrite(self.fd, bytes); - } else if (is_windows) { - const handle = %return self.getHandle(); - %return os.windowsWrite(handle, bytes); - } else { - @compileError("Unsupported OS"); - } - } - -}; - -// TODO created a BufferedInStream struct and move some of this code there -// BufferedInStream API goes on top of minimal InStream API. -pub const InStream = struct { - fd: if (is_posix) i32 else void, - handle_id: if (is_windows) system.DWORD else void, - handle: if (is_windows) ?system.HANDLE else void, - - /// `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 open(path: []const u8, allocator: ?&mem.Allocator) -> %InStream { - if (is_posix) { - const flags = system.O_LARGEFILE|system.O_RDONLY; - const fd = %return os.posixOpen(path, flags, 0, allocator); - return InStream { - .fd = fd, - .handle_id = {}, - .handle = {}, - }; - } else if (is_windows) { - const handle = %return os.windowsOpen(path, system.GENERIC_READ, system.FILE_SHARE_READ, - system.OPEN_EXISTING, system.FILE_ATTRIBUTE_NORMAL, allocator); - return InStream { - .fd = {}, - .handle_id = undefined, - .handle = handle, - }; - } else { - unreachable; - } - } /// Upon success, the stream is in an uninitialized state. To continue using it, /// you must use the open() function. - pub fn close(self: &InStream) { - if (is_posix) { - os.posixClose(self.fd); - } else if (is_windows) { - os.windowsClose(%%self.getHandle()); - } else { - unreachable; - } + pub fn close(self: &File) { + os.close(self.handle); + self.handle = undefined; } - /// Returns the number of bytes read. If the number read is smaller than buf.len, then - /// the stream reached End Of File. - pub fn read(self: &InStream, buf: []u8) -> %usize { - if (is_posix) { - var index: usize = 0; - while (index < buf.len) { - const amt_read = system.read(self.fd, &buf[index], buf.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 (amt_read == 0) return index; - index += amt_read; - } - return index; - } else if (is_windows) { - const handle = %return self.getHandle(); - var index: usize = 0; - while (index < buf.len) { - const want_read_count = system.DWORD(math.min(system.DWORD(@maxValue(system.DWORD)), buf.len - index)); - var amt_read: system.DWORD = undefined; - if (system.ReadFile(handle, @ptrCast(&c_void, &buf[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 (amt_read == 0) return index; - index += amt_read; - } - return index; - } else { - unreachable; - } + /// Calls `os.isTty` on `self.handle`. + pub fn isTty(self: &File) -> bool { + return os.isTty(self.handle); } - pub fn readNoEof(is: &InStream, buf: []u8) -> %void { - const amt_read = %return is.read(buf); - if (amt_read < buf.len) return error.EndOfFile; - } - - pub fn readByte(is: &InStream) -> %u8 { - var result: [1]u8 = undefined; - %return is.readNoEof(result[0..]); - return result[0]; - } - - pub fn readByteSigned(is: &InStream) -> %i8 { - var result: [1]i8 = undefined; - %return is.readNoEof(([]u8)(result[0..])); - return result[0]; - } - - pub fn readIntLe(is: &InStream, comptime T: type) -> %T { - is.readInt(false, T) - } - - pub fn readIntBe(is: &InStream, comptime T: type) -> %T { - is.readInt(true, T) - } - - pub fn readInt(is: &InStream, is_be: bool, comptime T: type) -> %T { - var bytes: [@sizeOf(T)]u8 = undefined; - %return is.readNoEof(bytes[0..]); - return mem.readInt(bytes, T, is_be); - } - - pub fn readVarInt(is: &InStream, is_be: bool, 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]; - %return is.readNoEof(input_slice); - return mem.readInt(input_slice, T, is_be); - } - - pub fn seekForward(is: &InStream, amount: isize) -> %void { + pub fn seekForward(self: &File, amount: isize) -> %void { switch (builtin.os) { Os.linux, Os.darwin => { - const result = system.lseek(is.fd, amount, system.SEEK_CUR); + const result = system.lseek(self.handle, amount, system.SEEK_CUR); const err = system.getErrno(result); if (err > 0) { return switch (err) { @@ -379,10 +177,10 @@ pub const InStream = struct { } } - pub fn seekTo(is: &InStream, pos: usize) -> %void { + pub fn seekTo(self: &File, pos: usize) -> %void { switch (builtin.os) { Os.linux, Os.darwin => { - const result = system.lseek(is.fd, @bitCast(isize, pos), system.SEEK_SET); + const result = system.lseek(self.handle, @bitCast(isize, pos), system.SEEK_SET); const err = system.getErrno(result); if (err > 0) { return switch (err) { @@ -399,10 +197,10 @@ pub const InStream = struct { } } - pub fn getPos(is: &InStream) -> %usize { + pub fn getPos(self: &File) -> %usize { switch (builtin.os) { Os.linux, Os.darwin => { - const result = system.lseek(is.fd, 0, system.SEEK_CUR); + const result = system.lseek(self.handle, 0, system.SEEK_CUR); const err = system.getErrno(result); if (err > 0) { return switch (err) { @@ -420,9 +218,9 @@ pub const InStream = struct { } } - pub fn getEndPos(is: &InStream) -> %usize { + pub fn getEndPos(self: &File) -> %usize { var stat: system.Stat = undefined; - const err = system.getErrno(system.fstat(is.fd, &stat)); + const err = system.getErrno(system.fstat(self.handle, &stat)); if (err > 0) { return switch (err) { system.EBADF => error.BadFd, @@ -434,89 +232,217 @@ pub const InStream = struct { return usize(stat.size); } - pub fn readAll(is: &InStream, buf: &Buffer) -> %void { - %return buf.resize(os.page_size); - - var actual_buf_len: usize = 0; - while (true) { - const dest_slice = buf.toSlice()[actual_buf_len..]; - const bytes_read = %return is.read(dest_slice); - actual_buf_len += bytes_read; - - if (bytes_read != dest_slice.len) { - return buf.resize(actual_buf_len); - } - - %return buf.resize(actual_buf_len + os.page_size); - } - } - - pub fn readLine(is: &InStream, buf: &Buffer) -> %void { - %return buf.resize(0); - - while (true) { - var byte: u8 = %return is.readByte(); - %return buf.appendByte(byte); - - if (buf.endsWith(os.line_sep)) { - break; - } - } - } - - pub fn isTty(self: &InStream) -> %bool { + fn readFn(in_stream: &InStream, buffer: []u8) -> %usize { + const self = @fieldParentPtr(File, "in_stream", in_stream); if (is_posix) { - if (builtin.link_libc) { - return c.isatty(self.fd) != 0; - } else { - return system.isatty(self.fd); + 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 (amt_read == 0) return index; + index += amt_read; } + return index; } else if (is_windows) { - return os.windowsIsTty(%return self.getHandle()); + 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 (amt_read == 0) return index; + index += amt_read; + } + return index; + } else { + unreachable; + } + } + + fn writeFn(out_stream: &OutStream, bytes: []const u8) -> %void { + const self = @fieldParentPtr(File, "out_stream", out_stream); + if (is_posix) { + %return os.posixWrite(self.handle, bytes); + } else if (is_windows) { + %return os.windowsWrite(self.handle, bytes); } else { @compileError("Unsupported OS"); } } - fn getHandle(self: &InStream) -> %system.HANDLE { - if (self.handle) |handle| return handle; - if (system.GetStdHandle(self.handle_id)) |handle| { - if (handle == system.INVALID_HANDLE_VALUE) { - const err = system.GetLastError(); - return switch (err) { - else => os.unexpectedErrorWindows(err), - }; - } - self.handle = handle; - return handle; - } else { - return error.NoStdHandles; - } - } }; -pub fn openSelfExe() -> %InStream { - switch (builtin.os) { - Os.linux => { - return InStream.open("/proc/self/exe", null); - }, - Os.darwin => { - debug.panic("TODO: openSelfExe on Darwin"); - }, - else => @compileError("Unsupported OS"), - } -} - /// `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. +/// 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 { - // TODO have an unbuffered File abstraction and use that here. - // Then a buffered out stream abstraction can go on top of that for - // use cases like stdout and stderr. - var out_stream = %return OutStream.open(path, allocator); - defer out_stream.close(); - %return out_stream.write(data); - %return out_stream.flush(); + var file = %return File.openWrite(path, allocator); + defer file.close(); + %return file.out_stream.write(data); } + +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()` woould 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 { + %return buffer.resize(0); + + var actual_buf_len: usize = 0; + while (true) { + const dest_slice = buffer.toSlice()[actual_buf_len..]; + const bytes_read = %return 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; + %return buffer.resize(new_buf_size); + } + } + + /// 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(); + + %return self.readAllBuffer(self, &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 { + %return buf.resize(0); + + while (true) { + var byte: u8 = %return self.readByte(); + + if (byte == delimiter) { + return; + } + + if (buf.len() == max_size) { + return error.StreamTooLong; + } + + %return buf.appendByte(byte); + } + } + + /// 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(); + + %return 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 = %return 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; + %return 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, %return self.readByte()); + } + + pub fn readIntLe(self: &InStream, comptime T: type) -> %T { + return self.readInt(false, T); + } + + pub fn readIntBe(self: &InStream, comptime T: type) -> %T { + return self.readInt(true, T); + } + + pub fn readInt(self: &InStream, is_be: bool, comptime T: type) -> %T { + var bytes: [@sizeOf(T)]u8 = undefined; + %return self.readNoEof(bytes[0..]); + return mem.readInt(bytes, T, is_be); + } + + pub fn readVarInt(self: &InStream, is_be: bool, 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]; + %return self.readNoEof(input_slice); + return mem.readInt(input_slice, T, is_be); + } + + +}; + +pub const OutStream = struct { + writeFn: fn(self: &OutStream, bytes: []const u8) -> %void, + + pub fn print(self: &OutStream, comptime format: []const u8, args: ...) -> %void { + return std.fmt.format(self, self.writeFn, format, args); + } + + pub fn write(self: &OutStream, 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); + } +}; diff --git a/std/mem.zig b/std/mem.zig index 6f4a1e16e0..e37ccf011d 100644 --- a/std/mem.zig +++ b/std/mem.zig @@ -1,16 +1,9 @@ const debug = @import("debug.zig"); const assert = debug.assert; const math = @import("math/index.zig"); -const os = @import("os/index.zig"); -const io = @import("io.zig"); -const builtin = @import("builtin"); -const Os = builtin.Os; -const c = @import("c/index.zig"); pub const Cmp = math.Cmp; -error OutOfMemory; - pub const Allocator = struct { /// Allocate byte_count bytes and return them in a slice, with the /// slicer's pointer aligned at least to alignment bytes. @@ -85,151 +78,6 @@ pub const Allocator = struct { } }; -pub var c_allocator = Allocator { - .allocFn = cAlloc, - .reallocFn = cRealloc, - .freeFn = cFree, -}; - -fn cAlloc(self: &Allocator, n: usize, alignment: usize) -> %[]u8 { - if (c.malloc(usize(n))) |mem| { - @ptrCast(&u8, mem)[0..n] - } else { - error.OutOfMemory - } -} - -fn cRealloc(self: &Allocator, old_mem: []u8, new_size: usize, alignment: usize) -> %[]u8 { - if (new_size <= old_mem.len) { - old_mem[0..new_size] - } else { - const old_ptr = @ptrCast(&c_void, old_mem.ptr); - if (c.realloc(old_ptr, usize(new_size))) |mem| { - @ptrCast(&u8, mem)[0..new_size] - } else { - error.OutOfMemory - } - } -} - -fn cFree(self: &Allocator, old_mem: []u8) { - const old_ptr = @ptrCast(&c_void, old_mem.ptr); - c.free(old_ptr); -} - -pub const IncrementingAllocator = struct { - allocator: Allocator, - bytes: []u8, - end_index: usize, - heap_handle: if (builtin.os == Os.windows) os.windows.HANDLE else void, - - fn init(capacity: usize) -> %IncrementingAllocator { - switch (builtin.os) { - Os.linux, Os.darwin, Os.macosx, Os.ios => { - const p = os.posix; - const addr = p.mmap(null, capacity, p.PROT_READ|p.PROT_WRITE, - p.MAP_PRIVATE|p.MAP_ANONYMOUS|p.MAP_NORESERVE, -1, 0); - if (addr == p.MAP_FAILED) { - return error.OutOfMemory; - } - return IncrementingAllocator { - .allocator = Allocator { - .allocFn = alloc, - .reallocFn = realloc, - .freeFn = free, - }, - .bytes = @intToPtr(&u8, addr)[0..capacity], - .end_index = 0, - .heap_handle = {}, - }; - }, - Os.windows => { - const heap_handle = os.windows.GetProcessHeap() ?? return error.OutOfMemory; - const ptr = os.windows.HeapAlloc(heap_handle, 0, capacity) ?? return error.OutOfMemory; - return IncrementingAllocator { - .allocator = Allocator { - .allocFn = alloc, - .reallocFn = realloc, - .freeFn = free, - }, - .bytes = @ptrCast(&u8, ptr)[0..capacity], - .end_index = 0, - .heap_handle = heap_handle, - }; - }, - else => @compileError("Unsupported OS"), - } - } - - fn deinit(self: &IncrementingAllocator) { - switch (builtin.os) { - Os.linux, Os.darwin, Os.macosx, Os.ios => { - _ = os.posix.munmap(self.bytes.ptr, self.bytes.len); - }, - Os.windows => { - _ = os.windows.HeapFree(self.heap_handle, 0, @ptrCast(os.windows.LPVOID, self.bytes.ptr)); - }, - else => @compileError("Unsupported OS"), - } - } - - fn reset(self: &IncrementingAllocator) { - self.end_index = 0; - } - - fn bytesLeft(self: &const IncrementingAllocator) -> usize { - return self.bytes.len - self.end_index; - } - - fn alloc(allocator: &Allocator, n: usize, alignment: usize) -> %[]u8 { - const self = @fieldParentPtr(IncrementingAllocator, "allocator", allocator); - const addr = @ptrToInt(&self.bytes[self.end_index]); - const rem = @rem(addr, alignment); - const march_forward_bytes = if (rem == 0) 0 else (alignment - rem); - const adjusted_index = self.end_index + march_forward_bytes; - const new_end_index = adjusted_index + n; - if (new_end_index > self.bytes.len) { - return error.OutOfMemory; - } - const result = self.bytes[adjusted_index .. new_end_index]; - self.end_index = new_end_index; - return result; - } - - fn realloc(allocator: &Allocator, old_mem: []u8, new_size: usize, alignment: usize) -> %[]u8 { - if (new_size <= old_mem.len) { - return old_mem[0..new_size]; - } else { - const result = %return alloc(allocator, new_size, alignment); - copy(u8, result, old_mem); - return result; - } - } - - fn free(allocator: &Allocator, bytes: []u8) { - // Do nothing. That's the point of an incrementing allocator. - } -}; - -test "mem.IncrementingAllocator" { - const total_bytes = 100 * 1024 * 1024; - var inc_allocator = %%IncrementingAllocator.init(total_bytes); - defer inc_allocator.deinit(); - - const allocator = &inc_allocator.allocator; - const slice = %%allocator.alloc(&i32, 100); - - for (slice) |*item, i| { - *item = %%allocator.create(i32); - **item = i32(i); - } - - assert(inc_allocator.bytesLeft() == total_bytes - @sizeOf(i32) * 100 - @sizeOf(usize) * 100); - - inc_allocator.reset(); - - assert(inc_allocator.bytesLeft() == total_bytes); -} /// Copy all of source into dest at position 0. /// dest.len must be >= source.len. diff --git a/std/os/child_process.zig b/std/os/child_process.zig index 32dc6d1172..6e8d49ef57 100644 --- a/std/os/child_process.zig +++ b/std/os/child_process.zig @@ -28,9 +28,9 @@ pub const ChildProcess = struct { pub allocator: &mem.Allocator, - pub stdin: ?&io.OutStream, - pub stdout: ?&io.InStream, - pub stderr: ?&io.InStream, + pub stdin: ?io.File, + pub stdout: ?io.File, + pub stderr: ?io.File, pub term: ?%Term, @@ -250,17 +250,17 @@ pub const ChildProcess = struct { } fn cleanupStreams(self: &ChildProcess) { - if (self.stdin) |stdin| { stdin.close(); self.allocator.destroy(stdin); self.stdin = null; } - if (self.stdout) |stdout| { stdout.close(); self.allocator.destroy(stdout); self.stdout = null; } - if (self.stderr) |stderr| { stderr.close(); self.allocator.destroy(stderr); self.stderr = null; } + if (self.stdin) |*stdin| { stdin.close(); self.stdin = null; } + if (self.stdout) |*stdout| { stdout.close(); self.stdout = null; } + if (self.stderr) |*stderr| { stderr.close(); self.stderr = null; } } fn cleanupAfterWait(self: &ChildProcess, status: i32) -> %Term { children_nodes.remove(&self.llnode); defer { - os.posixClose(self.err_pipe[0]); - os.posixClose(self.err_pipe[1]); + os.close(self.err_pipe[0]); + os.close(self.err_pipe[1]); }; // Write @maxValue(ErrInt) to the write end of the err_pipe. This is after @@ -310,7 +310,7 @@ pub const ChildProcess = struct { } else { undefined }; - defer { if (any_ignore) os.posixClose(dev_null_fd); }; + defer { if (any_ignore) os.close(dev_null_fd); }; var env_map_owned: BufMap = undefined; var we_own_env_map: bool = undefined; @@ -329,27 +329,6 @@ pub const ChildProcess = struct { const err_pipe = %return makePipe(); %defer destroyPipe(err_pipe); - const stdin_ptr = if (self.stdin_behavior == StdIo.Pipe) { - %return self.allocator.create(io.OutStream) - } else { - null - }; - %defer if (stdin_ptr) |ptr| self.allocator.destroy(ptr); - - const stdout_ptr = if (self.stdout_behavior == StdIo.Pipe) { - %return self.allocator.create(io.InStream) - } else { - null - }; - %defer if (stdout_ptr) |ptr| self.allocator.destroy(ptr); - - const stderr_ptr = if (self.stderr_behavior == StdIo.Pipe) { - %return self.allocator.create(io.InStream) - } else { - null - }; - %defer if (stderr_ptr) |ptr| self.allocator.destroy(ptr); - block_SIGCHLD(); const pid_result = posix.fork(); const pid_err = posix.getErrno(pid_result); @@ -390,46 +369,35 @@ pub const ChildProcess = struct { // we are the parent const pid = i32(pid_result); - if (stdin_ptr) |outstream| { - *outstream = io.OutStream { - .fd = stdin_pipe[1], - .handle = {}, - .handle_id = {}, - .buffer = undefined, - .index = 0, - }; + if (self.stdin_behavior == StdIo.Pipe) { + self.stdin = io.File.openHandle(stdin_pipe[1]); + } else { + self.stdin = null; } - if (stdout_ptr) |instream| { - *instream = io.InStream { - .fd = stdout_pipe[0], - .handle = {}, - .handle_id = {}, - }; + if (self.stdout_behavior == StdIo.Pipe) { + self.stdout = io.File.openHandle(stdout_pipe[0]); + } else { + self.stdout = null; } - if (stderr_ptr) |instream| { - *instream = io.InStream { - .fd = stderr_pipe[0], - .handle = {}, - .handle_id = {}, - }; + if (self.stderr_behavior == StdIo.Pipe) { + self.stderr = io.File.openHandle(stderr_pipe[0]); + } else { + self.stderr = null; } self.pid = pid; self.err_pipe = err_pipe; self.llnode = LinkedList(&ChildProcess).Node.init(self); self.term = null; - self.stdin = stdin_ptr; - self.stdout = stdout_ptr; - self.stderr = stderr_ptr; // TODO make this atomic so it works even with threads children_nodes.prepend(&self.llnode); restore_SIGCHLD(); - if (self.stdin_behavior == StdIo.Pipe) { os.posixClose(stdin_pipe[0]); } - if (self.stdout_behavior == StdIo.Pipe) { os.posixClose(stdout_pipe[1]); } - if (self.stderr_behavior == StdIo.Pipe) { os.posixClose(stderr_pipe[1]); } + if (self.stdin_behavior == StdIo.Pipe) { os.close(stdin_pipe[0]); } + if (self.stdout_behavior == StdIo.Pipe) { os.close(stdout_pipe[1]); } + if (self.stderr_behavior == StdIo.Pipe) { os.close(stderr_pipe[1]); } } fn spawnWindows(self: &ChildProcess) -> %void { @@ -509,27 +477,6 @@ pub const ChildProcess = struct { } %defer if (self.stdin_behavior == StdIo.Pipe) { windowsDestroyPipe(g_hChildStd_ERR_Rd, g_hChildStd_ERR_Wr); }; - const stdin_ptr = if (self.stdin_behavior == StdIo.Pipe) { - %return self.allocator.create(io.OutStream) - } else { - null - }; - %defer if (stdin_ptr) |ptr| self.allocator.destroy(ptr); - - const stdout_ptr = if (self.stdout_behavior == StdIo.Pipe) { - %return self.allocator.create(io.InStream) - } else { - null - }; - %defer if (stdout_ptr) |ptr| self.allocator.destroy(ptr); - - const stderr_ptr = if (self.stderr_behavior == StdIo.Pipe) { - %return self.allocator.create(io.InStream) - } else { - null - }; - %defer if (stderr_ptr) |ptr| self.allocator.destroy(ptr); - const cmd_line = %return windowsCreateCommandLine(self.allocator, self.argv); defer self.allocator.free(cmd_line); @@ -609,36 +556,25 @@ pub const ChildProcess = struct { } }; - if (stdin_ptr) |outstream| { - *outstream = io.OutStream { - .fd = {}, - .handle = g_hChildStd_IN_Wr, - .handle_id = undefined, - .buffer = undefined, - .index = 0, - }; + if (self.stdin_behavior == StdIo.Pipe) { + self.stdin = io.File.openHandle(g_hChildStd_IN_Wr); + } else { + self.stdin = null; } - if (stdout_ptr) |instream| { - *instream = io.InStream { - .fd = {}, - .handle = g_hChildStd_OUT_Rd, - .handle_id = undefined, - }; + if (self.stdout_behavior == StdIo.Pipe) { + self.stdout = io.File.openHandle(g_hChildStd_OUT_Rd); + } else { + self.stdout = null; } - if (stderr_ptr) |instream| { - *instream = io.InStream { - .fd = {}, - .handle = g_hChildStd_ERR_Rd, - .handle_id = undefined, - }; + if (self.stderr_behavior == StdIo.Pipe) { + self.stderr = io.File.openHandle(g_hChildStd_ERR_Rd); + } else { + self.stderr = null; } self.handle = piProcInfo.hProcess; self.thread_handle = piProcInfo.hThread; self.term = null; - self.stdin = stdin_ptr; - self.stdout = stdout_ptr; - self.stderr = stderr_ptr; if (self.stdin_behavior == StdIo.Pipe) { os.windowsClose(??g_hChildStd_IN_Rd); } if (self.stderr_behavior == StdIo.Pipe) { os.windowsClose(??g_hChildStd_ERR_Wr); } @@ -648,7 +584,7 @@ pub const ChildProcess = struct { fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) -> %void { switch (stdio) { StdIo.Pipe => %return os.posixDup2(pipe_fd, std_fileno), - StdIo.Close => os.posixClose(std_fileno), + StdIo.Close => os.close(std_fileno), StdIo.Inherit => {}, StdIo.Ignore => %return os.posixDup2(dev_null_fd, std_fileno), } @@ -771,8 +707,8 @@ fn makePipe() -> %[2]i32 { } fn destroyPipe(pipe: &const [2]i32) { - os.posixClose((*pipe)[0]); - os.posixClose((*pipe)[1]); + os.close((*pipe)[0]); + os.close((*pipe)[1]); } // Child of fork calls this to report an error to the fork parent. diff --git a/std/os/index.zig b/std/os/index.zig index 9de6ed39b5..72ded6ccd8 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -1,6 +1,7 @@ const builtin = @import("builtin"); const Os = builtin.Os; const is_windows = builtin.os == Os.windows; +const os = this; pub const windows = @import("windows/index.zig"); pub const darwin = @import("darwin.zig"); @@ -26,14 +27,14 @@ pub const UserInfo = @import("get_user_id.zig").UserInfo; pub const getUserInfo = @import("get_user_id.zig").getUserInfo; const windows_util = @import("windows/util.zig"); -pub const windowsClose = windows_util.windowsClose; pub const windowsWaitSingle = windows_util.windowsWaitSingle; pub const windowsWrite = windows_util.windowsWrite; -pub const windowsIsTty = windows_util.windowsIsTty; pub const windowsIsCygwinPty = windows_util.windowsIsCygwinPty; pub const windowsOpen = windows_util.windowsOpen; pub const createWindowsEnvBlock = windows_util.createWindowsEnvBlock; +pub const FileHandle = if (is_windows) windows.HANDLE else i32; + const debug = @import("../debug.zig"); const assert = debug.assert; @@ -88,7 +89,7 @@ pub fn getRandomBytes(buf: []u8) -> %void { Os.darwin, Os.macosx, Os.ios => { const fd = %return posixOpen("/dev/urandom", posix.O_RDONLY|posix.O_CLOEXEC, 0, null); - defer posixClose(fd); + defer close(fd); %return posixRead(fd, buf); }, @@ -165,14 +166,18 @@ pub coldcc fn exit(status: i32) -> noreturn { } } -/// Calls POSIX close, and keeps trying if it gets interrupted. -pub fn posixClose(fd: i32) { - while (true) { - const err = posix.getErrno(posix.close(fd)); - if (err == posix.EINTR) { - continue; - } else { - return; +/// Closes the file handle. Keeps trying if it gets interrupted by a signal. +pub fn close(handle: FileHandle) { + if (is_windows) { + windows_util.windowsClose(handle); + } else { + while (true) { + const err = posix.getErrno(posix.close(handle)); + if (err == posix.EINTR) { + continue; + } else { + return; + } } } } @@ -716,19 +721,18 @@ pub fn copyFileMode(allocator: &Allocator, source_path: []const u8, dest_path: [ %return getRandomBytes(rand_buf[0..]); _ = base64.encodeWithAlphabet(tmp_path[dest_path.len..], rand_buf, b64_fs_alphabet); - var out_stream = %return io.OutStream.openMode(tmp_path, mode, allocator); - defer out_stream.close(); + var out_file = %return io.File.openWriteMode(tmp_path, mode, allocator); + defer out_file.close(); %defer _ = deleteFile(allocator, tmp_path); - var in_stream = %return io.InStream.open(source_path, allocator); - defer in_stream.close(); + var in_file = %return io.File.openRead(source_path, allocator); + defer in_file.close(); - const buf = out_stream.buffer[0..]; + var buf: [page_size]u8 = undefined; while (true) { - const amt = %return in_stream.read(buf); - out_stream.index = amt; - %return out_stream.flush(); - if (amt != out_stream.buffer.len) + const amt = %return in_file.in_stream.read(buf[0..]); + %return out_file.out_stream.write(buf[0..amt]); + if (amt != buf.len) return rename(allocator, tmp_path, dest_path); } } @@ -973,7 +977,7 @@ pub const Dir = struct { pub fn close(self: &Dir) { self.allocator.free(self.buf); - posixClose(self.fd); + close(self.fd); } /// Memory such as file names referenced in this returned entry becomes invalid @@ -1135,7 +1139,6 @@ test "os.sleep" { sleep(0, 1); } - error ResourceLimitReached; error InvalidUserId; error PermissionDenied; @@ -1184,6 +1187,21 @@ pub fn posix_setregid(rgid: u32, egid: u32) -> %void { }; } +error NoStdHandles; +pub fn windowsGetStdHandle(handle_id: windows.DWORD) -> %windows.HANDLE { + if (windows.GetStdHandle(handle_id)) |handle| { + if (handle == windows.INVALID_HANDLE_VALUE) { + const err = windows.GetLastError(); + return switch (err) { + else => os.unexpectedErrorWindows(err), + }; + } + return handle; + } else { + return error.NoStdHandles; + } +} + pub const ArgIteratorPosix = struct { index: usize, count: usize, @@ -1458,3 +1476,28 @@ pub fn unexpectedErrorWindows(err: windows.DWORD) -> error { } return error.Unexpected; } + +pub fn openSelfExe() -> %io.File { + switch (builtin.os) { + Os.linux => { + return io.File.openRead("/proc/self/exe", null); + }, + Os.darwin => { + @panic("TODO: openSelfExe on Darwin"); + }, + else => @compileError("Unsupported OS"), + } +} + +pub fn isTty(handle: FileHandle) -> bool { + if (is_windows) { + return windows_util.windowsIsTty(handle); + } else { + if (builtin.link_libc) { + return c.isatty(handle) != 0; + } else { + return posix.isatty(handle); + } + } +} + diff --git a/std/os/path.zig b/std/os/path.zig index beaabc38d6..9a7b6b139e 100644 --- a/std/os/path.zig +++ b/std/os/path.zig @@ -940,7 +940,7 @@ pub fn real(allocator: &Allocator, pathname: []const u8) -> %[]u8 { else => os.unexpectedErrorWindows(err), }; } - defer os.windowsClose(h_file); + defer os.close(h_file); var buf = %return allocator.alloc(u8, 256); %defer allocator.free(buf); while (true) { @@ -1009,7 +1009,7 @@ pub fn real(allocator: &Allocator, pathname: []const u8) -> %[]u8 { }, Os.linux => { const fd = %return os.posixOpen(pathname, posix.O_PATH|posix.O_NONBLOCK|posix.O_CLOEXEC, 0, allocator); - defer os.posixClose(fd); + defer os.close(fd); var buf: ["/proc/self/fd/-2147483648".len]u8 = undefined; const proc_path = fmt.bufPrint(buf[0..], "/proc/self/fd/{}", fd); diff --git a/std/special/build_runner.zig b/std/special/build_runner.zig index 089137c923..162cd31c25 100644 --- a/std/special/build_runner.zig +++ b/std/special/build_runner.zig @@ -6,6 +6,7 @@ const os = std.os; const Builder = std.build.Builder; const mem = std.mem; const ArrayList = std.ArrayList; +const warn = std.debug.warn; error InvalidArgs; @@ -13,7 +14,7 @@ pub fn main() -> %void { var arg_it = os.args(); // TODO use a more general purpose allocator here - var inc_allocator = %%mem.IncrementingAllocator.init(20 * 1024 * 1024); + var inc_allocator = %%std.heap.IncrementingAllocator.init(20 * 1024 * 1024); defer inc_allocator.deinit(); const allocator = &inc_allocator.allocator; @@ -23,15 +24,15 @@ pub fn main() -> %void { _ = arg_it.skip(); const zig_exe = %return unwrapArg(arg_it.next(allocator) ?? { - %%io.stderr.printf("Expected first argument to be path to zig compiler\n"); + warn("Expected first argument to be path to zig compiler\n"); return error.InvalidArgs; }); const build_root = %return unwrapArg(arg_it.next(allocator) ?? { - %%io.stderr.printf("Expected second argument to be build root directory path\n"); + warn("Expected second argument to be build root directory path\n"); return error.InvalidArgs; }); const cache_root = %return unwrapArg(arg_it.next(allocator) ?? { - %%io.stderr.printf("Expected third argument to be cache root directory path\n"); + warn("Expected third argument to be cache root directory path\n"); return error.InvalidArgs; }); @@ -42,32 +43,37 @@ pub fn main() -> %void { var prefix: ?[]const u8 = null; + var stderr_file = io.getStdErr(); + var stderr_stream: %&io.OutStream = if (stderr_file) |*f| &f.out_stream else |err| err; + var stdout_file = io.getStdOut(); + var stdout_stream: %&io.OutStream = if (stdout_file) |*f| &f.out_stream else |err| err; + while (arg_it.next(allocator)) |err_or_arg| { const arg = %return unwrapArg(err_or_arg); if (mem.startsWith(u8, arg, "-D")) { const option_contents = arg[2..]; if (option_contents.len == 0) { - %%io.stderr.printf("Expected option name after '-D'\n\n"); - return usage(&builder, false, &io.stderr); + warn("Expected option name after '-D'\n\n"); + return usageAndErr(&builder, false, %return stderr_stream); } if (mem.indexOfScalar(u8, option_contents, '=')) |name_end| { const option_name = option_contents[0..name_end]; const option_value = option_contents[name_end + 1..]; if (builder.addUserInputOption(option_name, option_value)) - return usage(&builder, false, &io.stderr); + return usageAndErr(&builder, false, %return stderr_stream); } else { if (builder.addUserInputFlag(option_contents)) - return usage(&builder, false, &io.stderr); + return usageAndErr(&builder, false, %return stderr_stream); } } else if (mem.startsWith(u8, arg, "-")) { if (mem.eql(u8, arg, "--verbose")) { builder.verbose = true; } else if (mem.eql(u8, arg, "--help")) { - return usage(&builder, false, &io.stdout); + return usage(&builder, false, %return stdout_stream); } else if (mem.eql(u8, arg, "--prefix")) { prefix = %return unwrapArg(arg_it.next(allocator) ?? { - %%io.stderr.printf("Expected argument after --prefix\n\n"); - return usage(&builder, false, &io.stderr); + warn("Expected argument after --prefix\n\n"); + return usageAndErr(&builder, false, %return stderr_stream); }); } else if (mem.eql(u8, arg, "--verbose-tokenize")) { builder.verbose_tokenize = true; @@ -82,8 +88,8 @@ pub fn main() -> %void { } else if (mem.eql(u8, arg, "--verbose-cimport")) { builder.verbose_cimport = true; } else { - %%io.stderr.printf("Unrecognized argument: {}\n\n", arg); - return usage(&builder, false, &io.stderr); + warn("Unrecognized argument: {}\n\n", arg); + return usageAndErr(&builder, false, %return stderr_stream); } } else { %%targets.append(arg); @@ -94,11 +100,11 @@ pub fn main() -> %void { root.build(&builder); if (builder.validateUserInputDidItFail()) - return usage(&builder, true, &io.stderr); + return usageAndErr(&builder, true, %return stderr_stream); builder.make(targets.toSliceConst()) %% |err| { if (err == error.InvalidStepName) { - return usage(&builder, true, &io.stderr); + return usageAndErr(&builder, true, %return stderr_stream); } return err; }; @@ -112,7 +118,7 @@ fn usage(builder: &Builder, already_ran_build: bool, out_stream: &io.OutStream) } // This usage text has to be synchronized with src/main.cpp - %%out_stream.printf( + %return out_stream.print( \\Usage: {} build [steps] [options] \\ \\Steps: @@ -121,10 +127,10 @@ fn usage(builder: &Builder, already_ran_build: bool, out_stream: &io.OutStream) const allocator = builder.allocator; for (builder.top_level_steps.toSliceConst()) |top_level_step| { - %%out_stream.printf(" {s22} {}\n", top_level_step.step.name, top_level_step.description); + %return out_stream.print(" {s22} {}\n", top_level_step.step.name, top_level_step.description); } - %%out_stream.write( + %return out_stream.write( \\ \\General Options: \\ --help Print this help and exit @@ -136,17 +142,17 @@ fn usage(builder: &Builder, already_ran_build: bool, out_stream: &io.OutStream) ); if (builder.available_options_list.len == 0) { - %%out_stream.print(" (none)\n"); + %return out_stream.print(" (none)\n"); } else { for (builder.available_options_list.toSliceConst()) |option| { - const name = %%fmt.allocPrint(allocator, + const name = %return fmt.allocPrint(allocator, " -D{}=${}", option.name, Builder.typeIdName(option.type_id)); defer allocator.free(name); - %%out_stream.print("{s24} {}\n", name, option.description); + %return out_stream.print("{s24} {}\n", name, option.description); } } - %%out_stream.write( + %return out_stream.write( \\ \\Advanced Options: \\ --build-file $file Override path to build.zig @@ -159,16 +165,16 @@ fn usage(builder: &Builder, already_ran_build: bool, out_stream: &io.OutStream) \\ --verbose-cimport Enable compiler debug output for C imports \\ ); +} - %%out_stream.flush(); - - if (out_stream == &io.stderr) - return error.InvalidArgs; +fn usageAndErr(builder: &Builder, already_ran_build: bool, out_stream: &io.OutStream) -> error { + usage(builder, already_ran_build, out_stream) %% {}; + return error.InvalidArgs; } fn unwrapArg(arg: %[]u8) -> %[]u8 { return arg %% |err| { - %%io.stderr.printf("Unable to parse command line: {}\n", 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 a1a1bf320f..f133eed59f 100644 --- a/std/special/test_runner.zig +++ b/std/special/test_runner.zig @@ -1,13 +1,15 @@ -const io = @import("std").io; +const std = @import("std"); +const io = std.io; const builtin = @import("builtin"); const test_fn_list = builtin.__zig_test_fn_slice; +const warn = std.debug.warn; pub fn main() -> %void { for (test_fn_list) |test_fn, i| { - %%io.stderr.printf("Test {}/{} {}...", i + 1, test_fn_list.len, test_fn.name); + warn("Test {}/{} {}...", i + 1, test_fn_list.len, test_fn.name); test_fn.func(); - %%io.stderr.printf("OK\n"); + warn("OK\n"); } } diff --git a/test/compare_output.zig b/test/compare_output.zig index d49b694b08..141bdf0aa0 100644 --- a/test/compare_output.zig +++ b/test/compare_output.zig @@ -17,7 +17,8 @@ pub fn addCases(cases: &tests.CompareOutputContext) { \\ \\pub fn main() -> %void { \\ privateFunction(); - \\ %%stdout.printf("OK 2\n"); + \\ const stdout = &(%%getStdOut()).out_stream; + \\ %%stdout.print("OK 2\n"); \\} \\ \\fn privateFunction() { @@ -31,7 +32,8 @@ pub fn addCases(cases: &tests.CompareOutputContext) { \\// purposefully conflicting function with main.zig \\// but it's private so it should be OK \\fn privateFunction() { - \\ %%stdout.printf("OK 1\n"); + \\ const stdout = &(%%getStdOut()).out_stream; + \\ %%stdout.print("OK 1\n"); \\} \\ \\pub fn printText() { @@ -56,7 +58,8 @@ pub fn addCases(cases: &tests.CompareOutputContext) { tc.addSourceFile("foo.zig", \\use @import("std").io; \\pub fn foo_function() { - \\ %%stdout.printf("OK\n"); + \\ const stdout = &(%%getStdOut()).out_stream; + \\ %%stdout.print("OK\n"); \\} ); @@ -66,7 +69,8 @@ pub fn addCases(cases: &tests.CompareOutputContext) { \\ \\pub fn bar_function() { \\ if (foo_function()) { - \\ %%stdout.printf("OK\n"); + \\ const stdout = &(%%getStdOut()).out_stream; + \\ %%stdout.print("OK\n"); \\ } \\} ); @@ -97,7 +101,8 @@ pub fn addCases(cases: &tests.CompareOutputContext) { \\pub const a_text = "OK\n"; \\ \\pub fn ok() { - \\ %%io.stdout.printf(b_text); + \\ const stdout = &(%%io.getStdOut()).out_stream; + \\ %%stdout.print(b_text); \\} ); @@ -114,7 +119,8 @@ pub fn addCases(cases: &tests.CompareOutputContext) { \\const io = @import("std").io; \\ \\pub fn main() -> %void { - \\ %%io.stdout.printf("Hello, world!\n{d4} {x3} {c}\n", u32(12), u16(0x12), u8('a')); + \\ const stdout = &(%%io.getStdOut()).out_stream; + \\ %%stdout.print("Hello, world!\n{d4} {x3} {c}\n", u32(12), u16(0x12), u8('a')); \\} , "Hello, world!\n0012 012 a\n"); @@ -266,7 +272,8 @@ pub fn addCases(cases: &tests.CompareOutputContext) { \\ var x_local : i32 = print_ok(x); \\} \\fn print_ok(val: @typeOf(x)) -> @typeOf(foo) { - \\ %%io.stdout.printf("OK\n"); + \\ const stdout = &(%%io.getStdOut()).out_stream; + \\ %%stdout.print("OK\n"); \\ return 0; \\} \\const foo : i32 = 0; @@ -347,24 +354,26 @@ pub fn addCases(cases: &tests.CompareOutputContext) { \\pub fn main() -> %void { \\ const bar = Bar {.field2 = 13,}; \\ const foo = Foo {.field1 = bar,}; + \\ const stdout = &(%%io.getStdOut()).out_stream; \\ if (!foo.method()) { - \\ %%io.stdout.printf("BAD\n"); + \\ %%stdout.print("BAD\n"); \\ } \\ if (!bar.method()) { - \\ %%io.stdout.printf("BAD\n"); + \\ %%stdout.print("BAD\n"); \\ } - \\ %%io.stdout.printf("OK\n"); + \\ %%stdout.print("OK\n"); \\} , "OK\n"); cases.add("defer with only fallthrough", \\const io = @import("std").io; \\pub fn main() -> %void { - \\ %%io.stdout.printf("before\n"); - \\ defer %%io.stdout.printf("defer1\n"); - \\ defer %%io.stdout.printf("defer2\n"); - \\ defer %%io.stdout.printf("defer3\n"); - \\ %%io.stdout.printf("after\n"); + \\ const stdout = &(%%io.getStdOut()).out_stream; + \\ %%stdout.print("before\n"); + \\ defer %%stdout.print("defer1\n"); + \\ defer %%stdout.print("defer2\n"); + \\ defer %%stdout.print("defer3\n"); + \\ %%stdout.print("after\n"); \\} , "before\nafter\ndefer3\ndefer2\ndefer1\n"); @@ -372,13 +381,14 @@ pub fn addCases(cases: &tests.CompareOutputContext) { \\const io = @import("std").io; \\const os = @import("std").os; \\pub fn main() -> %void { - \\ %%io.stdout.printf("before\n"); - \\ defer %%io.stdout.printf("defer1\n"); - \\ defer %%io.stdout.printf("defer2\n"); + \\ const stdout = &(%%io.getStdOut()).out_stream; + \\ %%stdout.print("before\n"); + \\ defer %%stdout.print("defer1\n"); + \\ defer %%stdout.print("defer2\n"); \\ var args_it = @import("std").os.args(); \\ if (args_it.skip() and !args_it.skip()) return; - \\ defer %%io.stdout.printf("defer3\n"); - \\ %%io.stdout.printf("after\n"); + \\ defer %%stdout.print("defer3\n"); + \\ %%stdout.print("after\n"); \\} , "before\ndefer2\ndefer1\n"); @@ -388,12 +398,13 @@ pub fn addCases(cases: &tests.CompareOutputContext) { \\ do_test() %% return; \\} \\fn do_test() -> %void { - \\ %%io.stdout.printf("before\n"); - \\ defer %%io.stdout.printf("defer1\n"); - \\ %defer %%io.stdout.printf("deferErr\n"); + \\ const stdout = &(%%io.getStdOut()).out_stream; + \\ %%stdout.print("before\n"); + \\ defer %%stdout.print("defer1\n"); + \\ %defer %%stdout.print("deferErr\n"); \\ %return its_gonna_fail(); - \\ defer %%io.stdout.printf("defer3\n"); - \\ %%io.stdout.printf("after\n"); + \\ defer %%stdout.print("defer3\n"); + \\ %%stdout.print("after\n"); \\} \\error IToldYouItWouldFail; \\fn its_gonna_fail() -> %void { @@ -407,12 +418,13 @@ pub fn addCases(cases: &tests.CompareOutputContext) { \\ do_test() %% return; \\} \\fn do_test() -> %void { - \\ %%io.stdout.printf("before\n"); - \\ defer %%io.stdout.printf("defer1\n"); - \\ %defer %%io.stdout.printf("deferErr\n"); + \\ const stdout = &(%%io.getStdOut()).out_stream; + \\ %%stdout.print("before\n"); + \\ defer %%stdout.print("defer1\n"); + \\ %defer %%stdout.print("deferErr\n"); \\ %return its_gonna_pass(); - \\ defer %%io.stdout.printf("defer3\n"); - \\ %%io.stdout.printf("after\n"); + \\ defer %%stdout.print("defer3\n"); + \\ %%stdout.print("after\n"); \\} \\fn its_gonna_pass() -> %void { } , "before\nafter\ndefer3\ndefer1\n"); @@ -423,7 +435,8 @@ pub fn addCases(cases: &tests.CompareOutputContext) { \\const io = @import("std").io; \\ \\pub fn main() -> %void { - \\ %%io.stdout.printf(foo_txt); + \\ const stdout = &(%%io.getStdOut()).out_stream; + \\ %%stdout.print(foo_txt); \\} , "1234\nabcd\n"); diff --git a/test/tests.zig b/test/tests.zig index 3755214161..b762376005 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -1,5 +1,6 @@ const std = @import("std"); const debug = std.debug; +const warn = debug.warn; const build = std.build; const os = std.os; const StdIo = os.ChildProcess.StdIo; @@ -50,6 +51,8 @@ const test_targets = []TestTarget { error TestFailed; +const max_stdout_size = 1 * 1024 * 1024; // 1 MB + pub fn addCompareOutputTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step { const cases = %%b.allocator.create(CompareOutputContext); *cases = CompareOutputContext { @@ -231,7 +234,7 @@ pub const CompareOutputContext = struct { const full_exe_path = b.pathFromRoot(self.exe_path); - %%io.stderr.printf("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name); + warn("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name); const child = %%os.ChildProcess.init([][]u8{full_exe_path}, b.allocator); defer child.deinit(); @@ -246,8 +249,8 @@ pub const CompareOutputContext = struct { var stdout = Buffer.initNull(b.allocator); var stderr = Buffer.initNull(b.allocator); - %%(??child.stdout).readAll(&stdout); - %%(??child.stderr).readAll(&stderr); + %%(??child.stdout).in_stream.readAllBuffer(&stdout, max_stdout_size); + %%(??child.stderr).in_stream.readAllBuffer(&stderr, max_stdout_size); const term = child.wait() %% |err| { debug.panic("Unable to spawn {}: {}\n", full_exe_path, @errorName(err)); @@ -255,19 +258,19 @@ pub const CompareOutputContext = struct { switch (term) { Term.Exited => |code| { if (code != 0) { - %%io.stderr.printf("Process {} exited with error code {}\n", full_exe_path, code); + warn("Process {} exited with error code {}\n", full_exe_path, code); return error.TestFailed; } }, else => { - %%io.stderr.printf("Process {} terminated unexpectedly\n", full_exe_path); + warn("Process {} terminated unexpectedly\n", full_exe_path); return error.TestFailed; }, }; if (!mem.eql(u8, self.expected_output, stdout.toSliceConst())) { - %%io.stderr.printf( + warn( \\ \\========= Expected this output: ========= \\{} @@ -277,7 +280,7 @@ pub const CompareOutputContext = struct { , self.expected_output, stdout.toSliceConst()); return error.TestFailed; } - %%io.stderr.printf("OK\n"); + warn("OK\n"); } }; @@ -310,7 +313,7 @@ pub const CompareOutputContext = struct { const full_exe_path = b.pathFromRoot(self.exe_path); - %%io.stderr.printf("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name); + warn("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name); const child = %%os.ChildProcess.init([][]u8{full_exe_path}, b.allocator); defer child.deinit(); @@ -328,24 +331,24 @@ pub const CompareOutputContext = struct { switch (term) { Term.Exited => |code| { if (code != expected_exit_code) { - %%io.stderr.printf("\nProgram expected to exit with code {} " ++ + warn("\nProgram expected to exit with code {} " ++ "but exited with code {}\n", expected_exit_code, code); return error.TestFailed; } }, Term.Signal => |sig| { - %%io.stderr.printf("\nProgram expected to exit with code {} " ++ + warn("\nProgram expected to exit with code {} " ++ "but instead signaled {}\n", expected_exit_code, sig); return error.TestFailed; }, else => { - %%io.stderr.printf("\nProgram expected to exit with code {}" ++ + warn("\nProgram expected to exit with code {}" ++ " but exited in an unexpected way\n", expected_exit_code); return error.TestFailed; }, } - %%io.stderr.printf("OK\n"); + warn("OK\n"); } }; @@ -554,7 +557,7 @@ pub const CompileErrorContext = struct { Mode.ReleaseFast => %%zig_args.append("--release-fast"), } - %%io.stderr.printf("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name); + warn("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name); if (b.verbose) { printInvocation(zig_args.toSliceConst()); @@ -573,8 +576,8 @@ pub const CompileErrorContext = struct { var stdout_buf = Buffer.initNull(b.allocator); var stderr_buf = Buffer.initNull(b.allocator); - %%(??child.stdout).readAll(&stdout_buf); - %%(??child.stderr).readAll(&stderr_buf); + %%(??child.stdout).in_stream.readAllBuffer(&stdout_buf, max_stdout_size); + %%(??child.stderr).in_stream.readAllBuffer(&stderr_buf, max_stdout_size); const term = child.wait() %% |err| { debug.panic("Unable to spawn {}: {}\n", zig_args.items[0], @errorName(err)); @@ -582,12 +585,12 @@ pub const CompileErrorContext = struct { switch (term) { Term.Exited => |code| { if (code == 0) { - %%io.stderr.printf("Compilation incorrectly succeeded\n"); + warn("Compilation incorrectly succeeded\n"); return error.TestFailed; } }, else => { - %%io.stderr.printf("Process {} terminated unexpectedly\n", b.zig_exe); + warn("Process {} terminated unexpectedly\n", b.zig_exe); return error.TestFailed; }, }; @@ -597,7 +600,7 @@ pub const CompileErrorContext = struct { const stderr = stderr_buf.toSliceConst(); if (stdout.len != 0) { - %%io.stderr.printf( + warn( \\ \\Expected empty stdout, instead found: \\================================================ @@ -610,7 +613,7 @@ pub const CompileErrorContext = struct { for (self.case.expected_errors.toSliceConst()) |expected_error| { if (mem.indexOf(u8, stderr, expected_error) == null) { - %%io.stderr.printf( + warn( \\ \\========= Expected this compile error: ========= \\{} @@ -621,15 +624,15 @@ pub const CompileErrorContext = struct { return error.TestFailed; } } - %%io.stderr.printf("OK\n"); + warn("OK\n"); } }; fn printInvocation(args: []const []const u8) { for (args) |arg| { - %%io.stderr.printf("{} ", arg); + warn("{} ", arg); } - %%io.stderr.printf("\n"); + warn("\n"); } pub fn create(self: &CompileErrorContext, name: []const u8, source: []const u8, @@ -822,7 +825,7 @@ pub const ParseCContext = struct { %%zig_args.append("parsec"); %%zig_args.append(b.pathFromRoot(root_src)); - %%io.stderr.printf("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name); + warn("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name); if (b.verbose) { printInvocation(zig_args.toSliceConst()); @@ -841,8 +844,8 @@ pub const ParseCContext = struct { var stdout_buf = Buffer.initNull(b.allocator); var stderr_buf = Buffer.initNull(b.allocator); - %%(??child.stdout).readAll(&stdout_buf); - %%(??child.stderr).readAll(&stderr_buf); + %%(??child.stdout).in_stream.readAllBuffer(&stdout_buf, max_stdout_size); + %%(??child.stderr).in_stream.readAllBuffer(&stderr_buf, max_stdout_size); const term = child.wait() %% |err| { debug.panic("Unable to spawn {}: {}\n", zig_args.toSliceConst()[0], @errorName(err)); @@ -850,16 +853,16 @@ pub const ParseCContext = struct { switch (term) { Term.Exited => |code| { if (code != 0) { - %%io.stderr.printf("Compilation failed with exit code {}\n", code); + warn("Compilation failed with exit code {}\n", code); return error.TestFailed; } }, Term.Signal => |code| { - %%io.stderr.printf("Compilation failed with signal {}\n", code); + warn("Compilation failed with signal {}\n", code); return error.TestFailed; }, else => { - %%io.stderr.printf("Compilation terminated unexpectedly\n"); + warn("Compilation terminated unexpectedly\n"); return error.TestFailed; }, }; @@ -868,7 +871,7 @@ pub const ParseCContext = struct { const stderr = stderr_buf.toSliceConst(); if (stderr.len != 0 and !self.case.allow_warnings) { - %%io.stderr.printf( + warn( \\====== parsec emitted warnings: ============ \\{} \\============================================ @@ -879,7 +882,7 @@ pub const ParseCContext = struct { for (self.case.expected_lines.toSliceConst()) |expected_line| { if (mem.indexOf(u8, stdout, expected_line) == null) { - %%io.stderr.printf( + warn( \\ \\========= Expected this output: ================ \\{} @@ -890,15 +893,15 @@ pub const ParseCContext = struct { return error.TestFailed; } } - %%io.stderr.printf("OK\n"); + warn("OK\n"); } }; fn printInvocation(args: []const []const u8) { for (args) |arg| { - %%io.stderr.printf("{} ", arg); + warn("{} ", arg); } - %%io.stderr.printf("\n"); + warn("\n"); } pub fn create(self: &ParseCContext, allow_warnings: bool, filename: []const u8, name: []const u8,